<?php

namespace Kylin\WordFilter\Services;

/**
 * 变体检测服务
 *
 * 检测敏感词的各种变体形式，包括谐音、拆字、拼音、缩写等，
 * 并提供文本标准化功能（全角转半角、繁体转简体等）。
 */
class VariantDetector
{
    /**
     * 全角字符到半角字符的映射表
     */
    private const FULLWIDTH_MAP = [
        '０' => '0', '１' => '1', '２' => '2', '３' => '3', '４' => '4',
        '５' => '5', '６' => '6', '７' => '7', '８' => '8', '９' => '9',
        'Ａ' => 'A', 'Ｂ' => 'B', 'Ｃ' => 'C', 'Ｄ' => 'D', 'Ｅ' => 'E',
        'Ｆ' => 'F', 'Ｇ' => 'G', 'Ｈ' => 'H', 'Ｉ' => 'I', 'Ｊ' => 'J',
        'Ｋ' => 'K', 'Ｌ' => 'L', 'Ｍ' => 'M', 'Ｎ' => 'N', 'Ｏ' => 'O',
        'Ｐ' => 'P', 'Ｑ' => 'Q', 'Ｒ' => 'R', 'Ｓ' => 'S', 'Ｔ' => 'T',
        'Ｕ' => 'U', 'Ｖ' => 'V', 'Ｗ' => 'W', 'Ｘ' => 'X', 'Ｙ' => 'Y',
        'Ｚ' => 'Z',
        'ａ' => 'a', 'ｂ' => 'b', 'ｃ' => 'c', 'ｄ' => 'd', 'ｅ' => 'e',
        'ｆ' => 'f', 'ｇ' => 'g', 'ｈ' => 'h', 'ｉ' => 'i', 'ｊ' => 'j',
        'ｋ' => 'k', 'ｌ' => 'l', 'ｍ' => 'm', 'ｎ' => 'n', 'ｏ' => 'o',
        'ｐ' => 'p', 'ｑ' => 'q', 'ｒ' => 'r', 'ｓ' => 's', 'ｔ' => 't',
        'ｕ' => 'u', 'ｖ' => 'v', 'ｗ' => 'w', 'ｘ' => 'x', 'ｙ' => 'y',
        'ｚ' => 'z',
        '！' => '!', '＠' => '@', '＃' => '#', '＄' => '$', '％' => '%',
        '＾' => '^', '＆' => '&', '＊' => '*', '（' => '(', '）' => ')',
        '－' => '-', '＝' => '=', '＿' => '_', '＋' => '+', '｜' => '|',
        '：' => ':', '；' => ';', '，' => ',', '。' => '.', '／' => '/',
        '＜' => '<', '＞' => '>', '？' => '?', '｛' => '{', '｝' => '}',
        '［' => '[', '］' => ']', '＼' => '\\',
    ];

    /**
     * 常见谐音映射表（可扩展）
     */
    private const HOMOPHONE_MAP = [
        '法' => ['发', '珐', '砝'],
        '轮' => ['仑', '伦', '沦'],
        '功' => ['工', '公', '攻'],
        '赌' => ['睹', '堵'],
        '毒' => ['读', '独', '犊'],
        '黄' => ['皇', '惶'],
        '枪' => ['腔', '锵'],
        '杀' => ['纱', '沙'],
        '血' => ['雪', '谑'],
        '色' => ['涩', '瑟'],
    ];

    /**
     * 常见拆字映射表（可扩展）
     */
    private const SPLIT_MAP = [
        '权' => ['木又'],
        '树' => ['木对'],
        '林' => ['木木'],
        '森' => ['木木木'],
        '明' => ['日月'],
        '好' => ['女子'],
        '岩' => ['山石'],
        '雷' => ['雨田'],
        '男' => ['田力'],
        '尖' => ['小大'],
    ];

    /**
     * 文本标准化
     *
     * 处理流程：全角转半角 -> 繁体转简体 -> 去除空白和特殊字符 -> 统一小写
     */
    public function normalizeText(string $text): string
    {
        // 1. 全角转半角
        $text = $this->convertFullwidthToHalfwidth($text);

        // 2. 繁体转简体（使用简繁映射表）
        $text = $this->convertTraditionalToSimplified($text);

        // 3. 去除零宽字符和不可见字符
        $text = preg_replace('/[\x{200B}-\x{200F}\x{2028}-\x{202F}\x{FEFF}\x{00AD}]/u', '', $text);

        // 4. 去除多余空白
        $text = preg_replace('/\s+/u', '', $text);

        // 5. 统一英文字母为小写
        $text = mb_strtolower($text, 'UTF-8');

        return $text;
    }

    /**
     * 谐音检测
     *
     * 根据谐音映射表，生成敏感词的所有谐音变体。
     *
     * @return array<int, string>
     */
    public function detectHomophone(string $word): array
    {
        if (! config('kylin-word-filter.variant_detection.homophone', true)) {
            return [];
        }

        $variants = [];
        $chars = mb_str_split($word);

        foreach ($chars as $char) {
            if (isset(self::HOMOPHONE_MAP[$char])) {
                foreach (self::HOMOPHONE_MAP[$char] as $homophone) {
                    $variants[] = str_replace($char, $homophone, $word);
                }
            }
        }

        return array_unique($variants);
    }

