<?php

namespace App\Http\Controllers\Reports;

use App\Http\Controllers\Controller;
use App\Models\CashModal;
use App\Models\CashMutation;
use App\Models\Deposit;
use App\Models\Expense;
use App\Models\Order;
use App\Models\Payment;
use App\Models\PaymentAccount;
use App\Models\PurchasePayment;
use App\Models\Salary;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Schema;

class GeneralJournalController extends Controller
{
    private const AUTO_DEPOSIT_TAG = '[AUTO-DEPOSIT]';

    public function __construct()
    {
        $this->middleware(\App\Http\Middleware\PermissionMiddleware::class . ':view reports')
            ->only(['index', 'print', 'export']);
    }

    public function index(Request $request)
    {
        $data = $this->buildJournalData($request);

        return view('pages.reports.general-journal', $data);
    }

    public function print(Request $request)
    {
        $data = $this->buildJournalData($request);
        $data['title'] = 'Cetak - Jurnal Umum';
        $data['isPrint'] = true;

        return view('pages.reports.general-journal-print', $data);
    }

    public function export(Request $request)
    {
        $data = $this->buildJournalData($request);
        $filename = 'jurnal-umum-' . ($data['startDate'] ?? now()->startOfMonth()->toDateString()) . '-sd-' . ($data['endDate'] ?? now()->toDateString()) . '.xls';

        return response()
            ->view('pages.reports.general-journal-export', $data)
            ->header('Content-Type', 'application/vnd.ms-excel; charset=UTF-8')
            ->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
    }

    private function buildJournalData(Request $request): array
    {
        $start = $request->filled('start_date')
            ? Carbon::parse($request->start_date)->startOfDay()
            : now()->startOfMonth()->startOfDay();

        $end = $request->filled('end_date')
            ? Carbon::parse($request->end_date)->endOfDay()
            : now()->endOfDay();

        $rawAccountId = $request->input('payment_account_id');
        $target = $this->resolveTarget($rawAccountId);

        $paymentAccounts = PaymentAccount::where('is_active', 1)->orderBy('name')->get();

        $systemAccounts = collect([
            (object) ['id' => 'cash', 'name' => 'Kas Tunai'],
            (object) ['id' => 'petty', 'name' => 'Kas Kecil'],
            (object) ['id' => 'bank', 'name' => 'Kas Bank'],
        ]);

        $accounts = $systemAccounts->merge($paymentAccounts);
        $accountMap = $paymentAccounts->pluck('name', 'id');

        $paymentDateField = $this->pickDateField('payments', ['payment_date', 'paid_at', 'created_at']);
        $purchasePayDateField = Schema::hasTable('purchase_payments')
            ? $this->pickDateField('purchase_payments', ['payment_date', 'created_at'])
            : null;
        $expenseDateField = Schema::hasTable('expenses')
            ? $this->pickDateField('expenses', ['expense_date', 'created_at'])
            : null;
        $depositDateField = Schema::hasTable('deposits')
            ? $this->pickDateField('deposits', ['deposit_date', 'created_at'])
            : null;
        $modalDateField = Schema::hasTable('cash_modals')
            ? $this->pickDateField('cash_modals', ['modal_date', 'created_at'])
            : null;
        $mutationDateField = Schema::hasTable('cash_mutations')
            ? $this->pickDateField('cash_mutations', ['mutation_date', 'created_at'])
            : null;
        $salaryDateField = Schema::hasTable('salaries')
            ? $this->pickDateField('salaries', ['paid_at', 'updated_at', 'created_at'])
            : null;

        $paymentsHasAccount = Schema::hasColumn('payments', 'payment_account_id');
        $paymentsHasMethod = Schema::hasColumn('payments', 'payment_method');
        $purchasePaysHasAccount = Schema::hasTable('purchase_payments') && Schema::hasColumn('purchase_payments', 'payment_account_id');
        $purchasePaysHasMethod = Schema::hasTable('purchase_payments') && Schema::hasColumn('purchase_payments', 'payment_method');

        $entries = $this->collectJournalEntries(
            $target,
            $start,
            $end,
            $accountMap,
            $paymentDateField,
            $purchasePayDateField,
            $expenseDateField,
            $depositDateField,
            $modalDateField,
            $mutationDateField,
            $salaryDateField,
            $paymentsHasAccount,
            $paymentsHasMethod,
            $purchasePaysHasAccount,
            $purchasePaysHasMethod
        )
            ->sortBy(function ($row) {
                $tanggal = $row['tanggal'] instanceof Carbon
                    ? $row['tanggal']
                    : Carbon::parse($row['tanggal']);

                return $tanggal->format('YmdHis') . '|' . ($row['group_key'] ?? '') . '|' . ($row['line_order'] ?? 0);
            })
            ->values();

        return [
            'title' => 'Jurnal Umum',
            'entries' => $entries,
            'accounts' => $accounts,
            'startDate' => $start->format('Y-m-d'),
            'endDate' => $end->format('Y-m-d'),
            'selectedAccount' => $rawAccountId,
            'summary' => [
                'total_rows' => $entries->count(),
                'total_journals' => $entries->pluck('group_key')->unique()->count(),
                'total_debit' => (float) $entries->sum('debit'),
                'total_kredit' => (float) $entries->sum('kredit'),
            ],
        ];
    }

