<?php

namespace App\Services\Payroll;

use App\Models\User;
use App\Services\Payroll\PayrollPeriodDeductionService;
use App\Services\Payroll\SalaryStructureResolver;
use App\Services\Payroll\Statutory\EsiCalculator;
use App\Services\Payroll\Statutory\PfCalculator;
use App\Services\Payroll\Statutory\PtCalculator;
use Carbon\Carbon;
use Illuminate\Support\Collection;

class EmployeePayrollCalculator
{
    public function __construct(
        protected LeaveLopCalculator $lopCalculator,
        protected PfCalculator $pfCalculator,
        protected EsiCalculator $esiCalculator,
        protected PtCalculator $ptCalculator,
        protected SalaryStructureResolver $salaryResolver,
        protected PayrollPeriodDeductionService $periodDeductions
    ) {}

    /**
     * Calculate payroll for one employee. Pure calculation; no DB writes.
     *
     * @param array{pf_enabled: bool, pf_rate: float, pf_ceiling: float|null, esi_enabled: bool, esi_employee_rate: float, esi_employer_rate: float, esi_cap: float|null, pt_enabled: bool} $settings
     * @param array<int, array{min_gross: float, max_gross: float|null, amount: float}> $ptSlabs
     * @return array{ctc_annual: float, gross_monthly: float, net_monthly: float, total_deductions: float, lop_days: float, lop_amount: float, working_days: int, paid_days: float, full_month_working_days: int, proration_factor: float, earnings: array, deductions: array, employer_contributions: array}|null
     */
    public function calculate(
        User $user,
        int $companyId,
        Carbon $start,
        Carbon $end,
        array $settings,
        array $ptSlabs,
        ?Collection $componentDefinitions = null
    ): ?array {
        $holidayDates = $this->lopCalculator->getHolidayDates($companyId, $start, $end);
        $context = $this->lopCalculator->buildPayrollContext($user, $companyId, $start, $end, $holidayDates);

        if (! $context['eligible']) {
            return null;
        }

        $eligibleStart = $context['eligible_start'];
        $eligibleEnd = $context['eligible_end'];
        $workingDays = $context['payable_working_days'];
        $prorationFactor = $context['proration_factor'];

        $presentDays = $this->lopCalculator->presentDaysForUser($user->id, $companyId, $eligibleStart, $eligibleEnd);
        $leaveData = $this->lopCalculator->leaveDaysForUser($user->id, $companyId, $eligibleStart, $eligibleEnd);
        $paidLeaveDays = $leaveData['paid'];
        $paidDays = $presentDays + $paidLeaveDays;
        $lopDays = max(0, $workingDays - $paidDays);

        $ctcAnnual = $this->resolveCtcAnnual($user, $companyId, $end);
        $grossMonthlyBeforeLop = $ctcAnnual > 0 ? round(($ctcAnnual / 12) * $prorationFactor, 2) : 0.0;

        $earnings = $this->buildEarnings($user, $companyId, $grossMonthlyBeforeLop, $componentDefinitions, $end);
        $groupDeductions = $this->buildGroupDeductions($user, $companyId, $end);
        $earnings = $this->applyProrationToEarnings($earnings, $prorationFactor);
        $groupDeductions = $this->applyProrationToDeductions($groupDeductions, $prorationFactor);

        $totalEarnings = array_sum(array_column($earnings, 'amount'));
        $grossMonthly = $totalEarnings > 0 ? round($totalEarnings, 2) : $grossMonthlyBeforeLop;

        $lopAmount = $this->lopCalculator->lopAmount($grossMonthly, $workingDays, $lopDays);
        $grossAfterLop = max(0, round($grossMonthly - $lopAmount, 2));

        $basic = $this->extractBasicFromEarnings($earnings);
        $deductions = [];
        $employerContributions = [];

        if ($lopAmount > 0) {
            $deductions[] = ['code' => 'LOP', 'name' => 'Loss of Pay', 'amount' => $lopAmount, 'meta' => ['lop_days' => $lopDays]];
        }
        foreach ($groupDeductions as $componentDeduction) {
            $deductions[] = $componentDeduction;
        }

        $revision = $this->salaryResolver->getEffectiveRevision($user->id, $companyId, $end);
        if ($revision) {
            $statutory = $this->statutoryFromRevision($revision, $settings);
            foreach ($statutory['deductions'] as $row) {
                $deductions[] = $row;
            }
            foreach ($statutory['employer_contributions'] as $row) {
                $employerContributions[] = $row;
            }
        } else {
            if (! empty($settings['pf_enabled']) && $basic > 0) {
                $pf = $this->pfCalculator->calculate(
                    $basic,
                    $settings['pf_rate'] ?? 12,
                    $settings['pf_rate'] ?? 12,
                    $settings['pf_ceiling'] ?? 15000
                );
                $deductions[] = ['code' => 'PF_EMP', 'name' => 'PF (Employee)', 'amount' => $pf['employee'], 'meta' => ['base' => $pf['base']]];
                $employerContributions[] = ['code' => 'PF_EMPR', 'name' => 'PF (Employer)', 'amount' => $pf['employer'], 'meta' => ['base' => $pf['base']]];
            }

            if (! empty($settings['esi_enabled'])) {
                $esi = $this->esiCalculator->calculate(
                    $grossAfterLop,
                    $settings['esi_employee_rate'] ?? 0.75,
                    $settings['esi_employer_rate'] ?? 3.25,
                    $settings['esi_cap'] ?? 21000
                );
                $deductions[] = ['code' => 'ESI_EMP', 'name' => 'ESI (Employee)', 'amount' => $esi['employee'], 'meta' => ['gross_considered' => $esi['gross_considered']]];
                $employerContributions[] = ['code' => 'ESI_EMPR', 'name' => 'ESI (Employer)', 'amount' => $esi['employer'], 'meta' => ['gross_considered' => $esi['gross_considered']]];
            }

            if (! empty($settings['pt_enabled'])) {
                $state = $user->work_state ?? '';
                $ptAmount = ! empty($ptSlabs)
                    ? $this->ptCalculator->calculate($grossAfterLop, $ptSlabs)
                    : round((float) ($settings['pt_amount'] ?? 0), 2);
                if ($ptAmount > 0) {
                    $deductions[] = ['code' => 'PT', 'name' => 'Professional Tax', 'amount' => $ptAmount, 'meta' => ['state' => $state]];
                }
            }
        }

        $year = (int) $start->year;
        $month = (int) $start->month;
        $prePaymentAmount = $this->periodDeductions->prePaymentAmount((int) $user->id, $companyId, $year, $month);
        if ($prePaymentAmount > 0) {
            $deductions[] = ['code' => 'ADVANCE', 'name' => 'Advance / Pre-payment', 'amount' => $prePaymentAmount, 'meta' => []];
        }
        $reimbursementAmount = $this->periodDeductions->expenseReimbursementAmount((int) $user->id, $companyId, $year, $month);
        $recoveryAmount = $this->periodDeductions->expenseDeductionAmount((int) $user->id, $companyId, $year, $month);
        if ($reimbursementAmount > 0) {
            $earnings[] = [
                'code' => 'EXP_REIMB',
                'name' => 'Expense Reimbursement',
                'amount' => $reimbursementAmount,
                'meta' => ['statutory_excluded' => true],
            ];
        }
        if ($recoveryAmount > 0) {
            $deductions[] = ['code' => 'EXP_RECOVERY', 'name' => 'Expense Recovery', 'amount' => $recoveryAmount, 'meta' => []];
        }

        $totalDeductions = array_sum(array_column($deductions, 'amount'));
        $netMonthly = max(0, round($grossMonthly - $totalDeductions + $reimbursementAmount, 2));

        return [
            'ctc_annual' => $ctcAnnual,
            'gross_monthly' => $grossMonthly,
            'net_monthly' => $netMonthly,
            'total_deductions' => round($totalDeductions, 2),
            'lop_days' => $lopDays,
            'lop_amount' => $lopAmount,
            'working_days' => $workingDays,
            'paid_days' => $paidDays,
            'full_month_working_days' => $context['full_month_working_days'],
            'proration_factor' => $prorationFactor,
            'earnings' => $earnings,
            'deductions' => $deductions,
            'employer_contributions' => $employerContributions,
        ];
    }

