<?php

namespace App\Livewire;

use App\Services\FinanceRepository;
use App\Support\BudgetInference;
use App\Support\CategoryCatalog;
use App\Support\DescriptionAutocomplete;
use App\Support\FinanceListOrdering;
use Illuminate\Support\Carbon;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Url;
use Livewire\Component;

#[Layout('layouts.app')]
class BudgetsPage extends Component
{
    /** @var list<array<string,mixed>>|null */
    private ?array $budgetHistoryCache = null;

    /** @var list<string>|null */
    private ?array $budgetNameHistoryCache = null;

    #[Url]
    public ?string $create = null;

    #[Url(as: 'search')]
    public ?string $openSheet = null;

    public string $search = '';

    public bool $mobileSearchOpen = false;

    /** null | 'new' | id */
    public ?string $editing = null;

    public string $editName = '';

    public ?float $editLimit = null;

    public string $editCategoryId = 'food';

    public string $editPeriod = 'monthly';

    public string $editIcon = '📊';

    /** @var list<array{id: string, label: string, detail?: string, tone?: string}> */
    public array $smartSuggestions = [];

    /** @var list<array{value: string, hint?: string}> */
    public array $descriptionHints = [];

    public bool $manualCategory = false;

    public bool $manualPeriod = false;

    public bool $manualLimit = false;

    public function mount(): void
    {
        if ($this->create === 'budget') {
            $this->editing = 'new';
            $this->create = null;
            $this->bootstrapForm(null);
        }
        if ($this->openSheet === '1') {
            $this->mobileSearchOpen = true;
            $this->openSheet = null;
        }
    }

    public function openNew(): void
    {
        $this->editing = 'new';
        $this->bootstrapForm(null);
    }

    public function openEdit(string $id, FinanceRepository $finance): void
    {
        $row = collect($finance->budgets())->firstWhere('id', $id);
        if (! $row) {
            return;
        }
        $this->editing = $id;
        $this->bootstrapForm($row);
    }

    public function closeModal(): void
    {
        $this->editing = null;
    }

    public function closeMobileSearch(): void
    {
        $this->mobileSearchOpen = false;
    }

    /**
     * @param  array<string, mixed>|null  $budget
     */
    protected function bootstrapForm(?array $budget): void
    {
        $this->manualCategory = false;
        $this->manualPeriod = false;
        $this->manualLimit = false;

        if ($budget === null) {
            $this->editName = '';
            $this->editLimit = null;
            $this->editCategoryId = 'food';
            $this->editPeriod = 'monthly';
            $this->editIcon = '📊';
        } else {
            $this->editName = (string) ($budget['name'] ?? '');
            $this->editLimit = isset($budget['limit']) ? (float) $budget['limit'] : null;
            $this->editCategoryId = (string) ($budget['categoryId'] ?? 'food');
            $this->editPeriod = (string) ($budget['period'] ?? 'monthly');
            $this->editIcon = (string) ($budget['icon'] ?? '📊');
        }
        $this->smartSuggestions = [];
        $this->descriptionHints = [];
    }

    protected function syncInference(): void
    {
        $inf = BudgetInference::infer($this->editName, $this->budgetHistory());
        $this->smartSuggestions = $inf['suggestions'] ?? [];
        if (! empty($inf['categoryId']) && ! $this->manualCategory) {
            $this->editCategoryId = $inf['categoryId'];
        }
        if (! empty($inf['period']) && ! $this->manualPeriod) {
            $this->editPeriod = $inf['period'];
        }
        if (! empty($inf['icon'])) {
            $this->editIcon = $inf['icon'];
        }
        if (isset($inf['limit']) && is_numeric($inf['limit']) && ! $this->manualLimit && ($this->editLimit === null || $this->editLimit === 0.0)) {
            $this->editLimit = (float) $inf['limit'];
        }
        $this->descriptionHints = DescriptionAutocomplete::suggest(
            $this->editName,
            DescriptionAutocomplete::CONTEXT_BUDGET,
            $this->budgetNameHistory(),
            12,
        );
    }

    /**
     * @return list<string>
     */
    protected function budgetNameHistory(): array
    {
        if ($this->budgetNameHistoryCache !== null) {
            return $this->budgetNameHistoryCache;
        }

        $this->budgetNameHistoryCache = collect($this->budgetHistory())
            ->pluck('name')
            ->map(fn ($n) => trim((string) $n))
            ->filter()
            ->unique(fn (string $n) => mb_strtolower($n))
            ->values()
            ->all();

        return $this->budgetNameHistoryCache;
    }

    public function pickDescriptionHint(int $index): void
    {
        $row = $this->descriptionHints[$index] ?? null;
        if (! is_array($row) || ($row['value'] ?? '') === '') {
            return;
        }
        $this->editName = (string) $row['value'];
        $this->syncInference();
    }