    private function collectJournalEntries(
        array $target,
        Carbon $start,
        Carbon $end,
        Collection $accountMap,
        string $paymentDateField,
        ?string $purchasePayDateField,
        ?string $expenseDateField,
        ?string $depositDateField,
        ?string $modalDateField,
        ?string $mutationDateField,
        ?string $salaryDateField,
        bool $paymentsHasAccount,
        bool $paymentsHasMethod,
        bool $purchasePaysHasAccount,
        bool $purchasePaysHasMethod
    ): Collection {
        return collect()
            ->merge($this->buildSalesEntries($target, $start, $end, $accountMap, $paymentDateField, $paymentsHasAccount, $paymentsHasMethod))
            ->merge($this->buildPurchasePaymentEntries($target, $start, $end, $accountMap, $purchasePayDateField, $purchasePaysHasAccount, $purchasePaysHasMethod))
            ->merge($this->buildSalaryEntries($target, $start, $end, $accountMap, $salaryDateField))
            ->merge($this->buildExpenseEntries($target, $start, $end, $expenseDateField))
            ->merge($this->buildCashModalEntries($target, $start, $end, $modalDateField))
            ->merge($this->buildDepositEntries($target, $start, $end, $depositDateField))
            ->merge($this->buildMutationEntries($target, $start, $end, $mutationDateField));
    }

    private function buildSalesEntries(
        array $target,
        Carbon $start,
        Carbon $end,
        Collection $accountMap,
        string $paymentDateField,
        bool $paymentsHasAccount,
        bool $paymentsHasMethod
    ): Collection {
        if ($target['mode'] === 'petty') {
            return collect();
        }

        $query = Payment::query()->orderBy($paymentDateField);
        $this->applyDateTimeRange($query, $paymentDateField, $start, $end);
        $this->applyActiveOrderFilterToPayments($query);

        if (!$this->applyLedgerAccountFilter($query, $target, $paymentsHasAccount, $paymentsHasMethod)) {
            return collect();
        }

        $payments = $query->get();

        $invoiceMap = collect();
        $customerMap = collect();

        if (Schema::hasColumn('payments', 'order_id')) {
            $orderIds = $payments->pluck('order_id')->filter()->unique()->values();

            if ($orderIds->isNotEmpty()) {
                $orders = Order::whereIn('id', $orderIds)
                    ->where('is_cancelled', false)
                    ->get(['id', 'invoice_number', 'customer_name']);

                $invoiceMap = $orders->pluck('invoice_number', 'id');
                $customerMap = $orders->pluck('customer_name', 'id');
            }
        }

        return $payments->flatMap(function ($item) use ($paymentDateField, $accountMap, $invoiceMap, $customerMap, $paymentsHasAccount, $paymentsHasMethod, $target) {
            $orderId = Schema::hasColumn('payments', 'order_id') ? ($item->order_id ?? null) : null;
            $invoice = $orderId ? ($invoiceMap[$orderId] ?? null) : null;
            $customer = $orderId ? ($customerMap[$orderId] ?? '-') : '-';
            $ref = $invoice ?: 'SALE-' . $item->id;

            return $this->makeJournalPair(
                $item->{$paymentDateField},
                'sales-' . $item->id,
                '01_sales',
                $ref,
                'Penerimaan Penjualan' . ($invoice ? ' ' . $invoice : '') . ($customer && $customer !== '-' ? ' - ' . $customer : ''),
                $this->buildRekeningLabel($item, $accountMap, $paymentsHasAccount, $paymentsHasMethod, $target),
                'Penjualan',
                (float) $item->amount,
                'Payment'
            );
        });
    }

