<?php

namespace App\Services;

use Illuminate\Support\Facades\Log;

class MathFormulaProcessor
{
    /**
     * 处理数学公式，确保有正确的 LaTeX 标记
     *
     * 优化策略：最小化干预，只修复真正需要修复的问题
     * 1. 检查是否已有正确的 LaTeX 标记，如有则直接返回
     * 2. 只在检测到明显错误时才进行修复
     * 3. 优先保护正确的数学表达式不被破坏
     */
    public static function processFormulas(?string $content): string
    {
        if ($content === null || $content === '') {
            return '';
        }
        $content = (string) $content;

        // 0. 基础清理：解码 HTML 实体
        $decoded = html_entity_decode($content, ENT_QUOTES, 'UTF-8');
        while ($decoded !== $content) {
            $content = $decoded;
            $decoded = html_entity_decode($content, ENT_QUOTES, 'UTF-8');
        }
        $content = trim($content);

        // 0.5 将自定义 <image> 标签转换为标准 <img> 标签
        $content = self::convertImageTags($content);

        // 0.6 规范化几何平行符：AB||CD -> AB∥CD
        // 仅处理大写点位字母场景，避免误伤通用竖线表达式
        $content = self::normalizeParallelSymbol($content);

        // 1. 【关键修复】处理公式内的双反斜杠 -> 单反斜杠
        // 数据库存储时 \sqrt 变成 \\sqrt，需要还原
        $content = self::normalizeBackslashesInDelimiters($content);

        // 2. 如果内容中包含定界符，清理内部 HTML
        if (self::containsDelimiters($content)) {
            $content = self::cleanInsideDelimiters($content);
        }

        // 3. 检测内容类型：纯数学、混合内容还是纯文本
        $contentType = self::detectContentType($content);

        // 4. 根据内容类型采取不同的处理策略
        switch ($contentType) {
            case 'pure_math':
                // 纯数学表达式，如 "4x^2 - 25y^2" 或 "f(x) = x^2 - 4x + 5"
                return self::wrapPureMath($content);

            case 'mixed_content':
                // 混合内容，如 "已知函数 f(x) = x^2 - 4x + 5，求最小值"
                return self::smartWrapMixedContent($content);

            case 'delimited':
                // 已包含定界符的内容（$...$, $$...$$, \(...\), \[...\])
                // cleanInsideDelimiters() 已经清理了内部内容
                // 渲染工作由客户端 KaTeX 或服务端 KatexRenderer 完成
                // 【关键修复】将定界符内的 < > 编码为 HTML 实体，避免被浏览器当作 HTML 标签处理
                return self::encodeAngleBracketsInDelimiters($content);

            case 'plain_text':
            default:
                // 纯文本，不需要处理
                return $content;
        }
    }

    /**
     * 将自定义 <image> 标签转换为标准 <img> 标签
     * 例如：<image src="https://example.com/1.png"/> => <img src="https://example.com/1.png" />
     */
    private static function convertImageTags(string $content): string
    {
        // 匹配 <image src="..." /> 或 <image src="..."></image> 格式
        return preg_replace(
            '/<image\s+src=["\']([^"\']+)["\'](?:\s*\/>|><\/image>)/i',
            '<img src="$1" />',
            $content
        );
    }

    /**
     * 将几何文本中的 ASCII 平行符替换为数学平行符（∥）
     * 例如：AB||CD、AB || CD -> AB∥CD
     */
    private static function normalizeParallelSymbol(string $content): string
    {
        return preg_replace('/(?<!\\\\)([A-Z]{1,4})\s*\|\|\s*([A-Z]{1,4})/', '$1∥$2', $content) ?? $content;
    }