    public function updatedEditName(): void
    {
        $this->syncInference();
    }

    public function updatedEditLimit(): void
    {
        $this->manualLimit = true;
    }

    public function setCategory(string $id): void
    {
        $this->manualCategory = true;
        $this->editCategoryId = $id;
    }

    public function setPeriod(string $p): void
    {
        $this->manualPeriod = true;
        $this->editPeriod = $p;
    }

    public function saveBudget(FinanceRepository $finance): void
    {
        if ($this->editing === null || $this->editName === '' || $this->editLimit === null) {
            return;
        }
        $rows = collect($finance->budgets())->keyBy('id');
        $id = $this->editing === 'new'
            ? 'b'.(string) (int) round(microtime(true) * 1000)
            : $this->editing;
        $existing = $rows->get($id);

        $row = [
            'id' => $id,
            'name' => $this->editName,
            'categoryId' => $this->editCategoryId,
            'limit' => (float) $this->editLimit,
            'spent' => is_array($existing) ? (float) ($existing['spent'] ?? 0) : 0.0,
            'period' => $this->editPeriod,
            'icon' => $this->editIcon,
        ];
        $rows->put($id, $row);
        $finance->setBudgets($rows->values()->all());
        $this->invalidateInferenceCaches();
        $this->closeModal();
    }

    public function deleteBudget(): void
    {
        if ($this->editing === null || $this->editing === 'new') {
            return;
        }
        $finance = app(FinanceRepository::class);
        $deleted = $finance->deleteBudget($this->editing);
        if (! $deleted) {
            $msg = (string) __('budgets.budgets_page.toast_delete_failed');
            session()->flash('toast', $msg);
            $this->dispatch('app-toast', message: $msg);

            return;
        }
        $this->invalidateInferenceCaches();
        $this->closeModal();
        $msg = (string) __('budgets.budgets_page.toast_deleted');
        session()->flash('toast', $msg);
        $this->dispatch('app-toast', message: $msg);
    }

    /** @return list<array<string,mixed>> */
    protected function budgetHistory(): array
    {
        if ($this->budgetHistoryCache !== null) {
            return $this->budgetHistoryCache;
        }

        $this->budgetHistoryCache = app(FinanceRepository::class)->budgets();

        return $this->budgetHistoryCache;
    }

    protected function invalidateInferenceCaches(): void
    {
        $this->budgetHistoryCache = null;
        $this->budgetNameHistoryCache = null;
    }

    public function render(FinanceRepository $finance)
    {
        $budgets = $finance->budgets();
        $q = mb_strtolower(trim($this->search));
        $filtered = $q === ''
            ? $budgets
            : array_values(array_filter($budgets, fn ($b) => str_contains(mb_strtolower((string) ($b['name'] ?? '')), $q)));
        $filtered = FinanceListOrdering::budgetsByUrgency($filtered);

        $totalLimit = (float) collect($budgets)->sum(fn ($b) => (float) ($b['limit'] ?? 0));
        $totalSpent = (float) collect($budgets)->sum(fn ($b) => (float) ($b['spent'] ?? 0));
        $daysLeft = 14;
        $pctUsed = $totalLimit > 0 ? ($totalSpent / $totalLimit) * 100 : 0;
        $perDay = $daysLeft > 0 && $totalLimit > 0 ? ($totalLimit - $totalSpent) / $daysLeft : 0;
        $overBudgetCount = (int) collect($budgets)->filter(fn ($b) => (float) ($b['spent'] ?? 0) > (float) ($b['limit'] ?? 0))->count();
        $nearLimitCount = (int) collect($budgets)->filter(function ($b) {
            $limit = (float) ($b['limit'] ?? 0);
            $spent = (float) ($b['spent'] ?? 0);

            return $limit > 0 && $spent / $limit >= 0.85 && $spent <= $limit;
        })->count();
        $topSpending = collect($budgets)->sortByDesc(fn ($b) => (float) ($b['spent'] ?? 0))->first();
        $monthYear = Carbon::now()->locale((string) app()->getLocale())->translatedFormat('F Y');
        $heroPeriod = (string) __('budgets.budgets_page.hero_period', [
            'month' => $monthYear,
            'days' => $daysLeft,
        ]);

        return view('livewire.budgets-page', [
            'filtered' => $filtered,
            'totalLimit' => $totalLimit,
            'totalSpent' => $totalSpent,
            'daysLeft' => $daysLeft,
            'pctUsed' => $pctUsed,
            'perDay' => $perDay,
            'overBudgetCount' => $overBudgetCount,
            'nearLimitCount' => $nearLimitCount,
            'topSpending' => $topSpending,
            'heroPeriod' => $heroPeriod,
            'categories' => CategoryCatalog::all(),
        ])->title((string) __('budgets.budgets_page.title'));
    }
}