    private function buildPurchasePaymentEntries(
        array $target,
        Carbon $start,
        Carbon $end,
        Collection $accountMap,
        ?string $purchasePayDateField,
        bool $purchasePaysHasAccount,
        bool $purchasePaysHasMethod
    ): Collection {
        if (!Schema::hasTable('purchase_payments') || !$purchasePayDateField) {
            return collect();
        }

        if ($target['mode'] === 'petty') {
            return collect();
        }

        $query = PurchasePayment::query()->orderBy($purchasePayDateField);
        $this->applyDateTimeRange($query, $purchasePayDateField, $start, $end);

        $rows = $query->get()->filter(function ($item) use ($target, $accountMap, $purchasePaysHasAccount, $purchasePaysHasMethod) {
            return $this->matchesPurchasePaymentTarget($item, $target, $accountMap, $purchasePaysHasAccount, $purchasePaysHasMethod);
        })->values();

        $supplierMap = $this->buildPurchaseSupplierMap($rows);

        return $rows->flatMap(function ($item) use ($purchasePayDateField, $accountMap, $supplierMap, $purchasePaysHasAccount, $purchasePaysHasMethod, $target) {
            $purchaseId = Schema::hasColumn('purchase_payments', 'purchase_id') ? ($item->purchase_id ?? null) : null;
            $supplier = $purchaseId ? ($supplierMap[$purchaseId] ?? '') : '';
            $ref = 'PP-' . $item->id;

            return $this->makeJournalPair(
                $item->{$purchasePayDateField},
                'purchase-payment-' . $item->id,
                '02_purchase_payment',
                $ref,
                'Pembayaran Supplier' . ($supplier ? ' - ' . $supplier : ''),
                'Hutang Usaha',
                $this->buildPurchasePaymentRekeningLabel($item, $accountMap, $purchasePaysHasAccount, $purchasePaysHasMethod, $target),
                (float) $item->amount,
                'Purchase Payment'
            );
        });
    }

    private function buildSalaryEntries(
        array $target,
        Carbon $start,
        Carbon $end,
        Collection $accountMap,
        ?string $salaryDateField
    ): Collection {
        if (!Schema::hasTable('salaries') || !$salaryDateField) {
            return collect();
        }

        if ($target['mode'] === 'petty') {
            return collect();
        }

        $query = Salary::query()->orderBy($salaryDateField);

        if (Schema::hasColumn('salaries', 'status')) {
            $query->where('status', 'paid');
        } elseif (Schema::hasColumn('salaries', 'is_paid')) {
            $query->where('is_paid', 1);
        } else {
            return collect();
        }

        $this->applyDateTimeRange($query, $salaryDateField, $start, $end);

        $rows = $query->get()->filter(function ($item) use ($target) {
            $source = $this->resolveSalarySource($item);

            if ($target['mode'] === 'all') {
                return in_array($source, ['cash', 'bank'], true);
            }

            if ($target['mode'] === 'cash') {
                return $source === 'cash';
            }

            if ($target['mode'] === 'bank') {
                return $source === 'bank';
            }

            if ($target['mode'] === 'account') {
                return $source === 'bank' && (int) ($item->payment_account_id ?? 0) === (int) $target['account_id'];
            }

            return false;
        })->values();

        $employeeMap = collect();

        if (class_exists(\App\Models\Employee::class) && Schema::hasColumn('salaries', 'employee_id')) {
            $employeeIds = $rows->pluck('employee_id')->filter()->unique()->values();

            if ($employeeIds->isNotEmpty()) {
                $employeeMap = \App\Models\Employee::whereIn('id', $employeeIds)->pluck('name', 'id');
            }
        }

        return $rows->flatMap(function ($item) use ($salaryDateField, $accountMap, $employeeMap) {
            $employeeName = $employeeMap[$item->employee_id] ?? 'Karyawan';
            $ref = 'SAL-' . $item->id;

            return $this->makeJournalPair(
                $item->{$salaryDateField},
                'salary-' . $item->id,
                '02a_salary',
                $ref,
                'Pembayaran Gaji - ' . $employeeName,
                'Beban Gaji',
                $this->buildSalaryRekeningLabel($item, $accountMap),
                $this->getSalaryAmount($item),
                'Salary'
            );
        });
    }