    /**
     * @param  array<int, array{code: string, name: string, amount: float, meta?: array}>  $earnings
     * @return array<int, array{code: string, name: string, amount: float, meta?: array}>
     */
    protected function applyProrationToEarnings(array $earnings, float $prorationFactor): array
    {
        if ($prorationFactor >= 1) {
            return $earnings;
        }

        return array_map(function (array $row) use ($prorationFactor) {
            $row['amount'] = round((float) ($row['amount'] ?? 0) * $prorationFactor, 2);
            $row['meta'] = array_merge($row['meta'] ?? [], ['prorated' => true]);

            return $row;
        }, $earnings);
    }

    /**
     * @param  array<int, array{code: string, name: string, amount: float, meta?: array}>  $deductions
     * @return array<int, array{code: string, name: string, amount: float, meta?: array}>
     */
    protected function applyProrationToDeductions(array $deductions, float $prorationFactor): array
    {
        if ($prorationFactor >= 1) {
            return $deductions;
        }

        return array_map(function (array $row) use ($prorationFactor) {
            if ($this->isStatutoryDeduction($row)) {
                return $row;
            }
            $row['amount'] = round((float) ($row['amount'] ?? 0) * $prorationFactor, 2);
            $row['meta'] = array_merge($row['meta'] ?? [], ['prorated' => true]);

            return $row;
        }, $deductions);
    }