    /**
     * 【新增】将公式定界符内被JSON双重转义的LaTeX命令还原
     * 例如：\\sqrt -> \sqrt, \\frac -> \frac
     * 但保留矩阵换行符 \\ (后面不跟字母的情况)
     */
    private static function normalizeBackslashesInDelimiters(string $content): string
    {
        // 只替换 \\+小写字母 的情况（被JSON转义的LaTeX命令，如 \\sqrt -> \sqrt）
        // 保留 \\+大写字母 的情况（换行符后跟文本，如 \\CD 应保持为 \\CD）
        // 保留 \\+数字 或 \\+空白 的情况（矩阵换行符）
        $fixEscapedCommands = function ($tex) {
            // 保护多行环境中的换行符 \\，避免被误判为 LaTeX 命令
            $placeholder = '__KATEX_BR__';
            $originalTex = $tex;
            $protectedEnvs = [];
            $environments = [
                'cases',
                'aligned',
                'align',
                'align*',
                'array',
                'matrix',
                'pmatrix',
                'bmatrix',
                'vmatrix',
                'Vmatrix',
                'gather',
                'split',
                'eqnarray',
            ];
            foreach ($environments as $env) {
                $pattern = '/\\\\begin\{' . preg_quote($env, '/') . '\}([\s\S]*?)\\\\end\{' . preg_quote($env, '/') . '\}/';
                $tex = preg_replace_callback($pattern, function ($m) use ($env, $placeholder) {
                    $content = str_replace('\\\\', $placeholder, $m[1]);
                    return '\\begin{' . $env . '}' . $content . '\\end{' . $env . '}';
                }, $tex);
                if ($tex !== $originalTex && !in_array($env, $protectedEnvs, true)) {
                    $protectedEnvs[] = $env;
                }
            }

            // \\sqrt -> \sqrt, \\frac -> \frac, 但 \\CD 或 \\2 保持不变
            // 【修复】只匹配小写字母，因为 LaTeX 命令都是小写
            $tex = preg_replace('/\\\\\\\\([a-z])/', '\\\\$1', $tex);

            // 还原多行环境换行
            $tex = str_replace($placeholder, '\\\\', $tex);
            if ($protectedEnvs) {
                Log::debug('MathFormulaProcessor: protected multiline line breaks', [
                    'envs' => $protectedEnvs,
                ]);
            }
            return $tex;
        };

        // 1. 处理 $$...$$ 块级公式
        $content = preg_replace_callback('/\$\$([\s\S]*?)\$\$/', function ($matches) use ($fixEscapedCommands) {
            return '$$'.$fixEscapedCommands($matches[1]).'$$';
        }, $content);

        // 2. 处理 $...$ 行内公式（避免与$$冲突）
        $content = preg_replace_callback('/(?<!\$)\$([^$\n]+?)\$(?!\$)/', function ($matches) use ($fixEscapedCommands) {
            return '$'.$fixEscapedCommands($matches[1]).'$';
        }, $content);

        // 3. 处理 \(...\) 行内公式
        $content = preg_replace_callback('/\\\\\(([\s\S]*?)\\\\\)/', function ($matches) use ($fixEscapedCommands) {
            return '\\('.$fixEscapedCommands($matches[1]).'\\)';
        }, $content);

        // 4. 处理 \[...\] 块级公式
        $content = preg_replace_callback('/\\\\\[([\s\S]*?)\\\\\]/', function ($matches) use ($fixEscapedCommands) {
            return '\\['.$fixEscapedCommands($matches[1]).'\\]';
        }, $content);

        return $content;
    }

    /**
     * 【新增】将定界符内的 < > 编码为 HTML 实体
     * 避免 LaTeX 公式中的 < > 被浏览器当作 HTML 标签处理
     * 例如：$x<4$ 中的 <4 会被浏览器当作无效标签移除
     */
    private static function encodeAngleBracketsInDelimiters(string $content): string
    {
        $encodeInner = function (string $tex): string {
            // 将 < 和 > 编码为 HTML 实体，KaTeX 会正确处理这些实体
            return str_replace(['<', '>'], ['&lt;', '&gt;'], $tex);
        };

        // 1. 处理 $$...$$ 块级公式
        $content = preg_replace_callback('/\$\$([\s\S]*?)\$\$/', function ($matches) use ($encodeInner) {
            return '$$' . $encodeInner($matches[1]) . '$$';
        }, $content);

        // 2. 处理 $...$ 行内公式（避免与$$冲突）
        $content = preg_replace_callback('/(?<!\$)\$([^$\n]+?)\$(?!\$)/', function ($matches) use ($encodeInner) {
            return '$' . $encodeInner($matches[1]) . '$';
        }, $content);

        // 3. 处理 \(...\) 行内公式
        $content = preg_replace_callback('/\\\\\(([\s\S]*?)\\\\\)/', function ($matches) use ($encodeInner) {
            return '\\(' . $encodeInner($matches[1]) . '\\)';
        }, $content);

        // 4. 处理 \[...\] 块级公式
        $content = preg_replace_callback('/\\\\\[([\s\S]*?)\\\\\]/', function ($matches) use ($encodeInner) {
            return '\\[' . $encodeInner($matches[1]) . '\\]';
        }, $content);

        return $content;
    }