    private function buildExpenseEntries(
        array $target,
        Carbon $start,
        Carbon $end,
        ?string $expenseDateField
    ): Collection {
        if (!Schema::hasTable('expenses') || !$expenseDateField) {
            return collect();
        }

        if (!in_array($target['mode'], ['all', 'petty'], true)) {
            return collect();
        }

        $query = Expense::query()
            ->where(function ($q) {
                $q->whereNull('description')
                    ->orWhere('description', 'not like', '%purchase_payment:%');
            })
            ->orderBy($expenseDateField);

        $this->applyDateTimeRange($query, $expenseDateField, $start, $end);

        $rows = $query->get();

        $descField = Schema::hasColumn('expenses', 'description')
            ? 'description'
            : (Schema::hasColumn('expenses', 'notes')
                ? 'notes'
                : (Schema::hasColumn('expenses', 'name') ? 'name' : null));

        return $rows->flatMap(function ($item) use ($expenseDateField, $descField) {
            $desc = $descField ? trim((string) ($item->{$descField} ?? '')) : '';
            $ref = 'EXP-' . $item->id;

            return $this->makeJournalPair(
                $item->{$expenseDateField},
                'expense-' . $item->id,
                '03_expense',
                $ref,
                'Biaya Operasional' . ($desc ? ' - ' . $desc : ''),
                'Beban Operasional',
                'Kas Kecil',
                (float) $item->amount,
                'Expense'
            );
        });
    }

    private function buildCashModalEntries(
        array $target,
        Carbon $start,
        Carbon $end,
        ?string $modalDateField
    ): Collection {
        if (!Schema::hasTable('cash_modals') || !$modalDateField) {
            return collect();
        }

        if ($target['mode'] === 'account') {
            return collect();
        }

        $query = CashModal::query()->orderBy($modalDateField);
        $this->applyDateTimeRange($query, $modalDateField, $start, $end);

        if (in_array($target['mode'], ['cash', 'petty', 'bank'], true)) {
            $query->where('account', $target['mode']);
        }

        $rows = $query->get();

        return $rows->flatMap(function ($item) {
            $accountLabel = $this->accountCodeToLabel($item->account ?? '');
            $typeLabel = method_exists($item, 'getTypeLabel')
                ? $item->getTypeLabel()
                : ucfirst((string) ($item->type ?? 'Modal'));
            $ref = 'MOD-' . $item->id;

            return $this->makeJournalPair(
                $item->modal_date ?? $item->created_at,
                'modal-' . $item->id,
                '04_modal',
                $ref,
                $typeLabel . ' - ' . $accountLabel,
                $accountLabel,
                'Modal Pemilik',
                (float) $item->amount,
                'Cash Modal'
            );
        });
    }

    private function buildDepositEntries(
        array $target,
        Carbon $start,
        Carbon $end,
        ?string $depositDateField
    ): Collection {
        if (!Schema::hasTable('deposits') || !$depositDateField) {
            return collect();
        }

        if (!in_array($target['mode'], ['all', 'cash', 'bank'], true)) {
            return collect();
        }

        $query = Deposit::query()->orderBy($depositDateField);
        $this->applyDateTimeRange($query, $depositDateField, $start, $end);

        return $query->get()->flatMap(function ($item) use ($depositDateField) {
            $ref = 'DEP-' . $item->id;

            return $this->makeJournalPair(
                $item->{$depositDateField},
                'deposit-' . $item->id,
                '05_deposit',
                $ref,
                'Setoran Kas ke Bank',
                'Kas Bank',
                'Kas Tunai',
                (float) $item->amount,
                'Deposit'
            );
        });
    }