    /**
     * Indicative monthly salary breakup for display (full month, no attendance / LOP).
     *
     * @param array{pf_enabled: bool, pf_rate: float, pf_ceiling: float|null, esi_enabled: bool, esi_employee_rate: float, esi_employer_rate: float, esi_cap: float|null, pt_enabled: bool} $settings
     * @param array<int, array{min_gross: float, max_gross: float|null, amount: float}> $ptSlabs
     * @return array{
     *   ctc_annual: float,
     *   gross_monthly: float,
     *   gross_annual: float,
     *   net_monthly: float,
     *   net_annual: float,
     *   earning_rows: array<int, array{code: string, name: string, monthly: float, annual: float}>,
     *   subtotal_monthly: float,
     *   subtotal_annual: float,
     *   employer_rows: array<int, array{code: string, name: string, monthly: float, annual: float, strikethrough_label: bool}>,
     *   total_employer_monthly: float,
     *   total_employer_annual: float,
     *   total_earnings_monthly: float,
     *   total_earnings_annual: float,
     *   deduction_rows: array<int, array{code: string, name: string, monthly: float, annual: float}>,
     *   total_deductions_monthly: float,
     *   total_deductions_annual: float
     * }
     */
    public function indicativeBreakdown(
        User $user,
        int $companyId,
        Carbon $asOf,
        array $settings,
        array $ptSlabs,
        int $salaryRevisionId = null
    ): array {
        $EffectiveRevision = $this->salaryResolver->getEffectiveRevision($user->id, $companyId, $asOf, $salaryRevisionId);

        if(!$EffectiveRevision) {
            return [
                'ctc_annual' => 0,
                'gross_monthly' => 0,
                'gross_annual' => 0,
                'net_monthly' => 0,
                'net_annual' => 0,
                'earning_rows' => [],
                'subtotal_monthly' => 0,
                'subtotal_annual' => 0,
                'employer_rows' => [],
                'total_employer_monthly' => 0,
                'total_employer_annual' => 0,
                'total_earnings_monthly' => 0,
                'total_earnings_annual' => 0,
                'deduction_rows' => [],
                'total_deductions_monthly' => 0,
                'total_deductions_annual' => 0,
                'effective_date' => '-',
            ];
        }

        $ctcAnnual = $EffectiveRevision->ctc_annual;
        $ctcMonthly = round($ctcAnnual / 12, 2);
        
        $pfEmployer = $EffectiveRevision->pf_employer;
        $pfEmployee = $EffectiveRevision->pf_employee;
        $esiEmployer = $EffectiveRevision->esi_employer;
        $esiEmployee = $EffectiveRevision->esi_employee;
        $pt = $EffectiveRevision->pt;

        $grossMonthly = $ctcMonthly - $pfEmployer - $esiEmployer;

        $components_json = $EffectiveRevision->components_json;

        $earningRows = collect($components_json)
                        ->whereIn('type', ['earnings','earning'])
                        ->map(fn ($c) => [
                            'code' => strtoupper(preg_replace('/\s+/', '_', (string) ($c['name'] ?? ''))),
                            'name' => (string) ($c['name'] ?? ''),
                            'monthly' => $c['monthly'],
                            'annual' => $c['annual'],
                        ])
                        ->values()
                        ->all();
        $specialAllowance = round($grossMonthly - array_sum(array_column($earningRows, 'monthly')), 2);

        if($specialAllowance > 0) {
            $earningRows[] = ['code' => 'SPECIAL_ALLOWANCE', 'name' => 'Special Allowance', 'monthly' => $specialAllowance, 'annual' => round($specialAllowance * 12, 2)];
        }

        $deductionRows = collect($components_json)
                        ->whereIn('type', ['deductions','deduction'])
                        ->map(fn ($c) => [
                            'code' => strtoupper(preg_replace('/\s+/', '_', (string) ($c['name'] ?? ''))),
                            'name' => (string) ($c['name'] ?? ''),
                            'monthly' => $c['monthly'],
                            'annual' => $c['annual'],
                        ])
                        ->values()
                        ->all();
        
        if($pfEmployee > 0) {
            $deductionRows[] = ['code' => 'PF_EMP', 'name' => 'PF (Employee)', 'monthly' => $pfEmployee, 'annual' => round($pfEmployee * 12, 2)];
        }
        if($esiEmployee > 0) {
            $deductionRows[] = ['code' => 'ESI_EMP', 'name' => 'ESI (Employee)', 'monthly' => $esiEmployee, 'annual' => round($esiEmployee * 12, 2)];
        }
        if ($pt > 0) {
            $deductionRows[] = ['code' => 'PT', 'name' => 'Professional Tax', 'monthly' => $pt, 'annual' => round($pt * 12, 2)];
        }
        $netMonthly = round($grossMonthly - array_sum(array_column($deductionRows, 'monthly')), 2);

        $subtotalMonthly = round(array_sum(array_column($earningRows, 'monthly')), 2);
        $subtotalAnnual = round(array_sum(array_column($earningRows, 'annual')), 2);

        $employerRows = [];
        if ($pfEmployer > 0) {
            $employerRows[] = ['code' => 'PF_EMPR', 'name' => 'PF (Employer)', 'monthly' => $pfEmployer, 'annual' => round($pfEmployer * 12, 2), 'strikethrough_label' => true];
        }
        if ($esiEmployer > 0) {
            $employerRows[] = ['code' => 'ESI_EMPR', 'name' => 'ESI (Employer)', 'monthly' => $esiEmployer, 'annual' => round($esiEmployer * 12, 2), 'strikethrough_label' => false];
        }
        // $specialAllowance = $subtotalMonthly;
        $totalEmployerMonthly = round(array_sum(array_column($employerRows, 'monthly')), 2);
        $totalEmployerAnnual = round(array_sum(array_column($employerRows, 'annual')), 2);
        $totalEarningsMonthly = round($subtotalMonthly + $pfEmployer + $esiEmployer, 2);
        $totalEarningsAnnual = round($subtotalAnnual + $pfEmployer * 12 + $esiEmployer * 12, 2);

        $totalDeductionsMonthly = round(array_sum(array_column($deductionRows, 'monthly')), 2);
        $totalDeductionsAnnual = round($totalDeductionsMonthly * 12, 2);
        
        return [
            'ctc_annual' => $ctcAnnual,
            'gross_monthly' => $grossMonthly,
            'gross_annual' => round($grossMonthly * 12, 2),
            'net_monthly' => $netMonthly,
            'net_annual' => round($netMonthly * 12, 2),
            'earning_rows' => $earningRows,
            'subtotal_monthly' => $subtotalMonthly,
            'subtotal_annual' => $subtotalAnnual,
            'employer_rows' => $employerRows,
            'total_employer_monthly' => $totalEmployerMonthly,
            'total_employer_annual' => $totalEmployerAnnual,
            'total_earnings_monthly' => $totalEarningsMonthly,
            'total_earnings_annual' => $totalEarningsAnnual,
            'deduction_rows' => $deductionRows,
            'total_deductions_monthly' => $totalDeductionsMonthly,
            'total_deductions_annual' => $totalDeductionsAnnual,
            'effective_date' => $EffectiveRevision->effective_date?->format('M j, Y')
        ];
    }