    /**
     * 【新增】检查内容中是否包含任意定界符（不要求整个字符串被包裹）
     */
    private static function containsDelimiters(string $content): bool
    {
        // 检查是否包含 $...$, $$...$$, \(...\), \[...\]
        return preg_match('/\$\$[\s\S]*?\$\$|\$[^$\n]+?\$|\\\\\([\s\S]*?\\\\\)|\\\\\[[\s\S]*?\\\\\]/', $content) === 1;
    }

    /**
     * 检测内容类型
     * 优化：加入中文检测，避免包裹包含中文的混合内容
     */
    private static function detectContentType(string $content): string
    {
        // 优先检查是否包含定界符（使用 containsDelimiters 检测混合内容中的公式）
        if (self::containsDelimiters($content)) {
            return 'delimited';
        }

        // 检查是否包含数学特征
        $hasMathFeatures = self::containsMathFeatures($content);

        // 如果不包含数学特征，返回纯文本
        if (! $hasMathFeatures) {
            return 'plain_text';
        }

        // 检查是否包含中文字符
        if (preg_match('/[\x{4e00}-\x{9fa5}]/u', $content)) {
            // 包含中文 + 数学特征 = 混合内容，需要智能提取数学部分
            return 'mixed_content';
        }

        // 检查是否包含长文本（超过一定长度的字母组合）
        $hasLongText = preg_match('/[a-zA-Z]{8,}/', $content);

        if ($hasLongText) {
            // 包含长文本，可能是混合内容，但不包裹（保守策略）
            return 'plain_text';
        }

        // 检查是纯数学还是混合内容
        // 混合内容：同时包含数学特征和普通英文单词
        $hasPlainText = preg_match('/\b[a-zA-Z]{3,7}\b/', $content) &&
                       ! preg_match('/^[a-zA-Z0-9\+\-\*\/\=\s\.\^\(\)\_\{\}]+$/', $content);

        if ($hasPlainText) {
            return 'mixed_content';
        }

        return 'pure_math';
    }

    /**
     * 包裹纯数学表达式
     * 优化：只添加定界符，不修改内容本身
     */
    private static function wrapPureMath(string $content): string
    {
        // 已经是纯数学格式，直接用 $ 包裹
        // 【修复】编码 < > 避免被浏览器当作 HTML 标签
        $encoded = str_replace(['<', '>'], ['&lt;', '&gt;'], $content);
        return '$' . $encoded . '$';
    }

    /**
     * 清理定界符内部的 HTML 标签
     * 【修复】不再使用 strip_tags()，因为它会把 LaTeX 中的 < > 当作标签删除
     * 例如：$x<4$ 中的 <4 会被 strip_tags 误删
     */
    private static function cleanInsideDelimiters(string $content): string
    {
        // 修复：使用更精确的正则表达式，避免模式冲突
        // 只移除真正的 HTML 标签（如 <span>, <br>, </div> 等），保留数学符号 < >

        // 定义安全的 HTML 标签清理函数（只移除真正的 HTML 标签）
        $cleanHtmlTags = function (string $tex): string {
            // 只移除看起来像 HTML 标签的内容（以字母开头的标签）
            // 例如：<span>, </div>, <br/>, 但保留 <4, >0, x<y 等数学表达式
            $tex = preg_replace('/<\/?[a-zA-Z][a-zA-Z0-9]*[^>]*>/', '', $tex);
            // 解码 HTML 实体
            $tex = html_entity_decode($tex, ENT_QUOTES, 'UTF-8');
            return trim($tex);
        };

        // 1. 处理 $$...$$ 显示公式
        $content = preg_replace_callback('/\$\$([\s\S]*?)\$\$/', function ($matches) use ($cleanHtmlTags) {
            return '$$' . $cleanHtmlTags($matches[1]) . '$$';
        }, $content);

        // 2. 处理 \(...\) 行内公式
        $content = preg_replace_callback('/\\\\\(([\s\S]*?)\\\\\)/', function ($matches) use ($cleanHtmlTags) {
            return '\\(' . $cleanHtmlTags($matches[1]) . '\\)';
        }, $content);

        // 3. 处理 \[...\] 显示公式
        $content = preg_replace_callback('/\\\\\[([\s\S]*?)\\\\\]/', function ($matches) use ($cleanHtmlTags) {
            return '\\[' . $cleanHtmlTags($matches[1]) . '\\]';
        }, $content);

        // 4. 最后处理 $...$ 行内公式（避免与$$冲突）
        $content = preg_replace_callback('/(?<!\$)\$([^$\n]+?)\$(?!\$)/', function ($matches) use ($cleanHtmlTags) {
            return '$' . $cleanHtmlTags($matches[1]) . '$';
        }, $content);

        return $content;
    }

