<?php

namespace App\Modules\HR\Employees\Repositories;

use App\Modules\HR\Employees\Models\Employee;
use App\Support\Lists\ListQuery;
use App\Support\SelectOptionFormatter;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\Request;
use Illuminate\Support\LazyCollection;

class EmployeeRepository
{
    public function countForTenant(int $tenantId): int
    {
        return Employee::query()->where('tenant_id', $tenantId)->count();
    }

    /**
     * @return array<int, string>
     */
    public function listForSelect(int $tenantId): array
    {
        return Employee::query()
            ->where('tenant_id', $tenantId)
            ->orderBy('full_name')
            ->pluck('full_name', 'id')
            ->all();
    }

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

        if ($onlyId !== null) {
            $builder->whereKey($onlyId);
        } else {
            if ($excludeId !== null) {
                $builder->whereKeyNot($excludeId);
            }
            if ($query !== '') {
                $builder->where(function ($q) use ($query): void {
                    $q->where('full_name', 'like', '%'.$query.'%')
                        ->orWhere('employee_code', 'like', '%'.$query.'%');
                });
            }
            $builder->limit($limit);
        }

        return SelectOptionFormatter::mapRows(
            $builder->get(['id', 'full_name', 'employee_code']),
            'full_name',
            'employee_code',
        );
    }

    public function paginateForList(Request $request): LengthAwarePaginator
    {
        $query = Employee::query()
            ->with([
                'department:id,name,tenant_id',
                'position:id,title,tenant_id,department_id',
                'address',
            ]);

        ListQuery::applySearch($query, $request->input('q'), [
            'full_name',
            'employee_code',
            'email',
            'company_email',
        ]);
        ListQuery::applyEquals($query, 'department_id', $request->input('department_id'));
        ListQuery::applyEquals($query, 'position_id', $request->input('position_id'));
        ListQuery::applyBoolean($query, 'is_active', $request->input('is_active'));
        ListQuery::applyDateFrom($query, 'hire_date', $request->input('hire_date_from'));
        ListQuery::applyDateTo($query, 'hire_date', $request->input('hire_date_to'));

        if (! $request->filled('sort')) {
            $query->orderByDesc('hire_date')->orderBy('full_name');
        } else {
            ListQuery::applySort(
                $query,
                $request->input('sort'),
                $request->input('dir'),
                [
                    'employee_code' => 'employee_code',
                    'full_name' => 'full_name',
                    'hire_date' => 'hire_date',
                    'is_active' => 'is_active',
                ],
                'hire_date',
                'desc',
            );
        }

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

    /**
     * @return LazyCollection<int, Employee>
     */
    public function cursorForTenantExport(int $tenantId): LazyCollection
    {
        return Employee::query()
            ->where('tenant_id', $tenantId)
            ->with([
                'department:id,name,tenant_id',
                'position:id,title,tenant_id,department_id',
            ])
            ->orderBy('full_name')
            ->orderBy('id')
            ->cursor();
    }

    /**
     * @return LazyCollection<int, Employee>
     */
    public function cursorActiveForTenant(int $tenantId): LazyCollection
    {
        return Employee::query()
            ->where('tenant_id', $tenantId)
            ->where('is_active', true)
            ->orderBy('id')
            ->cursor();
    }

    public function findForTenant(int $tenantId, int $employeeId): ?Employee
    {
        return Employee::query()
            ->where('tenant_id', $tenantId)
            ->whereKey($employeeId)
            ->first();
    }

    public function findOrFailForEdit(int $id): Employee
    {
        return Employee::query()
            ->with([
                'department:id,name,tenant_id',
                'position:id,title,tenant_id,department_id',
                'branch:id,name,tenant_id',
                'section:id,name,tenant_id',
                'team:id,name,tenant_id',
                'jobGrade:id,name,tenant_id',
                'workLocation:id,name,tenant_id',
                'costCenter:id,name,tenant_id',
                'manager:id,tenant_id,full_name',
                'user:id,tenant_id,name,email,employee_id',
                'address',
                'documents',
            ])
            ->findOrFail($id);
    }

    public function findOrFailForShow(int $id): Employee
    {
        return Employee::query()
            ->with([
                'department:id,name,tenant_id',
                'position:id,title,tenant_id,department_id',
                'manager:id,tenant_id,full_name,employee_code',
                'employmentStatus:id,name,code',
                'branch:id,name,tenant_id',
                'section:id,name,tenant_id',
                'team:id,name,tenant_id',
                'jobGrade:id,name,tenant_id',
                'workLocation:id,name,tenant_id',
                'costCenter:id,name,tenant_id',
                'user:id,tenant_id,name,email,employee_id',
                'address',
                'documents',
            ])
            ->findOrFail($id);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function create(array $data): Employee
    {
        return Employee::query()->create($data);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function update(Employee $employee, array $data): Employee
    {
        $employee->update($data);

        return $employee->fresh([
            'department:id,name,tenant_id',
            'position:id,title,tenant_id,department_id',
            'address',
            'documents',
        ]);
    }
}