    /**
     * Statutory deductions and employer contributions from salary revision (matches indicativeBreakdown).
     *
     * @param  array{pf_enabled: bool, esi_enabled: bool, pt_enabled: bool}  $settings
     * @return array{deductions: array<int, array{code: string, name: string, amount: float, meta: array}>, employer_contributions: array<int, array{code: string, name: string, amount: float, meta: array}>}
     */
    protected function statutoryFromRevision(\App\Models\SalaryRevision $revision, array $settings): array
    {
        $deductions = [];
        $employerContributions = [];

        $components = is_array($revision->components_json) ? $revision->components_json : [];
        foreach ($components as $component) {
            if (! is_array($component) || ! in_array($component['type'] ?? '', ['deduction', 'deductions'], true)) {
                continue;
            }
            $amount = round((float) ($component['monthly'] ?? 0), 2);
            if ($amount <= 0) {
                continue;
            }
            $name = (string) ($component['name'] ?? 'Deduction');
            $code = strtoupper(preg_replace('/\s+/', '_', $name));
            $deductions[] = [
                'code' => $code ?: 'DEDUCTION',
                'name' => $name,
                'amount' => $amount,
                'meta' => ['source' => 'salary_revision'],
            ];
        }

        if (! empty($settings['pf_enabled'])) {
            $pfEmployee = round((float) ($revision->pf_employee ?? 0), 2);
            $pfEmployer = round((float) ($revision->pf_employer ?? 0), 2);
            if ($pfEmployee > 0) {
                $deductions[] = [
                    'code' => 'PF_EMP',
                    'name' => 'PF (Employee)',
                    'amount' => $pfEmployee,
                    'meta' => ['source' => 'salary_revision'],
                ];
            }
            if ($pfEmployer > 0) {
                $employerContributions[] = [
                    'code' => 'PF_EMPR',
                    'name' => 'PF (Employer)',
                    'amount' => $pfEmployer,
                    'meta' => ['source' => 'salary_revision'],
                ];
            }
        }

        if (! empty($settings['esi_enabled'])) {
            $esiEmployee = round((float) ($revision->esi_employee ?? 0), 2);
            $esiEmployer = round((float) ($revision->esi_employer ?? 0), 2);
            if ($esiEmployee > 0) {
                $deductions[] = [
                    'code' => 'ESI_EMP',
                    'name' => 'ESI (Employee)',
                    'amount' => $esiEmployee,
                    'meta' => ['source' => 'salary_revision'],
                ];
            }
            if ($esiEmployer > 0) {
                $employerContributions[] = [
                    'code' => 'ESI_EMPR',
                    'name' => 'ESI (Employer)',
                    'amount' => $esiEmployer,
                    'meta' => ['source' => 'salary_revision'],
                ];
            }
        }

        if (! empty($settings['pt_enabled'])) {
            $pt = round((float) ($revision->pt ?? 0), 2);
            if ($pt > 0) {
                $deductions[] = [
                    'code' => 'PT',
                    'name' => 'Professional Tax',
                    'amount' => $pt,
                    'meta' => ['source' => 'salary_revision'],
                ];
            }
        }

        return [
            'deductions' => $deductions,
            'employer_contributions' => $employerContributions,
        ];
    }

