<?php

namespace App\Support;

use Carbon\Carbon;
use Carbon\CarbonInterface;

final class RelativeDayLabel
{
    /**
     * @return array{upcoming: list<array{label: string, items: list<array<string, mixed>>}>, history: list<array{label: string, items: list<array<string, mixed>>}>}
     */
    public static function groupedForTransactionsList(array $txns, string $locale): array
    {
        $sorted = FinanceListOrdering::transactionsNewestFirst($txns);
        $future = [];
        $history = [];
        foreach ($sorted as $t) {
            if (self::isFutureTransactionDate((string) ($t['date'] ?? ''))) {
                $future[] = $t;
            } else {
                $history[] = $t;
            }
        }

        return [
            'upcoming' => self::groupedUpcomingDaySections($future, $locale),
            'history' => self::groupedTransactionSections($history, $locale),
        ];
    }

    /** True when the calendar date is strictly after today (app timezone). */
    public static function isFutureTransactionDate(string $dateStr): bool
    {
        if (trim($dateStr) === '') {
            return false;
        }
        try {
            $d = Carbon::parse($dateStr)->startOfDay();
        } catch (\Throwable) {
            return false;
        }

        return $d->isAfter(Carbon::now()->startOfDay());
    }

    /**
     * @param  list<array<string, mixed>>  $txns
     * @return list<array{label: string, items: list<array<string, mixed>>}>
     */
    public static function groupedTransactionSections(array $txns, string $locale): array
    {
        $txns = FinanceListOrdering::transactionsNewestFirst($txns);
        $byDay = [];
        foreach ($txns as $t) {
            $dayKey = self::canonicalDayKey((string) ($t['date'] ?? ''));
            $byDay[$dayKey] ??= [];
            $byDay[$dayKey][] = $t;
        }

        $keys = array_keys($byDay);
        $isoKeys = [];
        $otherKeys = [];
        foreach ($keys as $key) {
            if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $key) === 1) {
                $isoKeys[] = $key;
            } else {
                $otherKeys[] = $key;
            }
        }
        rsort($isoKeys, SORT_STRING);
        sort($otherKeys);
        $keys = array_merge($isoKeys, $otherKeys);

        $out = [];
        foreach ($keys as $dayKey) {
            $out[] = [
                'label' => self::sectionLabelForDayKey($dayKey, $locale),
                'items' => FinanceListOrdering::transactionsNewestFirst($byDay[$dayKey]),
            ];
        }

        return $out;
    }

    /**
     * @param  list<array<string, mixed>>  $futureTxns
     * @return list<array{label: string, items: list<array<string, mixed>>}>
     */
    protected static function groupedUpcomingDaySections(array $futureTxns, string $locale): array
    {
        if ($futureTxns === []) {
            return [];
        }

        $ordered = FinanceListOrdering::transactionsUpcomingChronological($futureTxns);
        $byDay = [];
        foreach ($ordered as $t) {
            $dayKey = self::canonicalDayKey((string) ($t['date'] ?? ''));
            $byDay[$dayKey] ??= [];
            $byDay[$dayKey][] = $t;
        }

        $keys = array_keys($byDay);
        $isoKeys = [];
        $otherKeys = [];
        foreach ($keys as $key) {
            if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $key) === 1) {
                $isoKeys[] = $key;
            } else {
                $otherKeys[] = $key;
            }
        }
        sort($isoKeys, SORT_STRING);
        sort($otherKeys);
        $keys = array_merge($isoKeys, $otherKeys);

        $out = [];
        foreach ($keys as $dayKey) {
            $out[] = [
                'label' => self::sectionLabelForUpcomingDayKey($dayKey, $locale),
                'items' => FinanceListOrdering::transactionsNewestFirst($byDay[$dayKey]),
            ];
        }

        return $out;
    }

    /**
     * Human label for a future calendar day (tomorrow, weekday, or long date).
     */
    public static function sectionUpcomingCalendarDay(string $dateStr, string $locale): string
    {
        if (trim($dateStr) === '') {
            return '—';
        }

        try {
            $d = Carbon::parse($dateStr)->startOfDay()->locale($locale);
        } catch (\Throwable) {
            return $dateStr;
        }

        $today = Carbon::now()->startOfDay();
        if ($d->lte($today)) {
            return self::section($dateStr, $locale);
        }

        $days = (int) $today->diffInDays($d, false);
        if ($days === 1) {
            return (string) __('budgets.transactions_page.tomorrow');
        }
        if ($days <= 7) {
            return $d->translatedFormat('D, M j');
        }
        if ($d->year === $today->year) {
            return $d->translatedFormat('M j');
        }

        return $d->translatedFormat('M j, Y');
    }

    /** Heading for a transaction day group (matches transactions list). */
    public static function section(string $dateStr, string $locale): string
    {
        if (trim($dateStr) === '') {
            return '—';
        }

        try {
            $d = Carbon::parse($dateStr)->startOfDay()->locale($locale);
        } catch (\Throwable) {
            return $dateStr;
        }

        if ($d->isToday()) {
            return (string) __('budgets.transactions_page.today');
        }
        if ($d->isYesterday()) {
            return (string) __('budgets.transactions_page.yesterday');
        }

        return $d->translatedFormat('M j, Y');
    }

    /** Short line under amounts (e.g. dashboard rows). */
    public static function compact(string $dateStr, string $locale): string
    {
        if (trim($dateStr) === '') {
            return '—';
        }

        try {
            $d = Carbon::parse($dateStr)->startOfDay()->locale($locale);
        } catch (\Throwable) {
            return $dateStr;
        }

        if ($d->isToday()) {
            return (string) __('budgets.transactions_page.today');
        }
        if ($d->isYesterday()) {
            return (string) __('budgets.transactions_page.yesterday');
        }

        $now = Carbon::now()->locale($locale);
        if ($d->year === $now->year) {
            return $d->translatedFormat('M j');
        }

        return $d->translatedFormat('M j, Y');
    }

    /** For renewal / due rows (future or today). */
    public static function upcomingDay(CarbonInterface $d, string $locale): string
    {
        $day = Carbon::parse($d)->locale($locale)->startOfDay();
        $today = Carbon::now()->locale($locale)->startOfDay();

        if ($day->equalTo($today)) {
            return (string) __('budgets.transactions_page.today');
        }
        if ($day->equalTo($today->copy()->addDay())) {
            return (string) __('budgets.transactions_page.tomorrow');
        }

        return $day->translatedFormat('M j');
    }

    protected static function sectionLabelForDayKey(string $dayKey, string $locale): string
    {
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $dayKey) === 1) {
            return self::section($dayKey, $locale);
        }

        if ($dayKey === '_empty') {
            return '—';
        }

        if (str_starts_with($dayKey, '_bad:')) {
            return substr($dayKey, strlen('_bad:'));
        }

        return $dayKey;
    }

    protected static function sectionLabelForUpcomingDayKey(string $dayKey, string $locale): string
    {
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $dayKey) === 1) {
            return self::sectionUpcomingCalendarDay($dayKey, $locale);
        }

        return self::sectionLabelForDayKey($dayKey, $locale);
    }

    protected static function canonicalDayKey(string $dateStr): string
    {
        $trim = trim($dateStr);
        if ($trim === '') {
            return '_empty';
        }

        try {
            return Carbon::parse($trim)->toDateString();
        } catch (\Throwable) {
            return '_bad:'.$trim;
        }
    }
}