    /**
     * 智能识别并包裹富文本中的数学公式
     * 支持：函数定义、导数表达式、LaTeX命令、数学运算
     */
    private static function smartWrapMixedContent(string $content): string
    {
        // 匹配策略：只匹配明确的数学表达式，避免误判
        $tagPattern = '<[^>]+>';
        $existingDelimiterPattern = '(?:\$\$[\s\S]*?\$\$|\$[\s\S]*?\$|\\\\\([\s\S]*?\\\\\)|\\\\\[[\s\S]*?\\\\\])';

        // 数学公式模式（按优先级排列）
        $patterns = [
            // 1. 函数定义: f(x) = 2x^3 - 3x^2 + 4x - 5
            "[a-zA-Z]'?\\([a-zA-Z0-9,\\s]+\\)\\s*=\\s*[a-zA-Z0-9\\+\\-\\*\\/\\^\\s\\.\\(\\)\\_\\{\\}]+",
            // 2. 导数/函数调用: f'(1), g(5), sin(x)
            "[a-zA-Z]+'?\\([a-zA-Z0-9\\+\\-\\*\\/\\^\\s\\.]+\\)",
            // 3. LaTeX 命令: \frac{1}{2}
            '\\\\[a-zA-Z]+\\{[^}]*\\}(?:\\{[^}]*\\})?',
            // 4. 数学表达式: x^2 + y^2, 2x - 3
            '[a-zA-Z0-9]+[\\^_][a-zA-Z0-9\\{\\}]+(?:\\s*[\\+\\-\\*\\/]\\s*[a-zA-Z0-9\\^_\\{\\}\\.]+)*',
        ];

        $mathPattern = '(?:'.implode('|', $patterns).')';
        $pattern = "/($tagPattern)|($existingDelimiterPattern)|($mathPattern)/u";

        return preg_replace_callback($pattern, function ($matches) {
            // HTML 标签，原样返回
            if (! empty($matches[1])) {
                return $matches[1];
            }

            // 已有的定界符，原样返回
            if (! empty($matches[2])) {
                return $matches[2];
            }

            // 数学公式，添加 $ 包裹
            if (! empty($matches[3])) {
                $math = trim($matches[3]);
                // 再次检查是否已经包裹
                if (str_contains($math, '$')) {
                    return $math;
                }

                // 【修复】编码 < > 避免被浏览器当作 HTML 标签
                $encoded = str_replace(['<', '>'], ['&lt;', '&gt;'], $math);
                return '$' . $encoded . '$';
            }

            return $matches[0];
        }, $content);
    }

    /**
     * 检查是否已有定界符
     */
    private static function hasDelimiters(string $content): bool
    {
        $content = trim($content);
        // 检查 $$...$$
        if (str_starts_with($content, '$$') && str_ends_with($content, '$$')) {
            return true;
        }
        // 检查 $...$
        if (str_starts_with($content, '$') && str_ends_with($content, '$')) {
            return true;
        }
        // 检查 \[...\]
        if (str_starts_with($content, '\\[') && str_ends_with($content, '\\]')) {
            return true;
        }
        // 检查 \(...\)
        if (str_starts_with($content, '\\(') && str_ends_with($content, '\\)')) {
            return true;
        }

        return false;
    }