    /**
     * @param array{code?: string, name?: string} $deduction
     */
    protected function isStatutoryDeduction(array $deduction): bool
    {
        $code = strtoupper((string) ($deduction['code'] ?? ''));
        if (in_array($code, ['PF_EMP', 'ESI_EMP', 'PT', 'PROFESSIONAL_TAX'], true)) {
            return true;
        }

        $name = strtolower(trim((string) ($deduction['name'] ?? '')));
        if ($name === '') {
            return false;
        }

        return str_contains($name, 'pf') ||
            str_contains($name, 'provident fund') ||
            str_contains($name, 'esi') ||
            str_contains($name, 'professional tax');
    }

    protected function resolveCtcAnnual(User $user, int $companyId, Carbon $periodEnd): float
    {
        $revision = $this->salaryResolver->getEffectiveRevision($user->id, $companyId, $periodEnd);
        $revisionComponents = $this->salaryResolver->normalizeRevisionComponents($revision?->components_json);
        if (! empty($revisionComponents)) {
            if ((float) $revision?->ctc_annual > 0) {
                return round((float) $revision->ctc_annual, 2);
            }
            $annual = array_sum(array_column($revisionComponents, 'annual'));
            return round($annual, 2);
        }
        $components = $this->salaryResolver->resolveMonthlyComponents($user, $companyId, $periodEnd);
        if (empty($components)) {
            return 0.0;
        }
        $annual = array_sum(array_map(
            fn (array $row) => ($row['type'] ?? '') === 'earning' ? ((float) $row['amount'] * 12) : 0,
            $components
        ));
        return round($annual, 2);
    }

