<?php

declare(strict_types=1);

namespace app\common\library\game;

use InvalidArgumentException;

/**
 * 36 字花字典（game_config.zi_hua_36_dictionary）：仅存 JSON 数组
 * category 使用英文：zodiac|beast|fowl|vermin|divine
 */
final class ZiHuaDictionary
{
    public const CONFIG_KEY = 'zi_hua_36_dictionary';

    /** 与表单下拉一致，顺序即展示顺序 */
    public const CATEGORIES = ['zodiac', 'beast', 'fowl', 'vermin', 'divine'];

    private const LEGACY_CATEGORY_MAP = [
        '生肖'   => 'zodiac',
        '猛兽'   => 'beast',
        '飞禽'   => 'fowl',
        '虫蛇'   => 'vermin',
        '神兽'   => 'divine',
        'zodiac' => 'zodiac',
        'beast'  => 'beast',
        'fowl'   => 'fowl',
        'vermin' => 'vermin',
        'divine' => 'divine',
    ];

    /**
     * 默认 36 条（与 PRD 一致，category 为英文）
     *
     * @return list<array{no: int, name: string, category: string}>
     */
    public static function defaultItems(): array
    {
        return [
            ['no' => 1, 'name' => '鼠', 'category' => 'zodiac'],
            ['no' => 2, 'name' => '牛', 'category' => 'zodiac'],
            ['no' => 3, 'name' => '虎', 'category' => 'zodiac'],
            ['no' => 4, 'name' => '兔', 'category' => 'zodiac'],
            ['no' => 5, 'name' => '龙', 'category' => 'zodiac'],
            ['no' => 6, 'name' => '蛇', 'category' => 'zodiac'],
            ['no' => 7, 'name' => '马', 'category' => 'zodiac'],
            ['no' => 8, 'name' => '羊', 'category' => 'zodiac'],
            ['no' => 9, 'name' => '猴', 'category' => 'zodiac'],
            ['no' => 10, 'name' => '鸡', 'category' => 'zodiac'],
            ['no' => 11, 'name' => '狗', 'category' => 'zodiac'],
            ['no' => 12, 'name' => '猪', 'category' => 'zodiac'],
            ['no' => 13, 'name' => '狮', 'category' => 'beast'],
            ['no' => 14, 'name' => '象', 'category' => 'beast'],
            ['no' => 15, 'name' => '鹿', 'category' => 'beast'],
            ['no' => 16, 'name' => '熊', 'category' => 'beast'],
            ['no' => 17, 'name' => '狼', 'category' => 'beast'],
            ['no' => 18, 'name' => '豹', 'category' => 'beast'],
            ['no' => 19, 'name' => '鹰', 'category' => 'fowl'],
            ['no' => 20, 'name' => '鹤', 'category' => 'fowl'],
            ['no' => 21, 'name' => '孔雀', 'category' => 'fowl'],
            ['no' => 22, 'name' => '鸳鸯', 'category' => 'fowl'],
            ['no' => 23, 'name' => '鸵鸟', 'category' => 'fowl'],
            ['no' => 24, 'name' => '天鹅', 'category' => 'fowl'],
            ['no' => 25, 'name' => '蟾蜍', 'category' => 'vermin'],
            ['no' => 26, 'name' => '蜘蛛', 'category' => 'vermin'],
            ['no' => 27, 'name' => '蝙蝠', 'category' => 'vermin'],
            ['no' => 28, 'name' => '蜈蚣', 'category' => 'vermin'],
            ['no' => 29, 'name' => '蝎', 'category' => 'vermin'],
            ['no' => 30, 'name' => '蛇蜥', 'category' => 'vermin'],
            ['no' => 31, 'name' => '麒麟', 'category' => 'divine'],
            ['no' => 32, 'name' => '凤凰', 'category' => 'divine'],
            ['no' => 33, 'name' => '青龙', 'category' => 'divine'],
            ['no' => 34, 'name' => '白虎', 'category' => 'divine'],
            ['no' => 35, 'name' => '朱雀', 'category' => 'divine'],
            ['no' => 36, 'name' => '玄武', 'category' => 'divine'],
        ];
    }