    /**
     * 检测数学特征
     * 优化：更精确的检测，减少误判
     */
    private static function containsMathFeatures(string $content): bool
    {
        // 1. 检查是否有 LaTeX 命令
        if (preg_match('/\\\\[a-zA-Z]+\{?/', $content)) {
            return true;
        }

        // 2. 检查函数定义或等式（如 f(x) =, g(x) =）
        // 必须是：字母+括号+等号+数学内容
        if (preg_match('/[a-zA-Z]\([a-zA-Z0-9,\s]+\)\s*=\s*[a-zA-Z0-9\+\-\*\/\^\.\(\)\s\\\\_\{]+/', $content)) {
            return true;
        }

        // 3. 检查纯数学表达式（只包含数字、变量、运算符、括号）
        // 严格的数学表达式：必须包含字母和运算符，且没有中文字符
        if (preg_match('/^[a-zA-Z0-9\+\-\*\/\=\s\.\^\(\)\_\{\}]+$/', $content) &&
            preg_match('/[a-zA-Z]/', $content) &&
            preg_match('/[\+\-\*\/\=\^]/', $content)) {
            return true;
        }

        // 4. 检查包含变量的数学表达式（带约束）
        // 必须有明确的运算符连接，且周围是数学内容
        if (preg_match('/[a-zA-Z0-9\.\^\_\{\}]\s*[\+\-\*\/]\s*[a-zA-Z0-9\.\^\_\{\}\(\)]/', $content)) {
            return true;
        }

        // 5. 检查分数形式（如 \frac{}{}）
        if (preg_match('/\\\\frac\{/', $content)) {
            return true;
        }

        // 6. 检查上标或下标（仅当与数字/字母组合时）
        if (preg_match('/[a-zA-Z0-9]\s*[\^_]\s*[a-zA-Z0-9]/', $content)) {
            return true;
        }

        return false;
    }

    /**
     * 批量处理
     */
    public static function processArray(array $data, array $fieldsToProcess): array
    {
        foreach ($data as $key => &$value) {
            if (in_array($key, $fieldsToProcess)) {
                if (is_string($value)) {
                    $value = self::processFormulas($value);
                } elseif (is_array($value)) {
                    // 【修复】当字段在处理列表中且值是数组时（如 options），处理数组中的每个字符串元素
                    $value = self::processArrayValues($value);
                }
            } elseif (is_array($value)) {
                $value = self::processArray($value, $fieldsToProcess);
            }
        }

        return $data;
    }

    /**
     * 【新增】递归处理数组中的所有字符串值
     * 用于处理 options 等数组类型的字段
     */
    private static function processArrayValues(array $arr): array
    {
        foreach ($arr as $key => &$value) {
            if (is_string($value)) {
                $value = self::processFormulas($value);
            } elseif (is_array($value)) {
                $value = self::processArrayValues($value);
            }
        }

        return $arr;
    }

    /**
     * 处理题目数据
     */
    public static function processQuestionData(array $question): array
    {
        $fieldsToProcess = [
            'stem', 'content', 'question_text', 'answer',
            'correct_answer', 'student_answer', 'explanation',
            'solution', 'question_content', 'options',
        ];

        // 学情报告等场景：题干已按判卷口径完整处理（含 processFormulas），避免二次处理
        if (! empty($question['question_text_preprocessed'])) {
            $fieldsToProcess = array_values(array_diff($fieldsToProcess, ['question_text']));
        }
        unset($question['question_text_preprocessed']);

        return self::processArray($question, $fieldsToProcess);
    }

    /**
     * 修复被污染的数学公式（包含重复的转义字符）
     */
    private static function fixCorruptedFormulas(string $content): string
    {
        // 简化的修复策略，只处理明确的问题

        // 1. 将超过2个连续的$符号减少为2个
        $content = preg_replace('/\${3,}/', '$$', $content);

        // 2. 修复$$B . - \frac{1}{2}$$ 这种格式，在选项前加空格
        $content = preg_replace('/\$\$([A-Z])\s*\.\s*/', '$$ $1. ', $content);

        // 3. 修复不完整的frac命令：\frac{1}{2} -> \frac{1}{2}
        $content = preg_replace('/\\\\frac\\\\({[^}]+)([^}]*)\\\\/', '\\\\frac$1}{$2}', $content);

        // 4. 移除孤立的反斜杠（在非LaTeX命令前的）
        $content = preg_replace('/\\\\(?![a-zA-Z{])/', '', $content);

        return $content;
    }
}
