<?php

namespace App\Support;

/**
 * Rich, context-aware description suggestions for money forms (prefix / contains / word-start scoring).
 */
final class DescriptionAutocomplete
{
    public const CONTEXT_TRANSACTION = 'transaction';

    public const CONTEXT_BUDGET = 'budget';

    public const CONTEXT_GOAL = 'goal';

    public const CONTEXT_DEBT = 'debt';

    public const CONTEXT_SUBSCRIPTION = 'subscription';

    /**
     * @param  list<string>  $historyLabels
     * @return list<array{value: string, hint?: string}>
     */
    public static function suggest(string $query, string $context, array $historyLabels = [], int $limit = 12): array
    {
        $needle = BrandResolver::normalize($query);
        if ($needle === '' || mb_strlen($needle) < 2) {
            return [];
        }

        $candidates = self::buildCandidates($context, $historyLabels);
        $scored = [];

        foreach ($candidates as $row) {
            $value = (string) ($row['value'] ?? '');
            if (trim($value) === '') {
                continue;
            }
            $norm = BrandResolver::normalize($value);
            $score = self::score($needle, $norm);
            if ($score <= 0) {
                continue;
            }
            $key = mb_strtolower($value);
            $hint = isset($row['hint']) ? (string) $row['hint'] : null;
            if (! isset($scored[$key]) || $score > $scored[$key]['score']) {
                $scored[$key] = [
                    'score' => $score,
                    'value' => $value,
                    'hint' => $hint,
                ];
            }
        }

        usort($scored, static fn (array $a, array $b): int => $b['score'] <=> $a['score']);

        $out = [];
        foreach ($scored as $row) {
            $item = ['value' => $row['value']];
            if (! empty($row['hint'])) {
                $item['hint'] = $row['hint'];
            }
            $out[] = $item;
            if (count($out) >= $limit) {
                break;
            }
        }

        return $out;
    }

    /**
     * @param  list<string>  $historyLabels
     * @return list<array{value: string, hint?: string}>
     */
    private static function buildCandidates(string $context, array $historyLabels): array
    {
        $cfg = config('finance_description_hints', []);
        $out = [];

        foreach ($cfg['shared'] ?? [] as $row) {
            $out[] = self::normalizeRow($row);
        }

        foreach ($cfg[$context] ?? [] as $row) {
            $out[] = self::normalizeRow($row);
        }

        foreach (config('budgets_categories', []) as $cid => $cat) {
            $name = CategoryCatalog::label((string) $cid);
            if ($name !== '') {
                $out[] = ['value' => $name];
            }
        }

        foreach ($historyLabels as $h) {
            $t = trim((string) $h);
            if ($t !== '') {
                $out[] = ['value' => $t];
            }
        }

        return $out;
    }

    /**
     * @param  array<string, mixed>|string  $row
     * @return array{value: string, hint?: string}
     */
    private static function normalizeRow(array|string $row): array
    {
        if (is_string($row)) {
            return ['value' => $row];
        }
        $out = ['value' => (string) ($row['value'] ?? '')];
        if (! empty($row['hint'])) {
            $out['hint'] = (string) $row['hint'];
        }

        return $out;
    }

    private static function score(string $needleNorm, string $candidateNorm): int
    {
        if ($candidateNorm === '') {
            return 0;
        }

        if ($candidateNorm === $needleNorm) {
            return 5000;
        }

        if (str_starts_with($candidateNorm, $needleNorm)) {
            return 4000 + min(200, mb_strlen($needleNorm) * 8);
        }

        if (str_contains($candidateNorm, $needleNorm)) {
            return 2500 + min(100, mb_strlen($needleNorm) * 4);
        }

        foreach (preg_split('/\s+/u', $candidateNorm, -1, PREG_SPLIT_NO_EMPTY) ?: [] as $word) {
            if ($word !== '' && str_starts_with($word, $needleNorm)) {
                return 1800 + min(80, mb_strlen($needleNorm) * 3);
            }
        }

        return 0;
    }
}
