/** 题目 HTML 处理（与学生端 doWork/Question 保持一致） */

const LATEX_RENDER_URL = 'https://latex.codecogs.com/svg.image?';
const MAX_FORMAT_CACHE_SIZE = 200;
const formatCache = new Map<string, string>();

const escapeHtml = (text: string) =>
    text
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;');

const extractTagAttr = (tag: string, name: string): string => {
    const re = new RegExp(`\\b${name}\\s*=\\s*["']((?:\\\\.|[^"'])*)["']`, 'i');
    const match = tag.match(re);
    if (!match) return '';
    return match[1].replace(/\\\\/g, '\\').replace(/\\"/g, '"').replace(/\\'/g, "'");
};

const stripHtmlTags = (value: string) => String(value || '').replace(/<[^>]+>/g, '');

const decodeHtmlEntities = (value: string) =>
    String(value || '')
        .replace(/&nbsp;/gi, ' ')
        .replace(/&lt;/gi, '<')
        .replace(/&gt;/gi, '>')
        .replace(/&quot;/gi, '"')
        .replace(/&#39;/gi, "'")
        .replace(/&amp;/gi, '&');

const resolveFormulaSource = (openTag: string, innerHtml = ''): string => {
    const byAttr = extractTagAttr(openTag, 'data-value') || extractTagAttr(openTag, 'data-latex');
    if (String(byAttr || '').trim()) return byAttr;
    const innerText = decodeHtmlEntities(stripHtmlTags(innerHtml)).trim();
    return innerText;
};

const trimLatexWrapper = (value: string) => String(value || '').trim().replace(/^\$+/, '').replace(/\$+$/, '');

const renderMathTag = (openTag: string, innerHtml: string): string => {
    const latexRaw = decodeHtmlEntities(extractTagAttr(openTag, 'latex'));
    const latex = trimLatexWrapper(latexRaw);
    if (latex) {
        return renderFormulaContent(latex);
    }

    const svgUrlRaw = extractTagAttr(openTag, 'math-svg') || extractTagAttr(openTag, 'src');
    const svgUrl = String(svgUrlRaw || '').trim().replace(/http:\/\//gi, 'https://');
    if (svgUrl) {
        return `<img class="formula-img" src="${svgUrl}" style="height:1.4em;vertical-align:middle;" />`;
    }

    const text = decodeHtmlEntities(stripHtmlTags(innerHtml)).trim();
    return renderFormulaContent(text);
};

const processMathTags = (html: string): string => {
    if (!html || !/<math\b/i.test(html)) return html;
    return html.replace(/<math\b[^>]*>[\s\S]*?<\/math>/gi, (tag) => {
        const openTag = tag.match(/^<math\b[^>]*>/i)?.[0] || '';
        const innerHtml = tag.replace(/^<math\b[^>]*>/i, '').replace(/<\/math>\s*$/i, '');
        return renderMathTag(openTag, innerHtml);
    });
};

const findMatchingSpanClose = (html: string, fromIndex: number): number => {
    let depth = 1;
    const tagRe = /<(\/?)span\b[^>]*>/gi;
    tagRe.lastIndex = fromIndex;
    let match;
    while ((match = tagRe.exec(html)) !== null) {
        depth += match[1] === '/' ? -1 : 1;
        if (depth === 0) {
            return match.index + match[0].length;
        }
    }
    return -1;
};

const isPlainNumericFormula = (formula: string) =>
    /^[\-\u2212\uFF0D0-9.+\s(),]+$/.test(formula.trim());

const renderFormulaContent = (formula: string): string => {
    const trimmed = formula.trim();
    if (!trimmed) return '';
    if (isPlainNumericFormula(trimmed)) {
        return escapeHtml(trimmed);
    }
    if (isSimpleFormula(trimmed)) {
        return simpleFormulaToHtml(trimmed);
    }
    const imageUrl = latexToImageUrl(trimmed);
    return `<img class="formula-img" src="${imageUrl}" style="height:1.4em;vertical-align:middle;" referrerpolicy="no-referrer" loading="lazy" onerror="this.onerror=null;this.outerHTML='<span class=&quot;formula-fallback&quot;>${escapeHtml(trimmed)}</span>';" />`;
};

const replaceFormulaSpansInHtml = (html: string): string => {
    if (!html || !html.includes('data-w-e-type')) return html;

    const openRe = /<span\b[^>]*\bdata-w-e-type\s*=\s*["']formula["'][^>]*>/gi;
    let result = '';
    let cursor = 0;
    let match;
    let guard = 0;

    while ((match = openRe.exec(html)) !== null) {
        if (guard++ > 500) break;
        result += html.slice(cursor, match.index);
        const openTag = match[0];
        const contentStart = match.index + openTag.length;
        const closeEnd = findMatchingSpanClose(html, contentStart);

        if (closeEnd === -1) {
            const formula = resolveFormulaSource(openTag);
            result += renderFormulaContent(formula);
            cursor = match.index + openTag.length;
            openRe.lastIndex = cursor;
            continue;
        }

        const innerHtml = html.slice(contentStart, closeEnd - '</span>'.length);
        const formula = resolveFormulaSource(openTag, innerHtml);
        result += renderFormulaContent(formula);
        cursor = closeEnd;
        openRe.lastIndex = closeEnd;
    }

    result += html.slice(cursor);
    return result;
};

const cleanupFormulaSpanRemnants = (html: string): string => {
    if (!html) return '';
    let result = html;
    let prev = '';
    let guard = 0;
    while (result !== prev && guard < 8) {
        prev = result;
        result = replaceFormulaSpansInHtml(result);
        guard += 1;
    }
    return result
        .replace(/<span\b[^>]*\bdata-w-e-type\s*=\s*["']formula["'][^>]*>[\s\S]*?<\/span>/gi, (tag) => {
            const openTag = tag.match(/^<span\b[^>]*>/i)?.[0] || '';
            const innerHtml = tag.replace(/^<span\b[^>]*>/i, '').replace(/<\/span>\s*$/i, '');
            const formula = resolveFormulaSource(openTag, innerHtml);
            return renderFormulaContent(formula);
        })
        .replace(/\sdata-w-e-type\s*=\s*["'][^"']*["']/gi, '')
        .replace(/\sdata-value\s*=\s*["'][^"']*["']/gi, '')
        .replace(/\sdata-size\s*=\s*["'][^"']*["']/gi, '')
        .replace(/\scontenteditable\s*=\s*["'][^"']*["']/gi, '')
        .replace(/<span\s*><\/span>/gi, '');
};

export const fixMathSymbolsInHtml = (html: string): string => {
    if (html == null || typeof html !== 'string') return '';
    let t = html;
    t = t.replace(/\uFE63/g, '\u2212');
    t = t.replace(/<(?![a-zA-Z/!])/g, '&lt;');
    t = t.replace(/ > /g, ' &gt; ');
    t = t.replace(/AUB/g, 'A∪B');
    return t;
};

const latexToImageUrl = (latex: string): string => {
    if (!latex) return '';
    let formula = latex.trim();

    const punctuationMap: Record<string, string> = {
        '，': ',', '。': '.', '：': ':', '；': ';', '！': '!', '？': '?',
        '（': '(', '）': ')', '【': '[', '】': ']', '｛': '{', '｝': '}',
        '《': '<', '》': '>',
        "\u201c": '"', "\u201d": '"', "\u2018": "'", "\u2019": "'",
        '＋': '+', '－': '-', '\u2212': '-', '×': '*', '÷': '/',
        '＝': '=', '＜': '<', '＞': '>', '％': '%', '＆': '&',
        '＊': '*', '＠': '@', '＃': '#', '＄': '$', '＾': '^',
        '～': '~', '｜': '|', '＼': '\\', '／': '/', '　': ' ',
    };
    for (const [cn, en] of Object.entries(punctuationMap)) {
        formula = formula.split(cn).join(en);
    }

    const mathSymbolMap: Record<string, string> = {
        '∈': ' \\in ', '∉': ' \\notin ', '⊂': ' \\subset ', '⊃': ' \\supset ',
        '⊆': ' \\subseteq ', '⊇': ' \\supseteq ', '∪': ' \\cup ', '∩': ' \\cap ',
        '∅': ' \\emptyset ',
        '≤': ' \\leq ', '≥': ' \\geq ', '≠': ' \\neq ', '≈': ' \\approx ',
        '≡': ' \\equiv ', '∝': ' \\propto ',
        '±': ' \\pm ', '∓': ' \\mp ', '×': ' \\times ', '÷': ' \\div ',
        '·': ' \\cdot ', '∘': ' \\circ ',
        'α': '\\alpha ', 'β': '\\beta ', 'γ': '\\gamma ', 'δ': '\\delta ',
        'ε': '\\varepsilon ', 'θ': '\\theta ', 'λ': '\\lambda ', 'μ': '\\mu ',
        'σ': '\\sigma ', 'φ': '\\varphi ', 'ω': '\\omega ',
        'Δ': '\\Delta ', 'Σ': '\\Sigma ', 'Ω': '\\Omega ',
        '∞': '\\infty ', '∂': '\\partial ', '∇': '\\nabla ', '∫': '\\int ',
        '∑': '\\sum ', '∏': '\\prod ', '√': '\\sqrt ', '∠': '\\angle ',
        '⊥': '\\perp ', '∥': '\\parallel ',
        '→': '\\to ', '⇒': '\\Rightarrow ', '⇔': '\\Leftrightarrow ',
        '∀': '\\forall ', '∃': '\\exists ', '¬': '\\neg ',
        '∧': '\\land ', '∨': '\\lor ',
    };
    for (const [symbol, latexCmd] of Object.entries(mathSymbolMap)) {
        formula = formula.split(symbol).join(latexCmd);
    }

    formula = formula.replace(/\b([RNZQC])\+/g, '$1^+');
    formula = formula.replace(/\b([RNZQC])\*/g, '$1^*');
    formula = formula
        .replace(/\\overset\s*\{\s*\\rightarrow\s*\}\s*\{([^}]+)\}/g, '\\vec{$1}')
        .replace(/\\text\s*\{\s*π\s*\}/g, '\\pi')
        .replace(/\\text\s*\{\s*sin\s*\}/gi, '\\sin')
        .replace(/\\text\s*\{\s*cos\s*\}/gi, '\\cos')
        .replace(/\\text\s*\{\s*tan\s*\}/gi, '\\tan')
        .replace(/\\text\s*\{\s*log\s*\}/gi, '\\log')
        .replace(/π/g, '\\pi');

    const encoded = encodeURIComponent(formula);
    return `${LATEX_RENDER_URL}\\inline&space;\\dpi{200}&space;${encoded}`;
};

const isSimpleFormula = (formula: string): boolean => {
    const complexPatterns = [
        /\\frac\b/, /\\sqrt\b/, /\\sum\b/, /\\int\b/, /\\prod\b/, /\\lim\b/,
        /\\vec\b/, /\\overline\b/, /\\underline\b/, /\\overset\b/, /\\underset\b/,
        /\\binom\b/, /\\matrix\b/, /\\begin\b/,
        /\^{[^}]+}/, /_{[^}]+}/,
    ];
    return !complexPatterns.some((pattern) => pattern.test(formula));
};