    /**
     * @return list<array{no: int, name: string, category: string}>
     */
    public static function parseFromConfigValue(?string $configValue): array
    {
        if ($configValue === null || trim($configValue) === '') {
            return self::defaultItems();
        }
        $decoded = json_decode($configValue, true);
        if (!is_array($decoded)) {
            return self::defaultItems();
        }
        if (isset($decoded['items']) && is_array($decoded['items'])) {
            return self::normalizeItemsList($decoded['items']);
        }
        if ($decoded !== [] && array_is_list($decoded)) {
            return self::normalizeItemsList($decoded);
        }
        return self::defaultItems();
    }

    /**
     * @param list<mixed> $items
     * @return list<array{no: int, name: string, category: string}>
     */
    public static function normalizeItemsList(array $items): array
    {
        $defaults = self::defaultItems();
        $defaultByNo = [];
        foreach ($defaults as $row) {
            $defaultByNo[$row['no']] = $row;
        }

        $out = [];
        foreach ($items as $row) {
            if (!is_array($row)) {
                continue;
            }
            $no = $row['no'] ?? null;
            if (!is_numeric($no)) {
                continue;
            }
            $n = intval($no, 10);
            if ($n < 1 || $n > 36) {
                continue;
            }
            $name = isset($row['name']) && is_string($row['name']) ? trim($row['name']) : '';
            if ($name === '') {
                $name = $defaultByNo[$n]['name'] ?? '';
            }
            $catRaw = $row['category'] ?? 'zodiac';
            $catStr = is_string($catRaw) ? trim($catRaw) : '';
            $category = self::LEGACY_CATEGORY_MAP[$catStr] ?? (in_array($catStr, self::CATEGORIES, true) ? $catStr : 'zodiac');
            $out[] = ['no' => $n, 'name' => $name, 'category' => $category];
        }

        if (count($out) !== 36) {
            return self::defaultItems();
        }

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

        $seen = [];
        foreach ($out as $row) {
            if (isset($seen[$row['no']])) {
                return self::defaultItems();
            }
            $seen[$row['no']] = true;
        }

        for ($i = 1; $i <= 36; $i++) {
            if (!isset($seen[$i])) {
                return self::defaultItems();
            }
        }

        return $out;
    }

    /**
     * 校验 POST 数据并输出入库用的 36 条（no 升序）
     *
     * @param list<array{no?: mixed, name?: mixed, category?: mixed}> $items
     *
     * @return list<array{no: int, name: string, category: string}>
     *
     * @throws InvalidArgumentException
     */
    public static function prepareItemsForSave(array $items): array
    {
        if (count($items) !== 36) {
            throw new InvalidArgumentException('Must be exactly 36 items');
        }
        $nos = [];
        $out = [];
        foreach ($items as $idx => $row) {
            if (!is_array($row)) {
                throw new InvalidArgumentException('Row format error');
            }
            $no = $row['no'] ?? null;
            if (!is_numeric($no)) {
                throw new InvalidArgumentException('No must be numeric');
            }
            $n = intval($no, 10);
            if ($n < 1 || $n > 36) {
                throw new InvalidArgumentException('No must be between 1 and 36');
            }
            if (isset($nos[$n])) {
                throw new InvalidArgumentException('Duplicate no');
            }
            $nos[$n] = true;

            $name = $row['name'] ?? '';
            if (!is_string($name) || trim($name) === '') {
                throw new InvalidArgumentException('Name is required');
            }

            $cat = $row['category'] ?? '';
            $catTrim = is_string($cat) ? trim($cat) : '';
            if (!in_array($catTrim, self::CATEGORIES, true)) {
                throw new InvalidArgumentException('Category is invalid');
            }

            $out[] = ['no' => $n, 'name' => trim($name), 'category' => $catTrim];
        }
        for ($i = 1; $i <= 36; $i++) {
            if (!isset($nos[$i])) {
                throw new InvalidArgumentException('Missing no');
            }
        }
        usort($out, static fn (array $a, array $b): int => $a['no'] <=> $b['no']);
        return $out;
    }

    /**
     * @param list<array{no: int, name: string, category: string}> $items
     */
    public static function encodeForDb(array $items): string
    {
        usort($items, static fn (array $a, array $b): int => $a['no'] <=> $b['no']);
        $encoded = json_encode($items, JSON_UNESCAPED_UNICODE);
        if ($encoded === false) {
            throw new InvalidArgumentException('JSON encode failed');
        }
        return $encoded;
    }
}