    private function buildMutationEntries(
        array $target,
        Carbon $start,
        Carbon $end,
        ?string $mutationDateField
    ): Collection {
        if (!Schema::hasTable('cash_mutations') || !$mutationDateField) {
            return collect();
        }

        if ($target['mode'] === 'account') {
            return collect();
        }

        $query = CashMutation::query()
            ->where(function ($q) {
                $q->whereNull('notes')
                    ->orWhere('notes', 'not like', '%' . self::AUTO_DEPOSIT_TAG . '%');
            })
            ->orderBy($mutationDateField);

        $this->applyDateTimeRange($query, $mutationDateField, $start, $end);

        if (in_array($target['mode'], ['cash', 'petty', 'bank'], true)) {
            $query->where(function ($q) use ($target) {
                $q->where('from_account', $target['mode'])
                    ->orWhere('to_account', $target['mode']);
            });
        }

        return $query->get()->flatMap(function ($item) use ($mutationDateField) {
            $fromLabel = $this->accountCodeToLabel($item->from_account ?? '');
            $toLabel = $this->accountCodeToLabel($item->to_account ?? '');
            $ref = 'MUT-' . $item->id;

            return $this->makeJournalPair(
                $item->{$mutationDateField},
                'mutation-' . $item->id,
                '06_mutation',
                $ref,
                'Mutasi Kas ' . $fromLabel . ' ke ' . $toLabel,
                $toLabel,
                $fromLabel,
                (float) $item->amount,
                'Mutation'
            );
        });
    }

    private function makeJournalPair(
        $tanggal,
        string $groupKey,
        string $sortKey,
        string $reference,
        string $description,
        string $debitAccount,
        string $creditAccount,
        float $amount,
        string $source
    ): Collection {
        return collect([
            [
                'tanggal' => $tanggal,
                'group_key' => $groupKey,
                'sort_key' => $sortKey,
                'line_order' => 1,
                'reference' => $reference,
                'description' => $description,
                'source' => $source,
                'account' => $debitAccount,
                'debit' => $amount,
                'kredit' => 0.0,
            ],
            [
                'tanggal' => $tanggal,
                'group_key' => $groupKey,
                'sort_key' => $sortKey,
                'line_order' => 2,
                'reference' => $reference,
                'description' => $description,
                'source' => $source,
                'account' => $creditAccount,
                'debit' => 0.0,
                'kredit' => $amount,
            ],
        ]);
    }

    private function resolveTarget($rawAccountId): array
    {
        if ($rawAccountId === 'cash') {
            return ['mode' => 'cash', 'account_id' => null];
        }

        if ($rawAccountId === 'petty') {
            return ['mode' => 'petty', 'account_id' => null];
        }

        if ($rawAccountId === 'bank') {
            return ['mode' => 'bank', 'account_id' => null];
        }

        if (filled($rawAccountId)) {
            return ['mode' => 'account', 'account_id' => (int) $rawAccountId];
        }

        return ['mode' => 'all', 'account_id' => null];
    }

    private function pickDateField(string $table, array $candidates): string
    {
        foreach ($candidates as $column) {
            if (Schema::hasColumn($table, $column)) {
                return $column;
            }
        }

        return 'created_at';
    }

    private function applyActiveOrderFilterToPayments(Builder $query): void
    {
        if (!Schema::hasColumn('payments', 'order_id')) {
            return;
        }

        $query->where(function ($q) {
            $q->whereNull('order_id')
                ->orWhereHas('order', function ($orderQuery) {
                    $orderQuery->where('is_cancelled', false);
                });
        });
    }