const simpleFormulaToHtml = (formula: string): string => {
    let text = formula;
    text = text.replace(/\\text\s*\{([^}]*)\}/g, '$1');

    const greekMap: Record<string, string> = {
        '\\alpha': 'α', '\\beta': 'β', '\\gamma': 'γ', '\\delta': 'δ',
        '\\epsilon': 'ε', '\\varepsilon': 'ε', '\\theta': 'θ', '\\lambda': 'λ',
        '\\mu': 'μ', '\\pi': 'π', '\\sigma': 'σ', '\\phi': 'φ', '\\varphi': 'φ',
        '\\omega': 'ω', '\\Delta': 'Δ', '\\Sigma': 'Σ', '\\Omega': 'Ω',
    };
    for (const [latex, symbol] of Object.entries(greekMap)) {
        text = text.split(latex).join(symbol);
    }

    text = text.replace(/\\sin\b/g, 'sin');
    text = text.replace(/\\cos\b/g, 'cos');
    text = text.replace(/\\tan\b/g, 'tan');
    text = text.replace(/\\log\b/g, 'log');
    text = text.replace(/\\ln\b/g, 'ln');
    text = text.replace(/\\infty\b/g, '∞');
    text = text.replace(/\\pm\b/g, '±');
    text = text.replace(/\\times\b/g, '×');
    text = text.replace(/\\div\b/g, '÷');
    text = text.replace(/\\le\b/g, '≤');
    text = text.replace(/\\leq\b/g, '≤');
    text = text.replace(/\\ge\b/g, '≥');
    text = text.replace(/\\geq\b/g, '≥');
    text = text.replace(/\\neq\b/g, '≠');
    text = text.replace(/\\in\b/g, '∈');
    text = text.replace(/\\subset\b/g, '⊂');
    text = text.replace(/\\cup\b/g, '∪');
    text = text.replace(/\\cap\b/g, '∩');
    text = text.replace(/\\emptyset\b/g, '∅');
    text = text.replace(/\\cdot\b/g, '·');
    text = text.replace(/\\to\b/g, '→');

    const superscriptMap: Record<string, string> = {
        '0': '⁰', '1': '¹', '2': '²', '3': '³', '4': '⁴',
        '5': '⁵', '6': '⁶', '7': '⁷', '8': '⁸', '9': '⁹',
        '+': '⁺', '-': '⁻', 'n': 'ⁿ',
    };
    const subscriptMap: Record<string, string> = {
        '0': '₀', '1': '₁', '2': '₂', '3': '₃', '4': '₄',
        '5': '₅', '6': '₆', '7': '₇', '8': '₈', '9': '₉',
        '+': '₊', '-': '₋', 'n': 'ₙ', 'i': 'ᵢ', 'k': 'ₖ',
    };
    text = text.replace(/\^([0-9n+\-])/g, (_, char) => superscriptMap[char] || `^${char}`);
    text = text.replace(/_([0-9nik+\-])/g, (_, char) => subscriptMap[char] || `_${char}`);
    text = text.replace(/\\left\s*/g, '');
    text = text.replace(/\\right\s*/g, '');
    text = text.replace(/\\/g, '');

    return `<span class="formula-text" style="font-style:italic;">${text}</span>`;
};

