<?php

namespace App\Modules\HR\Payroll\Repositories;

use App\Modules\HR\Payroll\Models\SalaryStructure;
use App\Modules\HR\Payroll\Models\SalaryStructureComponent;
use App\Support\Lists\ListQuery;
use App\Support\SelectOptionFormatter;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;

class SalaryStructureRepository
{
    public function paginateForIndex(Request $request): LengthAwarePaginator
    {
        $query = SalaryStructure::query()->withCount('components');

        ListQuery::applySearch($query, $request->input('q'), ['name', 'code']);
        ListQuery::applyBoolean($query, 'is_active', $request->input('is_active'));
        ListQuery::applySort(
            $query,
            $request->input('sort'),
            $request->input('dir'),
            [
                'name' => 'name',
                'code' => 'code',
                'currency' => 'currency',
                'is_active' => 'is_active',
            ],
            'name',
        );

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

    public function findOrFail(int $id): SalaryStructure
    {
        return SalaryStructure::query()->with('components')->findOrFail($id);
    }

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

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

        return $structure->fresh();
    }

    /**
     * Replace the structure's components with the given rows, keeping existing
     * rows (by id) updated and removing rows that were dropped from the form.
     *
     * @param  list<array{id: ?int, code: ?string, name: string, component_type: string, calculation: string, amount: float, is_taxable: bool, sort_order: int}>  $components
     */
    public function syncComponents(SalaryStructure $structure, array $components): void
    {
        $keepIds = [];

        foreach ($components as $row) {
            $id = $row['id'];
            unset($row['id']);

            if ($id !== null) {
                $component = SalaryStructureComponent::query()
                    ->where('structure_id', $structure->id)
                    ->whereKey($id)
                    ->first();

                if ($component !== null) {
                    $component->update($row);
                    $keepIds[] = $component->id;

                    continue;
                }
            }

            $created = $structure->components()->create($row);
            $keepIds[] = $created->id;
        }

        $structure->components()->whereNotIn('id', $keepIds)->delete();
    }

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

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

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

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

    public function collection(): Collection
    {
        return SalaryStructure::query()->orderBy('name')->get();
    }
}