    private function applyLedgerAccountFilter(
        Builder $query,
        array $target,
        bool $hasAccountColumn,
        bool $hasMethodColumn
    ): bool {
        if ($target['mode'] === 'all') {
            return true;
        }

        if ($target['mode'] === 'petty') {
            $query->whereRaw('1 = 0');
            return false;
        }

        if ($target['mode'] === 'cash') {
            if (!$hasAccountColumn && !$hasMethodColumn) {
                return true;
            }

            $query->where(function ($q) use ($hasAccountColumn, $hasMethodColumn) {
                $added = false;

                if ($hasMethodColumn) {
                    $q->where('payment_method', 'cash');
                    $added = true;
                }

                if ($hasAccountColumn) {
                    if ($added) {
                        $q->orWhereNull('payment_account_id');
                    } else {
                        $q->whereNull('payment_account_id');
                    }
                }
            });

            return true;
        }

        if ($target['mode'] === 'bank') {
            if (!$hasAccountColumn && !$hasMethodColumn) {
                return true;
            }

            $query->where(function ($q) use ($hasAccountColumn, $hasMethodColumn) {
                $added = false;

                if ($hasMethodColumn) {
                    $q->whereIn('payment_method', ['transfer', 'ewallet']);
                    $added = true;
                }

                if ($hasAccountColumn) {
                    if ($added) {
                        $q->orWhereNotNull('payment_account_id');
                    } else {
                        $q->whereNotNull('payment_account_id');
                    }
                }
            });

            return true;
        }

        if ($target['mode'] === 'account') {
            if (!$hasAccountColumn) {
                return true;
            }

            $query->where('payment_account_id', $target['account_id']);
            return true;
        }

        return true;
    }

    private function matchesPurchasePaymentTarget(
        $item,
        array $target,
        Collection $accountMap,
        bool $purchasePaysHasAccount,
        bool $purchasePaysHasMethod
    ): bool {
        if ($target['mode'] === 'all') {
            return true;
        }

        if ($target['mode'] === 'petty') {
            return false;
        }

        $info = $this->extractPaymentInfoFromNotes((string) ($item->notes ?? ''));

        $method = null;
        if ($purchasePaysHasMethod && !empty($item->payment_method)) {
            $method = strtolower((string) $item->payment_method);
        } elseif (!empty($info['method'])) {
            $method = $info['method'];
        }

        $accountId = null;
        if ($purchasePaysHasAccount && !empty($item->payment_account_id)) {
            $accountId = (int) $item->payment_account_id;
        }

        $accountName = null;
        if ($accountId && isset($accountMap[$accountId])) {
            $accountName = strtolower(trim((string) $accountMap[$accountId]));
        } elseif (!empty($info['rekening'])) {
            $accountName = strtolower(trim((string) $info['rekening']));
        }

        if ($target['mode'] === 'cash') {
            return $method === 'cash';
        }

        if ($target['mode'] === 'bank') {
            return $method === 'transfer';
        }

        if ($target['mode'] === 'account') {
            $selectedAccountName = strtolower(trim((string) ($accountMap[$target['account_id']] ?? '')));

            if ($accountId) {
                return $accountId === (int) $target['account_id'];
            }

            return $selectedAccountName !== '' && $accountName === $selectedAccountName;
        }

        return false;
    }

    private function buildPurchasePaymentRekeningLabel(
        $item,
        Collection $accountMap,
        bool $purchasePaysHasAccount,
        bool $purchasePaysHasMethod,
        array $target
    ): string {
        if ($purchasePaysHasAccount && !empty($item->payment_account_id)) {
            return $accountMap[$item->payment_account_id] ?? 'Kas Bank';
        }

        $info = $this->extractPaymentInfoFromNotes((string) ($item->notes ?? ''));

        if (!empty($info['rekening'])) {
            return $info['rekening'];
        }

        $method = null;
        if ($purchasePaysHasMethod && !empty($item->payment_method)) {
            $method = strtolower((string) $item->payment_method);
        } elseif (!empty($info['method'])) {
            $method = $info['method'];
        }

        if ($method === 'cash') {
            return 'Kas Tunai';
        }

        if ($method === 'transfer') {
            return 'Kas Bank';
        }

        if ($target['mode'] === 'cash') {
            return 'Kas Tunai';
        }

        if ($target['mode'] === 'bank') {
            return 'Kas Bank';
        }

        return '-';
    }

    private function buildRekeningLabel(
        $item,
        Collection $accountMap,
        bool $hasAccountColumn,
        bool $hasMethodColumn,
        array $target
    ): string {
        $rekeningId = $hasAccountColumn ? ($item->payment_account_id ?? null) : null;

        if ($rekeningId) {
            return $accountMap[$rekeningId] ?? 'Kas Bank';
        }

        if ($hasMethodColumn) {
            $method = $item->payment_method ?? null;

            if ($method === 'cash') {
                return 'Kas Tunai';
            }

            if (in_array($method, ['transfer', 'ewallet'], true)) {
                return 'Kas Bank';
            }
        }

        if ($target['mode'] === 'cash') {
            return 'Kas Tunai';
        }

        if ($target['mode'] === 'bank') {
            return 'Kas Bank';
        }

        return '-';
    }

