<?php

declare(strict_types=1);

namespace App\Filament\Widgets\Admin;

use Carbon\Carbon;
use Filament\Support\RawJs;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Leandrocfe\FilamentApexCharts\Widgets\ApexChartWidget;

/**
 * Gráfico de Flujo de Caja: Lo Cobrado vs Lo Prestado — Admin.
 *
 * Motor: ApexCharts (leandrocfe/filament-apex-charts ^5.0)
 *
 * Series:
 *   - Lo Cobrado : SUM(payments.amount) no anulados sobre créditos hoja.
 *   - Lo Prestado: SUM(expenses.amount) donde category IN ('credit_disbursement','disbursement').
 *
 * RENDIMIENTO — consultas a nivel de base de datos:
 *   Dos SELECT + GROUP BY DATE. No se carga ninguna colección Eloquent en memoria.
 *   El cursor PHP solo itera para rellenar ceros en días sin actividad (≤30 iteraciones).
 *
 * TOOLTIPS — sin JavaScript embebido en arrays PHP:
 *   Los formatters de moneda van en extraJsOptions() como RawJs, nunca como strings
 *   dentro de getOptions(). Esto elimina el error «s[t].call is not a function» de Chart.js.
 */
class CashFlowComparisonChart extends ApexChartWidget
{
    protected static ?string $chartId = 'cashFlowComparisonChart';

    protected static ?string $heading = 'Flujo de Caja: Lo Cobrado vs Lo Prestado';

    protected ?string $pollingInterval = '300s';

    protected static ?int $sort = 2;

    // columnSpan = 1 → ocupa 1 de las 2 columnas del grid Admin (mitad derecha).
    protected int|string|array $columnSpan = 1;

    public ?string $filter = 'month';

    public static function canView(): bool
    {
        $user = Auth::user();

        return $user && $user->hasRole('admin') && ! $user->isSuperAdmin();
    }

    protected function getFilters(): ?array
    {
        return [
            'today' => 'Hoy',
            'week' => 'Semana (7 días)',
            'fortnight' => 'Quincena (15 días)',
            'month' => 'Mes (30 días)',
        ];
    }

    protected function getOptions(): array
    {
        $companyId = (int) Auth::user()->company_id;
        $today = Carbon::today();
        $from = $this->resolveFromDate($today);

        $fromStr = $from->toDateString();
        $todayStr = $today->toDateString();

        // ── Lo Cobrado ────────────────────────────────────────────────────────
        // GROUP BY DATE — retorna ≤30 filas. Sin colecciones Eloquent en memoria.
        // Excluye pagos anulados y créditos padre (con hijos).
        $collected = DB::table('payments')
            ->where('payments.company_id', $companyId)
            ->where('payments.voided', false)
            ->where('payments.payment_method', '!=', 'reversal')
            ->whereBetween('payments.payment_date', [$fromStr, $todayStr])
            ->whereNotExists(function ($sub) {
                $sub->select(DB::raw(1))
                    ->from('credits as ch')
                    ->whereColumn('ch.parent_credit_id', 'payments.credit_id');
            })
            ->selectRaw('DATE(payment_date) as day, SUM(amount) as total')
            ->groupBy(DB::raw('DATE(payment_date)'))
            ->pluck('total', 'day');

        // ── Lo Prestado ───────────────────────────────────────────────────────
        // 'disbursement' es el valor legado (ExpenseCategory::DISBURSEMENT @deprecated).
        // Se incluye para no perder datos históricos pre-Feb 2026.
        $disbursed = DB::table('expenses')
            ->where('company_id', $companyId)
            ->whereIn('category', ['credit_disbursement', 'disbursement'])
            ->whereBetween('operation_date', [$fromStr, $todayStr])
            ->selectRaw('DATE(operation_date) as day, SUM(amount) as total')
            ->groupBy(DB::raw('DATE(operation_date)'))
            ->pluck('total', 'day');

        // ── Construir ejes ────────────────────────────────────────────────────
        $labels = [];
        $collectedData = [];
        $disbursedData = [];

        $cursor = $from->copy();
        while ($cursor->lte($today)) {
            $key = $cursor->toDateString();
            $labels[] = $this->filter === 'today' ? 'Hoy' : $cursor->format('d/m');

            $collectedData[] = round((float) ($collected[$key] ?? 0), 2);
            $disbursedData[] = round((float) ($disbursed[$key] ?? 0), 2);

            $cursor->addDay();
        }

        return [
            'chart' => [
                'type' => 'bar',
                'height' => 400,
                'toolbar' => ['show' => false],
            ],
            'series' => [
                [
                    'name' => 'Lo Cobrado',
                    'data' => $collectedData,
                ],
                [
                    'name' => 'Lo Prestado',
                    'data' => $disbursedData,
                ],
            ],
            'xaxis' => [
                'categories' => $labels,
                'labels' => [
                    'style' => ['colors' => '#9ca3af', 'fontWeight' => 600],
                ],
            ],
            'yaxis' => [
                'labels' => [
                    'style' => ['colors' => '#9ca3af', 'fontWeight' => 600],
                ],
            ],
            'colors' => ['#10b981', '#6366f1'],
            'plotOptions' => [
                'bar' => [
                    'borderRadius' => 4,
                    'columnWidth' => '65%',
                    'dataLabels' => ['position' => 'top'],
                ],
            ],
            'dataLabels' => ['enabled' => false],
            'legend' => [
                'position' => 'top',
                'labels' => ['colors' => '#9ca3af', 'fontWeight' => 600],
            ],
            'grid' => [
                'borderColor' => '#e5e7eb',
                'strokeDashArray' => 4,
            ],
        ];
    }

    /**
     * Formatters de moneda como JavaScript nativo.
     *
     * Se separan de getOptions() porque getOptions() se serializa como JSON
     * (via @js()), lo que destruiría cualquier función JS embebida.
     * extraJsOptions() se renderiza como texto crudo en la plantilla Blade y
     * se fusiona (deep merge) con las opciones antes de inicializar ApexCharts.
     */
    protected function extraJsOptions(): ?RawJs
    {
        return RawJs::make(<<<'JS'
        {
            yaxis: {
                labels: {
                    formatter: function(value) {
                        if (value >= 1000000) return '$' + (value / 1000000).toFixed(1) + 'M';
                        if (value >= 1000)    return '$' + (value / 1000).toFixed(0) + 'K';
                        return '$' + value.toLocaleString('es-CO');
                    }
                }
            },
            tooltip: {
                y: {
                    formatter: function(value) {
                        return '$' + value.toLocaleString('es-CO');
                    }
                }
            }
        }
        JS);
    }

    private function resolveFromDate(Carbon $today): Carbon
    {
        return match ($this->filter) {
            'today' => $today->copy(),
            'week' => $today->copy()->subDays(6),
            'fortnight' => $today->copy()->subDays(14),
            default => $today->copy()->subDays(29),
        };
    }
}