const processDollarLatex = (text: string): string => {
    if (!text || !text.includes('$')) return text;
    return text.replace(/\$(?!\$)([\s\S]+?)\$(?!\$)/g, (_match, formula: string) => {
        const trimmed = String(formula || '').trim();
        if (!trimmed) return _match;
        if (isSimpleFormula(trimmed)) {
            return simpleFormulaToHtml(trimmed);
        }
        const imageUrl = latexToImageUrl(trimmed);
        return `<img class="formula-img" src="${imageUrl}" style="height:1.4em;vertical-align:middle;" referrerpolicy="no-referrer" loading="lazy" onerror="this.onerror=null;this.outerHTML='<span class=&quot;formula-fallback&quot;>${escapeHtml(trimmed)}</span>';" />`;
    });
};

export const processFormula = (html: string): string => {
    if (html == null || typeof html !== 'string') return '';
    if (!html) return '';
    return cleanupFormulaSpanRemnants(html);
};

export const isHtmlContent = (value: string) => /<[a-z][\s\S]*>/i.test(value);

export const formatQuestionHtml = (title?: string, options?: { stripImages?: boolean }) => {
    if (title == null || title === '') return '';
    const cacheKey = `${options?.stripImages ? '1' : '0'}::${String(title)}`;
    const cached = formatCache.get(cacheKey);
    if (cached != null) return cached;
    try {
        let t = String(title);
        if (options?.stripImages) {
            t = t.replace(/<img(?![^>]*formula-img)[^>]*\/?>/gi, '');
        }
        t = t.replace(/<u><\/u>/gi, '<u class="fill-blank"></u>');
        t = t.replace(/http:\/\//gi, 'https://');
        t = processMathTags(t);
        t = cleanupFormulaSpanRemnants(t);
        t = processDollarLatex(t);
        t = fixMathSymbolsInHtml(t);
        if (formatCache.size >= MAX_FORMAT_CACHE_SIZE) {
            const firstKey = formatCache.keys().next().value;
            if (firstKey) formatCache.delete(firstKey);
        }
        formatCache.set(cacheKey, t);
        return t;
    } catch (err) {
        console.error('[questionHtml] format failed', err);
        const fallback = String(title)
            .replace(/http:\/\//gi, 'https://')
            .replace(/<(?![a-zA-Z/!])[^>]*>/g, '')
            .replace(/<(?![a-zA-Z/!])/g, '&lt;');
        if (formatCache.size >= MAX_FORMAT_CACHE_SIZE) {
            const firstKey = formatCache.keys().next().value;
            if (firstKey) formatCache.delete(firstKey);
        }
        formatCache.set(cacheKey, fallback);
        return fallback;
    }
};

/** 错题详情：与 Web 一致优先保留原始 HTML，仅做 rich-text 兼容转换 */
export const formatWrongQuestionHtml = (title?: string, options?: { stripImages?: boolean }) =>
    formatQuestionHtml(title, options);

export const getQuestionStemSource = (item: Record<string, unknown>) => {
    const pid = item.pidTitle ? String(item.pidTitle) : '';
    const stem = String(item.stemHtml || item.titleMu || item.title || item.stem || '');
    if (pid && stem) return `${pid}${stem}`;
    return pid || stem;
};

export const getQuestionAnswerSource = (item: Record<string, unknown>) =>
    String(item.answerMu ?? item.answer ?? '');

const IMAGE_URL_PATTERN = /\.(jpg|jpeg|png|gif|webp|bmp)(\?.*)?$/i;

export const isImageUrl = (url: string) => {
    const value = String(url || '').trim();
    if (!value) return false;
    if (IMAGE_URL_PATTERN.test(value)) return true;
    return /(?:cos\.|myqcloud|oss-|\/temp\/|\/upload)/i.test(value);
};

export const parseImageUrls = (raw: unknown) => {
    const urls = String(raw || '')
        .split(',')
        .map((item) => item.replace(/[`\s]/g, '').replace(/http:\/\//gi, 'https://').trim())
        .filter(Boolean);
    return urls.filter(isImageUrl);
};

export const extractAnswerImages = (item: Record<string, unknown>) => {
    const images = new Set<string>();
    parseImageUrls(item.submitAnswerPic).forEach((url) => images.add(url));

    const answer = String(item.submitAnswer || '');
    const links = answer.match(/https?:\/\/[^\s,]+/gi) || [];
    links.forEach((link) => {
        const url = link.replace(/http:\/\//gi, 'https://').trim();
        if (isImageUrl(url)) images.add(url);
    });

    return Array.from(images);
};

export const extractTitleImages = (html: string) => {
    if (!html) return [] as string[];
    const images: string[] = [];
    const imgRegex = /<img[^>]*\s*src=["']([^"']+)["'][^>]*\/?>/gi;
    let match;
    while ((match = imgRegex.exec(html)) !== null) {
        if (/formula-img/i.test(match[0])) continue;
        const url = (match[1] || '').trim().replace(/http:\/\//gi, 'https://');
        if (url) images.push(url);
    }
    return images;
};
