<?php

namespace App\Modules\HR\Departments\Repositories;

use App\Modules\HR\Departments\Models\Department;
use App\Support\Lists\ListQuery;
use App\Support\LookupCodeColumn;
use App\Support\SelectOptionFormatter;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;

class DepartmentRepository
{
    public function allWithParent(): Collection
    {
        return Department::query()
            ->with('parent')
            ->orderBy('name')
            ->get();
    }

    public function paginateForIndex(Request $request): LengthAwarePaginator
    {
        $query = Department::query()->with('parent');

        ListQuery::applySearch($query, $request->input('q'), array_values(array_filter([
            'name',
            LookupCodeColumn::exists('departments') ? 'code' : null,
        ])));
        ListQuery::applyBoolean($query, 'is_active', $request->input('is_active'));
        ListQuery::applySort(
            $query,
            $request->input('sort'),
            $request->input('dir'),
            [
                'name' => 'name',
                'code' => 'code',
                'is_active' => 'is_active',
                'created_at' => 'created_at',
            ],
            'name',
        );

        return ListQuery::paginate($query, $request);
    }

    public function find(int $id): ?Department
    {
        return Department::query()->find($id);
    }

    public function findOrFail(int $id): Department
    {
        return Department::query()->findOrFail($id);
    }

    public function findWithParent(int $id): Department
    {
        return Department::query()
            ->with('parent')
            ->findOrFail($id);
    }

    public function create(array $data): Department
    {
        return Department::query()->create($data);
    }

    public function update(Department $department, array $data): bool
    {
        return $department->update($data);
    }

    public function delete(Department $department): bool
    {
        return (bool) $department->delete();
    }

    public function hasChildren(int $departmentId): bool
    {
        return Department::query()
            ->where('parent_id', $departmentId)
            ->exists();
    }

    /**
     * @return list<int>
     */
    public function descendantIds(int $rootId): array
    {
        $ids = [];
        $direct = Department::query()
            ->where('parent_id', $rootId)
            ->pluck('id')
            ->all();

        foreach ($direct as $childId) {
            $ids[] = (int) $childId;
            foreach ($this->descendantIds((int) $childId) as $nestedId) {
                $ids[] = $nestedId;
            }
        }

        return $ids;
    }

    public function countByIds(array $ids): int
    {
        return Department::query()->whereIn('id', $ids)->count();
    }

    /**
     * @return array<int, string>
     */
    public function listForSelect(): array
    {
        return Department::query()
            ->where('is_active', true)
            ->orderBy('name')
            ->pluck('name', 'id')
            ->all();
    }

    /**
     * @return list<array{value: string, label: string}>
     */
    public function searchForLookup(
        string $query,
        int $limit = 30,
        ?int $excludeId = null,
        ?int $onlyId = null,
        ?int $tenantId = null,
    ): array {
        $builder = Department::query()
            ->where('is_active', true)
            ->orderBy('name');

        if ($tenantId !== null && $tenantId > 0) {
            $builder->where('tenant_id', $tenantId);
        }

        if ($onlyId !== null) {
            $builder->whereKey($onlyId);
        } else {
            if ($excludeId !== null) {
                $blocked = array_merge([$excludeId], $this->descendantIds($excludeId));
                $builder->whereNotIn('id', $blocked);
            }
            if ($query !== '') {
                $builder->where(function ($q) use ($query): void {
                    $q->where('name', 'like', '%'.$query.'%');
                    if (LookupCodeColumn::exists('departments')) {
                        $q->orWhere('code', 'like', '%'.$query.'%');
                    }
                });
            }
            $builder->limit($limit);
        }

        $columns = ['id', 'name'];
        $codeKey = LookupCodeColumn::exists('departments') ? 'code' : null;
        if ($codeKey !== null) {
            $columns[] = 'code';
        }

        return SelectOptionFormatter::mapRows(
            $builder->get($columns),
            'name',
            $codeKey,
        );
    }

    public function belongsToTenant(int $departmentId, int $tenantId): bool
    {
        return Department::query()
            ->where('id', $departmentId)
            ->where('tenant_id', $tenantId)
            ->exists();
    }
}