    /**
     * Build earnings list: from component definitions (CTC-based) or fallback to salary group.
     *
     * @return array<int, array{code: string, name: string, amount: float, meta?: array}>
     */
    protected function buildEarnings(User $user, int $companyId, float $defaultMonthlyGross, ?Collection $definitions, Carbon $periodEnd): array
    {
        if ($definitions !== null && $definitions->isNotEmpty()) {
            return $this->buildEarningsFromDefinitions($user, $companyId, $defaultMonthlyGross, $definitions, $periodEnd);
        }
        return $this->buildEarningsFromSalaryGroup($user, $companyId, $defaultMonthlyGross, $periodEnd);
    }

    protected function buildEarningsFromSalaryGroup(User $user, int $companyId, float $defaultMonthlyGross, Carbon $periodEnd): array
    {
        $revision = $this->salaryResolver->getEffectiveRevision($user->id, $companyId, $periodEnd);
        $revisionComponents = $this->salaryResolver->normalizeRevisionComponents($revision?->components_json);
        if (! empty($revisionComponents)) {
            $result = [];
            foreach ($revisionComponents as $component) {
                if (! in_array($component['type'] ?? 'earning', ['earning', 'earnings'], true)) {
                    continue;
                }
                $name = (string) $component['name'];
                $amount = round((float) ($component['monthly'] ?? ((float) $component['annual'] / 12)), 2);
                $code = strtoupper(preg_replace('/\s+/', '_', $name));
                $result[] = ['code' => $code ?: 'EARNING', 'name' => $name, 'amount' => $amount, 'meta' => []];
            }

            if ($revision && $result !== []) {
                $ctcMonthly = round((float) $revision->ctc_annual / 12, 2);
                $pfEmployer = (float) ($revision->pf_employer ?? 0);
                $esiEmployer = (float) ($revision->esi_employer ?? 0);
                $grossMonthly = $ctcMonthly - $pfEmployer - $esiEmployer;
                $specialAllowance = round($grossMonthly - array_sum(array_column($result, 'amount')), 2);

                if ($specialAllowance > 0 && ! $this->earningsIncludeSpecialAllowance($result)) {
                    $result[] = [
                        'code' => 'SPECIAL_ALLOWANCE',
                        'name' => 'Special Allowance',
                        'amount' => $specialAllowance,
                        'meta' => [],
                    ];
                }
            }

            return $result;
        }
        $components = $this->salaryResolver->resolveMonthlyComponents($user, $companyId, $periodEnd);
        $result = [];
        foreach ($components as $row) {
            if (($row['type'] ?? '') !== 'earning') {
                continue;
            }
            $code = strtoupper(preg_replace('/\s+/', '_', (string) $row['name']));
            $result[] = ['code' => $code ?: 'EARNING', 'name' => (string) $row['name'], 'amount' => (float) $row['amount'], 'meta' => []];
        }
        return $result ?: [['code' => 'BASIC', 'name' => 'Basic', 'amount' => max(0, $defaultMonthlyGross), 'meta' => []]];
    }

