<?php

namespace App\Support;

/**
 * Mirrors inferTransactionInput from bugets-smart-money/src/lib/smart-form.ts
 *
 * @return array{
 *     categoryId?: string,
 *     type?: string,
 *     isRecurring?: bool,
 *     recurringConfig?: array{frequency: string, startDate: string},
 *     suggestions: list<array{id: string, label: string, detail?: string, tone?: string}>
 * }
 */
final class TransactionInference
{
    private static function isoToday(): string
    {
        return date('Y-m-d');
    }

    /**
     * @param  list<array{match: list<string>, type?: string, recurring?: bool}>  $merchantRulebook
     * @return array<string, mixed>
     */
    public static function infer(string $name, ?float $amount = null, array $history = []): array
    {
        $input = mb_strtolower(trim($name));
        $suggestions = [];
        $result = ['suggestions' => $suggestions];

        $detected = CategoryDetector::detect($name);
        if ($detected !== null) {
            $cat = CategoryCatalog::get($detected);
            $result['categoryId'] = $detected;
            $suggestions[] = [
                'id' => 'category',
                'label' => (string) __('inference.transaction.category_set', ['category' => $cat['name']]),
                'tone' => 'positive',
            ];
            if ($detected === 'income') {
                $result['type'] = 'income';
                $suggestions[] = [
                    'id' => 'type',
                    'label' => (string) __('inference.transaction.income_marked'),
                    'tone' => 'positive',
                ];
            }
        }

        $merchantRulebook = [
            ['match' => ['salary', 'payroll', 'freelance', 'invoice', 'bonus'], 'type' => 'income'],
            ['match' => ['netflix', 'spotify', 'adobe', 'icloud', 'gym', 'membership', 'subscription', 'apple music', 'disney'], 'recurring' => true],
        ];

        foreach ($merchantRulebook as $rule) {
            $hit = false;
            foreach ($rule['match'] as $term) {
                if ($term !== '' && str_contains($input, $term)) {
                    $hit = true;
                    break;
                }
            }
            if (! $hit) {
                continue;
            }
            if (isset($rule['type'])) {
                $result['type'] = $rule['type'];
            }
            if (! empty($rule['recurring'])) {
                $result['isRecurring'] = true;
                $result['recurringConfig'] = ['frequency' => 'monthly', 'startDate' => self::isoToday()];
                $suggestions[] = [
                    'id' => 'recurring',
                    'label' => (string) __('inference.transaction.recurring_label'),
                    'detail' => (string) __('inference.transaction.recurring_detail'),
                    'tone' => 'warning',
                ];
            }
        }

        if (str_contains($input, 'rent') || str_contains($input, 'salary')) {
            $result['isRecurring'] = true;
            $result['recurringConfig'] = ['frequency' => 'monthly', 'startDate' => self::isoToday()];
        }

        // Learn from existing transactions for similar merchant names.
        $nameTokens = array_values(array_filter(preg_split('/[^a-z0-9]+/i', $input ?: '')));
        $bestScore = 0;
        $bestRow = null;
        foreach ($history as $row) {
            if (! is_array($row)) {
                continue;
            }
            $rowName = mb_strtolower((string) ($row['name'] ?? ''));
            if ($rowName === '') {
                continue;
            }
            $score = 0;
            foreach ($nameTokens as $token) {
                if (mb_strlen($token) < 3) {
                    continue;
                }
                if (str_contains($rowName, $token)) {
                    $score++;
                }
            }
            if ($score > $bestScore) {
                $bestScore = $score;
                $bestRow = $row;
            }
        }
        if ($bestRow !== null && $bestScore > 0) {
            $pastCategory = (string) ($bestRow['categoryId'] ?? '');
            if ($pastCategory !== '' && empty($result['categoryId'])) {
                $result['categoryId'] = $pastCategory;
                $cat = CategoryCatalog::get($pastCategory);
                $suggestions[] = [
                    'id' => 'history-category',
                    'label' => (string) __('inference.transaction.history_category_label'),
                    'detail' => (string) __('inference.transaction.history_category_detail', ['category' => $cat['name']]),
                    'tone' => 'positive',
                ];
            }

            $pastType = (string) ($bestRow['type'] ?? '');
            if (in_array($pastType, ['income', 'expense'], true) && empty($result['type'])) {
                $result['type'] = $pastType;
            }

            if (isset($bestRow['isRecurring']) && (bool) $bestRow['isRecurring'] && ! isset($result['isRecurring'])) {
                $result['isRecurring'] = true;
                $result['recurringConfig'] = [
                    'frequency' => (string) ($bestRow['recurringConfig']['frequency'] ?? 'monthly'),
                    'startDate' => self::isoToday(),
                ];
                $suggestions[] = [
                    'id' => 'history-recurring',
                    'label' => (string) __('inference.transaction.history_recurring_label'),
                    'detail' => (string) __('inference.transaction.history_recurring_detail'),
                    'tone' => 'warning',
                ];
            }

            if ($amount !== null) {
                $pastAmount = abs((float) ($bestRow['amount'] ?? 0));
                if ($pastAmount > 0) {
                    $diffPct = abs($pastAmount - abs($amount)) / $pastAmount;
                    if ($diffPct > 0.7) {
                        $suggestions[] = [
                            'id' => 'amount-outlier',
                            'label' => (string) __('inference.transaction.amount_outlier_label'),
                            'detail' => (string) __('inference.transaction.amount_outlier_detail', [
                                'amount' => Money::formatEur($pastAmount),
                            ]),
                            'tone' => 'warning',
                        ];
                    }
                }
            }
        }

        $result['suggestions'] = $suggestions;

        return $result;
    }
}