    /**
     * 拆字检测
     *
     * 将汉字拆分为部件组合，检测拆字变体。
     *
     * @return array<int, string>
     */
    public function detectSplit(string $word): array
    {
        if (! config('kylin-word-filter.variant_detection.split', true)) {
            return [];
        }

        $variants = [];
        $chars = mb_str_split($word);

        foreach ($chars as $char) {
            if (isset(self::SPLIT_MAP[$char])) {
                foreach (self::SPLIT_MAP[$char] as $split) {
                    $variants[] = str_replace($char, $split, $word);
                }
            }
        }

        return array_unique($variants);
    }

    /**
     * 拼音检测
     *
     * 将敏感词转换为拼音形式进行检测。
     * 注意：此方法需要 overtrue/pinyin 包支持，如未安装则返回空数组。
     *
     * @return array<int, string>
     */
    public function detectPinyin(string $word): array
    {
        if (! config('kylin-word-filter.variant_detection.pinyin', true)) {
            return [];
        }

        // 检查拼音转换器是否可用
        if (! class_exists(\Overtrue\Pinyin\Pinyin::class)) {
            return [];
        }

        $variants = [];

        try {
            $pinyin = new \Overtrue\Pinyin\Pinyin();

            // 全拼
            $fullPinyin = $pinyin->convert($word, PINYIN_KEEP_NONE);
            if (! empty($fullPinyin)) {
                $variants[] = implode('', $fullPinyin);
            }

            // 首字母缩写
            $abbr = $pinyin->abbr($word);
            if (! empty($abbr) && $abbr !== $word) {
                $variants[] = $abbr;
            }
        } catch (\Throwable) {
            // 拼音转换失败，静默处理
        }

        return array_unique($variants);
    }

    /**
     * 缩写检测
     *
     * 检测敏感词的首字母缩写形式（中文取拼音首字母，英文取首字母）。
     *
     * @return array<int, string>
     */
    public function detectAbbreviation(string $word): array
    {
        if (! config('kylin-word-filter.variant_detection.abbreviation', true)) {
            return [];
        }

        $variants = [];

        // 英文缩写：取首字母
        if (preg_match('/^[a-zA-Z\s]+$/', $word)) {
            $parts = explode(' ', $word);
            $abbr = implode('', array_map(fn (string $p) => $p[0] ?? '', $parts));
            if (mb_strlen($abbr) >= 2 && $abbr !== $word) {
                $variants[] = mb_strtolower($abbr);
            }
        }

        // 中文缩写：依赖拼音首字母
        if (preg_match('/[\x{4e00}-\x{9fff}]/u', $word)) {
            $pinyinVariants = $this->detectPinyin($word);
            foreach ($pinyinVariants as $variant) {
                if (mb_strlen($variant) < mb_strlen($word)) {
                    $variants[] = $variant;
                }
            }
        }

        return array_unique($variants);
    }

    /**
     * 全角转半角
     */
    private function convertFullwidthToHalfwidth(string $text): string
    {
        return strtr($text, self::FULLWIDTH_MAP);
    }

    /**
     * 繁体转简体
     *
     * 使用内置的常见繁简映射表进行转换。
     * 如需更完整的转换，建议安装 overtrue/chinese-util 包。
     */
    private function convertTraditionalToSimplified(string $text): string
    {
        // 常见繁简映射（高频字）
        static $map = null;

        if ($map === null) {
            $map = [
                '國' => '国', '說' => '说', '學' => '学', '對' => '对', '來' => '来',
                '時' => '时', '過' => '过', '個' => '个', '們' => '们', '為' => '为',
                '與' => '与', '這' => '这', '會' => '会', '開' => '开', '經' => '经',
                '動' => '动', '現' => '现', '點' => '点', '長' => '长', '門' => '门',
                '書' => '书', '見' => '见', '電' => '电', '話' => '话', '機' => '机',
                '體' => '体', '問' => '问', '題' => '题', '實' => '实', '氣' => '气',
                '黨' => '党', '產' => '产', '總' => '总', '戰' => '战', '無' => '无',
                '進' => '进', '論' => '论', '區' => '区', '歷' => '历', '結' => '结',
                '規' => '规', '質' => '质', '強' => '强', '據' => '据', '導' => '导',
                '東' => '东', '條' => '条', '濟' => '济', '權' => '权', '義' => '义',
                '網' => '网', '絡' => '络', '數' => '数', '據' => '据', '檢' => '检',
                '查' => '查', '驗' => '验', '證' => '证', '認' => '认', '識' => '识',
                '壓' => '压', '壞' => '坏', '環' => '环', '營' => '营', '業' => '业',
                '選' => '选', '舉' => '举', '辦' => '办', '務' => '务', '種' => '种',
                '層' => '层', '師' => '师', '團' => '团', '聯' => '联', '準' => '准',
                '備' => '备', '響' => '响', '擔' => '担', '構' => '构', '購' => '购',
                '續' => '续', '達' => '达', '遲' => '迟', '運' => '运', '適' => '适',
                '還' => '还', '遠' => '远', '違' => '违', '連' => '连', '離' => '离',
            ];
        }

        return strtr($text, $map);
    }
}