    protected function buildEarningsFromDefinitions(User $user, int $companyId, float $monthlyGross, Collection $definitions, Carbon $periodEnd): array
    {
        $earningDefs = $definitions->where('category', 'earning')->sortBy('display_order');
        if ($earningDefs->isEmpty()) {
            return $this->buildEarningsFromSalaryGroup($user, $companyId, $monthlyGross, $periodEnd);
        }
        $basicPct = 0.40;
        $hraPct = 0.40;
        $basic = round($monthlyGross * $basicPct, 2);
        $hra = round($basic * $hraPct, 2);
        $special = round($monthlyGross - $basic - $hra, 2);
        $result = [];
        foreach ($earningDefs as $def) {
            $code = $def->code;
            $amount = match ($code) {
                'BASIC' => $basic,
                'HRA' => $hra,
                'SPECIAL' => $special,
                default => (float) ($def->value ?? 0),
            };
            $result[] = ['code' => $code, 'name' => $def->name, 'amount' => $amount, 'meta' => []];
        }
        return $result;
    }

    /**
     * @param  array<int, array{code: string, name: string, amount: float, meta?: array}>  $earnings
     */
    protected function earningsIncludeSpecialAllowance(array $earnings): bool
    {
        foreach ($earnings as $earning) {
            $code = strtoupper((string) ($earning['code'] ?? ''));
            $name = strtolower(trim((string) ($earning['name'] ?? '')));
            if (in_array($code, ['SPECIAL', 'SPECIAL_ALLOWANCE'], true) || str_contains($name, 'special')) {
                return true;
            }
        }

        return false;
    }

    protected function extractBasicFromEarnings(array $earnings): float
    {
        foreach ($earnings as $e) {
            $code = strtoupper((string) ($e['code'] ?? ''));
            if (in_array($code, ['BASIC', 'BASIC_SALARY', 'BASIC_PAY'], true)) {
                return (float) ($e['amount'] ?? 0);
            }
        }

        foreach ($earnings as $e) {
            $name = strtolower(trim((string) ($e['name'] ?? '')));
            if ($name !== '' && str_contains($name, 'basic')) {
                return (float) ($e['amount'] ?? 0);
            }
        }

        return 0.0;
    }

    /**
     * @return array<int, array{code:string,name:string,amount:float,meta:array}>
     */
    protected function buildGroupDeductions(User $user, int $companyId, Carbon $periodEnd): array
    {
        $components = $this->salaryResolver->resolveMonthlyComponents($user, $companyId, $periodEnd);
        $result = [];
        foreach ($components as $row) {
            if (($row['type'] ?? '') !== 'deduction') {
                continue;
            }
            $amount = round(max(0, (float) ($row['amount'] ?? 0)), 2);
            if ($amount <= 0) {
                continue;
            }
            $name = (string) ($row['name'] ?? 'Deduction');
            $code = strtoupper(preg_replace('/\s+/', '_', $name));
            $result[] = ['code' => $code ?: 'DEDUCTION', 'name' => $name, 'amount' => $amount, 'meta' => ['source' => 'salary_group']];
        }
        return $result;
    }
}