    private function resolveSalarySource($salary): ?string
    {
        if (Schema::hasColumn('salaries', 'payment_source') && !empty($salary->payment_source)) {
            return strtolower((string) $salary->payment_source);
        }

        if (Schema::hasColumn('salaries', 'payment_method') && !empty($salary->payment_method)) {
            return strtolower((string) $salary->payment_method) === 'cash' ? 'cash' : 'bank';
        }

        if (Schema::hasColumn('salaries', 'payment_account_id') && !empty($salary->payment_account_id)) {
            return 'bank';
        }

        return null;
    }

    private function buildSalaryRekeningLabel($salary, Collection $accountMap): string
    {
        $source = $this->resolveSalarySource($salary);

        if ($source === 'cash') {
            return 'Kas Tunai';
        }

        if ($source === 'bank') {
            if (Schema::hasColumn('salaries', 'payment_account_id') && !empty($salary->payment_account_id)) {
                return $accountMap[$salary->payment_account_id] ?? 'Kas Bank';
            }

            return 'Kas Bank';
        }

        return '-';
    }

    private function getSalaryAmount($salary): float
    {
        if (isset($salary->total)) {
            return (float) $salary->total;
        }

        return
            (float) ($salary->base_salary ?? 0) +
            (float) ($salary->allowance ?? 0) +
            (float) ($salary->overtime ?? 0) +
            (float) ($salary->bonus ?? 0) -
            (float) ($salary->deduction ?? 0);
    }

    private function extractPaymentInfoFromNotes(string $notes): array
    {
        $normalized = trim($notes);

        $method = null;
        $rekening = null;

        if (preg_match('/Metode:\s*Bank Transfer/i', $normalized)) {
            $method = 'transfer';
        } elseif (preg_match('/Metode:\s*Cash/i', $normalized)) {
            $method = 'cash';
        }

        if (preg_match('/Rekening:\s*(.+?)(\s*\|\s*|$)/i', $normalized, $matches)) {
            $rekening = trim($matches[1] ?? '');
        }

        return [
            'method' => $method,
            'rekening' => $rekening,
        ];
    }

    private function accountCodeToLabel(?string $code): string
    {
        return match ($code) {
            'petty' => 'Kas Kecil',
            'cash' => 'Kas Tunai',
            'bank' => 'Kas Bank',
            default => (string) $code,
        };
    }

    private function buildPurchaseSupplierMap(Collection $purchasePayments): Collection
    {
        if (!Schema::hasColumn('purchase_payments', 'purchase_id')) {
            return collect();
        }

        $purchaseIds = $purchasePayments->pluck('purchase_id')->filter()->unique()->values();

        if ($purchaseIds->isEmpty()) {
            return collect();
        }

        $purchases = \App\Models\Purchase::whereIn('id', $purchaseIds)->get();

        return $purchases->mapWithKeys(function ($purchase) {
            $supplier = '';

            if (isset($purchase->supplier) && !empty($purchase->supplier)) {
                $supplier = $purchase->supplier;
            } elseif (isset($purchase->supplier_name) && !empty($purchase->supplier_name)) {
                $supplier = $purchase->supplier_name;
            } elseif (method_exists($purchase, 'supplierRelation') && $purchase->relationLoaded('supplierRelation') && $purchase->supplierRelation) {
                $supplier = $purchase->supplierRelation->name ?? '';
            } elseif (method_exists($purchase, 'supplierRelation')) {
                $relation = $purchase->supplierRelation()->first();
                $supplier = $relation->name ?? '';
            }

            return [$purchase->id => $supplier];
        });
    }

    private function applyDateTimeRange(Builder $query, string $column, ?Carbon $start, ?Carbon $end): void
    {
        if ($start) {
            $query->where($column, '>=', $start->copy()->startOfDay());
        }

        if ($end) {
            $query->where($column, '<=', $end->copy()->endOfDay());
        }
    }
}