import { visibleWidth } from "./utils.js";
const SYMBOLS = {
    alpha: "α",
    beta: "β",
    gamma: "γ",
    delta: "δ",
    epsilon: "ϵ",
    varepsilon: "ε",
    zeta: "ζ",
    eta: "η",
    theta: "θ",
    vartheta: "ϑ",
    iota: "ι",
    kappa: "κ",
    varkappa: "ϰ",
    lambda: "λ",
    mu: "μ",
    nu: "ν",
    xi: "ξ",
    pi: "π",
    varpi: "ϖ",
    rho: "ρ",
    varrho: "ϱ",
    sigma: "σ",
    varsigma: "ς",
    tau: "τ",
    upsilon: "υ",
    phi: "ϕ",
    varphi: "φ",
    chi: "χ",
    psi: "ψ",
    omega: "ω",
    Gamma: "Γ",
    Delta: "Δ",
    Theta: "Θ",
    Lambda: "Λ",
    Xi: "Ξ",
    Pi: "Π",
    Sigma: "Σ",
    Upsilon: "Υ",
    Phi: "Φ",
    Psi: "Ψ",
    Omega: "Ω",
    pm: "±",
    mp: "∓",
    times: "×",
    div: "÷",
    cdot: "·",
    ast: "∗",
    star: "⋆",
    circ: "∘",
    bullet: "•",
    oplus: "⊕",
    ominus: "⊖",
    otimes: "⊗",
    oslash: "⊘",
    odot: "⊙",
    bigcirc: "○",
    dagger: "†",
    ddagger: "‡",
    amalg: "⨿",
    uplus: "⊎",
    sqcap: "⊓",
    sqcup: "⊔",
    triangleleft: "◁",
    triangleright: "▷",
    wr: "≀",
    cap: "∩",
    cup: "∪",
    bigcap: "⋂",
    bigcup: "⋃",
    bigwedge: "⋀",
    bigvee: "⋁",
    bigsqcup: "⨆",
    biguplus: "⨄",
    bigoplus: "⨁",
    bigotimes: "⨂",
    bigodot: "⨀",
    setminus: "∖",
    in: "∈",
    notin: "∉",
    ni: "∋",
    subset: "⊂",
    supset: "⊃",
    subseteq: "⊆",
    supseteq: "⊇",
    sqsubset: "⊏",
    sqsupset: "⊐",
    sqsubseteq: "⊑",
    sqsupseteq: "⊒",
    prec: "≺",
    preceq: "≼",
    succ: "≻",
    succeq: "≽",
    ll: "≪",
    gg: "≫",
    le: "≤",
    leq: "≤",
    leqslant: "≤",
    ge: "≥",
    geq: "≥",
    geqslant: "≥",
    ne: "≠",
    neq: "≠",
    equiv: "≡",
    approx: "≈",
    sim: "∼",
    simeq: "≃",
    cong: "≅",
    asymp: "≍",
    doteq: "≐",
    propto: "∝",
    parallel: "∥",
    perp: "⊥",
    mid: "∣",
    vdash: "⊢",
    dashv: "⊣",
    models: "⊨",
    Vdash: "⊩",
    Vvdash: "⊪",
    nvdash: "⊬",
    nvDash: "⊭",
    forall: "∀",
    exists: "∃",
    nexists: "∄",
    neg: "¬",
    land: "∧",
    wedge: "∧",
    lor: "∨",
    vee: "∨",
    to: "→",
    rightarrow: "→",
    longrightarrow: "→",
    leftarrow: "←",
    longleftarrow: "←",
    gets: "←",
    leftrightarrow: "↔",
    longleftrightarrow: "↔",
    hookleftarrow: "↩",
    hookrightarrow: "↪",
    twoheadleftarrow: "↞",
    twoheadrightarrow: "↠",
    leftharpoonup: "↼",
    leftharpoondown: "↽",
    rightharpoonup: "⇀",
    rightharpoondown: "⇁",
    rightleftharpoons: "⇌",
    leftrightharpoons: "⇋",
    nearrow: "↗",
    searrow: "↘",
    swarrow: "↙",
    nwarrow: "↖",
    rightsquigarrow: "⇝",
    leadsto: "⇝",
    Rightarrow: "⇒",
    Longrightarrow: "⇒",
    Leftarrow: "⇐",
    Longleftarrow: "⇐",
    Leftrightarrow: "⇔",
    Longleftrightarrow: "⇔",
    implies: "⇒",
    iff: "⇔",
    mapsto: "↦",
    longmapsto: "↦",
    uparrow: "↑",
    downarrow: "↓",
    partial: "∂",
    nabla: "∇",
    int: "∫",
    iint: "∬",
    iiint: "∭",
    oint: "∮",
    sum: "∑",
    prod: "∏",
    coprod: "∐",
    infty: "∞",
    emptyset: "∅",
    varnothing: "∅",
    angle: "∠",
    therefore: "∴",
    because: "∵",
    aleph: "ℵ",
    beth: "ℶ",
    gimel: "ℷ",
    daleth: "ℸ",
    top: "⊤",
    bot: "⊥",
    triangle: "△",
    square: "□",
    lozenge: "◊",
    checkmark: "✓",
    complement: "∁",
    wp: "℘",
    prime: "′",
    ldots: "…",
    dots: "…",
    cdots: "⋯",
    vdots: "⋮",
    ddots: "⋱",
    ell: "ℓ",
    hbar: "ℏ",
    Im: "ℑ",
    Re: "ℜ",
    langle: "⟨",
    rangle: "⟩",
    vert: "|",
    lvert: "|",
    rvert: "|",
    Vert: "‖",
    lVert: "‖",
    rVert: "‖",
    lbrace: "{",
    rbrace: "}",
    backslash: "\\",
    lfloor: "⌊",
    rfloor: "⌋",
    lceil: "⌈",
    rceil: "⌉",
    colon: ":",
};
const NAMED_OPERATORS = new Set([
    "arccos",
    "arcsin",
    "arctan",
    "arg",
    "cos",
    "cosh",
    "cot",
    "coth",
    "csc",
    "deg",
    "det",
    "dim",
    "exp",
    "gcd",
    "hom",
    "inf",
    "ker",
    "lg",
    "lim",
    "liminf",
    "limsup",
    "ln",
    "log",
    "max",
    "min",
    "Pr",
    "sec",
    "sin",
    "sinh",
    "sup",
    "tan",
    "tanh",
]);
const LIMIT_OPERATORS = new Set([
    "argmax",
    "argmin",
    "inf",
    "injlim",
    "lim",
    "liminf",
    "limsup",
    "max",
    "min",
    "projlim",
    "sup",
]);
const DISPLAY_LIMIT_SYMBOLS = new Set([
    "bigcap",
    "bigcup",
    "bigodot",
    "bigoplus",
    "bigotimes",
    "bigsqcup",
    "biguplus",
    "bigvee",
    "bigwedge",
    "coprod",
    "int",
    "iint",
    "iiint",
    "oint",
    "prod",
    "sum",
]);
const RELATION_COMMANDS = new Set([
    "Leftarrow",
    "Leftrightarrow",
    "Longleftarrow",
    "Longleftrightarrow",
    "Longrightarrow",
    "Rightarrow",
    "Vdash",
    "Vvdash",
    "approx",
    "asymp",
    "cong",
    "dashv",
    "doteq",
    "downarrow",
    "equiv",
    "ge",
    "geq",
    "geqslant",
    "gets",
    "gg",
    "hookleftarrow",
    "hookrightarrow",
    "iff",
    "implies",
    "in",
    "leadsto",
    "le",
    "leftarrow",
    "leftharpoondown",
    "leftharpoonup",
    "leftrightarrow",
    "leftrightharpoons",
    "leq",
    "leqslant",
    "ll",
    "longleftarrow",
    "longleftrightarrow",
    "longmapsto",
    "longrightarrow",
    "mapsto",
    "mid",
    "models",
    "ne",
    "nearrow",
    "neq",
    "ni",
    "notin",
    "nvdash",
    "nvDash",
    "nwarrow",
    "parallel",
    "perp",
    "prec",
    "preceq",
    "propto",
    "rightharpoondown",
    "rightharpoonup",
    "rightleftharpoons",
    "rightarrow",
    "rightsquigarrow",
    "searrow",
    "sim",
    "simeq",
    "sqsubset",
    "sqsubseteq",
    "sqsupset",
    "sqsupseteq",
    "subset",
    "subseteq",
    "succ",
    "succeq",
    "supset",
    "supseteq",
    "swarrow",
    "to",
    "triangleleft",
    "triangleright",
    "twoheadleftarrow",
    "twoheadrightarrow",
    "uparrow",
    "vdash",
]);
const NEGATED_SYMBOLS = {
    "<": "≮",
    ">": "≯",
    "=": "≠",
    "∈": "∉",
    "∋": "∌",
    "∣": "∤",
    "∥": "∦",
    "∼": "≁",
    "≃": "≄",
    "≅": "≇",
    "≈": "≉",
    "≡": "≢",
    "≤": "≰",
    "≥": "≱",
    "≺": "⊀",
    "≻": "⊁",
    "⊂": "⊄",
    "⊃": "⊅",
    "⊆": "⊈",
    "⊇": "⊉",
    "⊢": "⊬",
    "⊨": "⊭",
    "↔": "↮",
    "←": "↚",
    "→": "↛",
    "⇒": "⇏",
    "⇐": "⇍",
    "⇔": "⇎",
    "≼": "⋠",
    "≽": "⋡",
};
const BLACKBOARD = {
    C: "ℂ",
    H: "ℍ",
    N: "ℕ",
    P: "ℙ",
    Q: "ℚ",
    R: "ℝ",
    Z: "ℤ",
};
const SUPERSCRIPTS = {
    "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: "ᵖ",
    r: "ʳ",
    s: "ˢ",
    t: "ᵗ",
    u: "ᵘ",
    v: "ᵛ",
    w: "ʷ",
    x: "ˣ",
    y: "ʸ",
    z: "ᶻ",
};
const SUBSCRIPTS = {
    "0": "₀",
    "1": "₁",
    "2": "₂",
    "3": "₃",
    "4": "₄",
    "5": "₅",
    "6": "₆",
    "7": "₇",
    "8": "₈",
    "9": "₉",
    "+": "₊",
    "-": "₋",
    "=": "₌",
    "(": "₍",
    ")": "₎",
    a: "ₐ",
    e: "ₑ",
    h: "ₕ",
    i: "ᵢ",
    j: "ⱼ",
    k: "ₖ",
    l: "ₗ",
    m: "ₘ",
    n: "ₙ",
    o: "ₒ",
    p: "ₚ",
    r: "ᵣ",
    s: "ₛ",
    t: "ₜ",
    u: "ᵤ",
    v: "ᵥ",
    x: "ₓ",
};
const SPACING_COMMANDS = new Set([
    ",",
    ":",
    ";",
    " ",
    ">",
    "enspace",
    "enskip",
    "medspace",
    "quad",
    "qquad",
    "thickspace",
    "thinspace",
]);
const NEGATIVE_SPACING_COMMANDS = new Set(["!", "negmedspace", "negthickspace", "negthinspace"]);
const NEGATIVE_SPACE = "\u0000";
const IGNORED_COMMANDS = new Set([
    "displaystyle",
    "limits",
    "nolimits",
    "scriptstyle",
    "scriptscriptstyle",
    "textstyle",
]);
const SIZE_COMMANDS = new Set([
    "big",
    "Big",
    "bigg",
    "Bigg",
    "bigl",
    "Bigl",
    "biggl",
    "Biggl",
    "bigr",
    "Bigr",
    "biggr",
    "Biggr",
]);
const PLAIN_WRAPPERS = new Set([
    "emph",
    "mathcal",
    "mathbf",
    "mathfrak",
    "mathit",
    "mathrm",
    "mathnormal",
    "mathscr",
    "mathsf",
    "mathtt",
    "mathup",
    "mbox",
    "overbrace",
    "pmb",
    "smash",
    "substack",
    "text",
    "textbf",
    "textit",
    "textmd",
    "textnormal",
    "textrm",
    "textsc",
    "textsf",
    "textsl",
    "texttt",
    "textup",
    "underbrace",
    "bm",
    "boldsymbol",
]);
const ACCENTS = {
    acute: "\u0301",
    bar: "\u0305",
    breve: "\u0306",
    check: "\u030c",
    ddot: "\u0308",
    dot: "\u0307",
    grave: "\u0300",
    hat: "\u0302",
    mathring: "\u030a",
    overleftarrow: "\u20d6",
    overleftrightarrow: "\u20e1",
    overline: "\u0305",
    overrightarrow: "\u20d7",
    tilde: "\u0303",
    underline: "\u0332",
    vec: "\u20d7",
    widehat: "\u0302",
    widetilde: "\u0303",
};
function replaceCharacters(value, replacements) {
    let result = "";
    for (const character of value) {
        const replacement = replacements[character];
        if (replacement === undefined) {
            return undefined;
        }
        result += replacement;
    }
    return result;
}
function formatScript(value, kind) {
    value = value.trim();
    const replacements = kind === "sub" ? SUBSCRIPTS : SUPERSCRIPTS;
    const unicode = replaceCharacters(value.replace(/\s*([=+-])\s*/g, "$1"), replacements);
    if (unicode !== undefined) {
        return unicode;
    }
    const prefix = kind === "sub" ? "_" : "^";
    if (Array.from(value).length === 1 || (kind === "sub" && /^[A-Za-z]+$/.test(value))) {
        return `${prefix}${value}`;
    }
    return `${prefix}(${value})`;
}
function formatFraction(numerator, denominator) {
    numerator = numerator.trim();
    denominator = denominator.trim();
    const simpleNumerator = /^[\p{L}\p{N}.]+$/u.test(numerator);
    const simpleDenominator = /^[\p{N}.]+$/u.test(denominator) || Array.from(denominator).length === 1;
    return `${simpleNumerator ? numerator : `(${numerator})`}/${simpleDenominator ? denominator : `(${denominator})`}`;
}
function formatRoot(value, symbol = "√") {
    value = value.trim();
    return /^[\p{L}\p{N}.]+$/u.test(value) ? `${symbol}${value}` : `${symbol}(${value})`;
}
const NAMED_OPERATOR_START = "\u{f0004}";
const NAMED_OPERATOR_END = "\u{f0005}";
const NAMED_OPERATOR_LEFT_SPACING_PATTERN = /(?<=[\p{L}\p{N})\]}\u{f0001}])\u{f0004}/gu;
const NAMED_OPERATOR_RIGHT_SPACING_PATTERN = /\u{f0005}(?=[\p{L}\p{N}√\u{f0000}])/gu;
function normalizeOutput(value) {
    return value
        .replace(NAMED_OPERATOR_LEFT_SPACING_PATTERN, " ")
        .replaceAll(NAMED_OPERATOR_START, "")
        .replace(NAMED_OPERATOR_RIGHT_SPACING_PATTERN, " ")
        .replaceAll(NAMED_OPERATOR_END, "")
        .split("\n")
        .map((line) => line.replace(/[ \t]+/g, " ").trim())
        .filter((line, index, lines) => line.length > 0 || (index > 0 && index < lines.length - 1))
        .join("\n")
        .trim();
}
const LAYOUT_MARKER_START = "\u{f0000}";
const LAYOUT_MARKER_END = "\u{f0001}";
const LAYOUT_MARKER_PATTERN = /\u{f0000}(\d+)\u{f0001}/gu;
const TRAILING_LAYOUT_MARKER_PATTERN = /\u{f0000}(\d+)\u{f0001}$/u;
const PROTECTED_SPACE = "\u{f0002}";
function padLayoutLine(line, width, centered = false) {
    const padding = Math.max(0, width - visibleWidth(line));
    const left = centered ? Math.floor(padding / 2) : 0;
    return `${" ".repeat(left)}${line}${" ".repeat(padding - left)}`;
}
function joinLayouts(layouts) {
    if (layouts.length === 0) {
        return { lines: [""], width: 0, baseline: 0 };
    }
    const baseline = Math.max(...layouts.map((layout) => layout.baseline));
    const below = Math.max(...layouts.map((layout) => layout.lines.length - layout.baseline - 1));
    const lines = [];
    for (let row = 0; row <= baseline + below; row++) {
        let line = "";
        for (const layout of layouts) {
            const sourceRow = row - baseline + layout.baseline;
            line +=
                sourceRow >= 0 && sourceRow < layout.lines.length
                    ? padLayoutLine(layout.lines[sourceRow] ?? "", layout.width)
                    : " ".repeat(layout.width);
        }
        lines.push(line.trimEnd());
    }
    return {
        lines,
        width: layouts.reduce((width, layout) => width + layout.width, 0),
        baseline,
    };
}
function renderLayout(source, nodes) {
    const renderedLines = [];
    let firstBaseline = 0;
    for (const sourceLine of source.split("\n")) {
        const layouts = [];
        let position = 0;
        let previousNode;
        for (const match of sourceLine.matchAll(LAYOUT_MARKER_PATTERN)) {
            const index = match.index;
            const node = nodes[Number(match[1])];
            if (!node) {
                continue;
            }
            if (index > position) {
                const sliced = sourceLine.slice(position, index);
                const trimmed = (previousNode ? sliced.trimStart() : sliced).trimEnd();
                const preserveLeadingSpace = previousNode?.type === "matrix" && /^\s/.test(sliced);
                const preserveTrailingSpace = node.type === "matrix" && /\s$/.test(sliced);
                const text = trimmed
                    ? `${preserveLeadingSpace ? " " : ""}${trimmed}${preserveTrailingSpace ? " " : ""}`
                    : preserveLeadingSpace || preserveTrailingSpace
                        ? " "
                        : "";
                layouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });
            }
            if (node.type === "fraction") {
                const numerator = renderLayout(node.numerator, nodes);
                const denominator = renderLayout(node.denominator, nodes);
                const contentWidth = Math.max(numerator.width, denominator.width, 1);
                const width = contentWidth + 2;
                layouts.push({
                    lines: [
                        ...numerator.lines.map((line) => padLayoutLine(line, width, true)),
                        ` ${"─".repeat(contentWidth)} `,
                        ...denominator.lines.map((line) => padLayoutLine(line, width, true)),
                    ],
                    width,
                    baseline: numerator.lines.length,
                });
            }
            else if (node.type === "operator") {
                const contentWidth = Math.max(visibleWidth(node.operator), node.lower === undefined ? 0 : visibleWidth(node.lower), node.upper === undefined ? 0 : visibleWidth(node.upper));
                const lines = [];
                if (node.upper !== undefined) {
                    lines.push(`${padLayoutLine(node.upper, contentWidth, true)} `);
                }
                lines.push(`${padLayoutLine(node.operator, contentWidth, true)} `);
                if (node.lower !== undefined) {
                    lines.push(`${padLayoutLine(node.lower, contentWidth, true)} `);
                }
                layouts.push({
                    lines,
                    width: contentWidth + 1,
                    baseline: node.upper === undefined ? 0 : 1,
                });
            }
            else {
                const width = Math.max(0, ...node.lines.map((line) => visibleWidth(line)));
                layouts.push({
                    lines: node.lines.map((line) => padLayoutLine(line, width)),
                    width,
                    baseline: node.baseline,
                });
            }
            position = index + match[0].length;
            previousNode = node;
        }
        if (position < sourceLine.length) {
            const sliced = sourceLine.slice(position);
            const trimmed = previousNode ? sliced.trimStart() : sliced;
            const text = previousNode?.type === "matrix" && /^\s/.test(sliced) ? ` ${trimmed}` : trimmed;
            layouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });
        }
        const lineLayout = joinLayouts(layouts);
        if (renderedLines.length === 0) {
            firstBaseline = lineLayout.baseline;
        }
        renderedLines.push(...lineLayout.lines);
    }
    return {
        lines: renderedLines,
        width: Math.max(0, ...renderedLines.map((line) => visibleWidth(line))),
        baseline: firstBaseline,
    };
}
class LatexParser {
    source;
    layoutNodes;
    display;
    position = 0;
    supported = true;
    stackFractions = true;
    constructor(source, layoutNodes, display) {
        this.source = source;
        this.layoutNodes = layoutNodes;
        this.display = display;
    }
    render() {
        const rendered = this.parseSequence();
        if (!this.supported || this.position !== this.source.length) {
            return undefined;
        }
        return normalizeOutput(rendered);
    }
    parseSequence(endCharacter) {
        let result = "";
        while (this.position < this.source.length) {
            const character = this.source[this.position];
            if (endCharacter && character === endCharacter) {
                this.position++;
                return result;
            }
            if (character === "}") {
                this.supported = false;
                return result;
            }
            if (character === "{") {
                this.position++;
                result += this.parseSequence("}");
                continue;
            }
            if (character === "\\") {
                const command = this.parseCommand();
                if (command === NEGATIVE_SPACE) {
                    result = result.trimEnd();
                    if (result.endsWith(NAMED_OPERATOR_END)) {
                        result = result.slice(0, -NAMED_OPERATOR_END.length);
                    }
                }
                else {
                    result += command;
                }
                continue;
            }
            if (character === "^" || character === "_") {
                this.position++;
                result = result.trimEnd();
                const script = formatScript(this.parseRequiredArgument(false), character === "_" ? "sub" : "sup");
                if (result.endsWith(NAMED_OPERATOR_END)) {
                    result = `${result.slice(0, -NAMED_OPERATOR_END.length)}${script}${NAMED_OPERATOR_END}`;
                }
                else {
                    result += script;
                }
                continue;
            }
            if (/\s/.test(character)) {
                result += this.parseWhitespace();
                continue;
            }
            if (character === "=" || character === "<" || character === ">") {
                result = `${result.trimEnd()} ${character} `;
                this.position++;
                continue;
            }
            if (character === "&") {
                this.position++;
                continue;
            }
            if (character === "~") {
                this.position++;
                result += " ";
                continue;
            }
            if (character === ".") {
                const marker = TRAILING_LAYOUT_MARKER_PATTERN.exec(result);
                const node = marker ? this.layoutNodes[Number(marker[1])] : undefined;
                if (node?.type === "matrix") {
                    const lastLine = node.lines.length - 1;
                    node.lines[lastLine] = `${node.lines[lastLine] ?? ""}${character}`;
                    this.position++;
                    continue;
                }
            }
            result += character;
            this.position++;
        }
        if (endCharacter) {
            this.supported = false;
        }
        return result;
    }
    parseWhitespace() {
        while (this.position < this.source.length && /\s/.test(this.source[this.position] ?? "")) {
            this.position++;
        }
        return " ";
    }
    parseCommand() {
        this.position++;
        if (this.position >= this.source.length) {
            this.supported = false;
            return "";
        }
        let command = "";
        const first = this.source[this.position] ?? "";
        if (/[A-Za-z]/.test(first)) {
            const start = this.position;
            while (this.position < this.source.length && /[A-Za-z]/.test(this.source[this.position] ?? "")) {
                this.position++;
            }
            command = this.source.slice(start, this.position);
        }
        else {
            command = first;
            this.position++;
        }
        if (command === "\\") {
            return "\n";
        }
        if (SPACING_COMMANDS.has(command)) {
            return " ";
        }
        if (NEGATIVE_SPACING_COMMANDS.has(command)) {
            return NEGATIVE_SPACE;
        }
        if (IGNORED_COMMANDS.has(command)) {
            return "";
        }
        if (command === "{" ||
            command === "}" ||
            command === "$" ||
            command === "%" ||
            command === "#" ||
            command === "_" ||
            command === "&") {
            return command;
        }
        if (command === "|") {
            return "‖";
        }
        if (command === "not") {
            const value = this.parseRequiredArgument(false).trim();
            const negated = NEGATED_SYMBOLS[value];
            if (negated !== undefined) {
                return ` ${negated} `;
            }
            const characters = Array.from(value);
            if (characters.length === 0) {
                this.supported = false;
                return "";
            }
            return ` ${characters[0]}\u0338${characters.slice(1).join("")} `;
        }
        if (LIMIT_OPERATORS.has(command)) {
            return this.parseOperator(command, "bracket", true, true);
        }
        const symbol = SYMBOLS[command];
        if (symbol !== undefined) {
            if (DISPLAY_LIMIT_SYMBOLS.has(command)) {
                return this.parseOperator(symbol, "script", true);
            }
            return command === "cdot" || command === "times" || RELATION_COMMANDS.has(command) ? ` ${symbol} ` : symbol;
        }
        if (NAMED_OPERATORS.has(command)) {
            return `${NAMED_OPERATOR_START}${command}${NAMED_OPERATOR_END}`;
        }
        if (SIZE_COMMANDS.has(command)) {
            return "";
        }
        if (command === "left" || command === "middle" || command === "right") {
            if (this.source[this.position] === ".") {
                this.position++;
            }
            return "";
        }
        if (command === "frac" || command === "dfrac" || command === "tfrac") {
            const shouldStack = this.display && this.stackFractions && command !== "tfrac";
            const numerator = this.parseRequiredArgument(!shouldStack);
            const denominator = this.parseRequiredArgument(!shouldStack);
            if (shouldStack) {
                const index = this.layoutNodes.push({
                    type: "fraction",
                    numerator: normalizeOutput(numerator),
                    denominator: normalizeOutput(denominator),
                }) - 1;
                return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
            }
            return formatFraction(numerator, denominator);
        }
        if (command === "sqrt") {
            const degree = this.parseOptionalArgument()?.trim();
            const value = this.parseRequiredArgument();
            if (degree === undefined || degree === "2") {
                return formatRoot(value);
            }
            if (degree === "3") {
                return formatRoot(value, "∛");
            }
            if (degree === "4") {
                return formatRoot(value, "∜");
            }
            return `${formatScript(degree, "sup")}${formatRoot(value)}`;
        }
        if (command === "boxed" || command === "fbox") {
            return `[${this.parseRequiredArgument().trim()}]`;
        }
        if (command === "binom" || command === "dbinom" || command === "tbinom") {
            return `(${this.parseRequiredArgument()} choose ${this.parseRequiredArgument()})`;
        }
        const accent = ACCENTS[command];
        if (accent !== undefined) {
            const value = this.parseRequiredArgument();
            return Array.from(value).length === 1 ? `${value}${accent}` : `${command}(${value})`;
        }
        if (command === "mathbb") {
            const value = this.parseRequiredArgument();
            return Array.from(value, (character) => BLACKBOARD[character] ?? character).join("");
        }
        if (command === "operatorname") {
            const starred = this.source[this.position] === "*";
            if (starred) {
                this.position++;
            }
            const operator = normalizeOutput(this.parseRequiredArgument()).trim();
            return this.parseOperator(operator, "bracket", starred, true);
        }
        if (command === "mod" || command === "bmod") {
            return " mod ";
        }
        if (command === "pmod" || command === "pod") {
            const value = this.parseRequiredArgument().trim();
            return command === "pmod" ? ` (mod ${value})` : ` (${value})`;
        }
        if (command === "overset" || command === "stackrel") {
            const upper = this.parseRequiredArgument();
            const value = this.parseRequiredArgument().trim();
            return `${value}${formatScript(upper, "sup")}`;
        }
        if (command === "underset") {
            const lower = this.parseRequiredArgument();
            const value = this.parseRequiredArgument().trim();
            return `${value}${formatScript(lower, "sub")}`;
        }
        if (PLAIN_WRAPPERS.has(command)) {
            const value = this.parseRequiredArgument();
            return command.startsWith("text") || command === "mbox" ? value : value.trim();
        }
        if (command === "begin") {
            return this.parseEnvironment();
        }
        if (command === "end") {
            this.supported = false;
            return "";
        }
        this.supported = false;
        return `\\${command}`;
    }
    parseOperator(operator, inlineLowerStyle, displayLimits, spaced = false) {
        let useDisplayLimits = displayLimits;
        let modifierPosition = this.position;
        while (modifierPosition < this.source.length && /[ \t]/.test(this.source[modifierPosition] ?? "")) {
            modifierPosition++;
        }
        const modifier = /^\\(limits|nolimits)(?![A-Za-z])/.exec(this.source.slice(modifierPosition));
        if (modifier) {
            useDisplayLimits = modifier[1] === "limits";
            this.position = modifierPosition + modifier[0].length;
        }
        let lower;
        let upper;
        while (true) {
            let scriptPosition = this.position;
            while (scriptPosition < this.source.length && /[ \t]/.test(this.source[scriptPosition] ?? "")) {
                scriptPosition++;
            }
            const kind = this.source[scriptPosition];
            if (kind !== "_" && kind !== "^") {
                break;
            }
            this.position = scriptPosition + 1;
            const value = normalizeOutput(this.parseRequiredArgument(false)).replaceAll(" ", "");
            if (kind === "_") {
                if (lower !== undefined) {
                    this.supported = false;
                }
                lower = value;
            }
            else {
                if (upper !== undefined) {
                    this.supported = false;
                }
                upper = value;
            }
        }
        if (this.display && useDisplayLimits && (lower !== undefined || upper !== undefined)) {
            const index = this.layoutNodes.push({ type: "operator", operator, lower, upper }) - 1;
            return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
        }
        let rendered = operator;
        if (lower !== undefined) {
            rendered += inlineLowerStyle === "bracket" ? `[${lower}]` : formatScript(lower, "sub");
        }
        if (upper !== undefined) {
            rendered += formatScript(upper, "sup");
        }
        return spaced ? ` ${rendered} ` : rendered;
    }
    parseRequiredArgument(stackFractions = true) {
        const previousStackFractions = this.stackFractions;
        this.stackFractions = previousStackFractions && stackFractions;
        const value = this.parseRequiredArgumentValue();
        this.stackFractions = previousStackFractions;
        return value;
    }
    parseRequiredArgumentValue() {
        while (this.position < this.source.length && /[ \t]/.test(this.source[this.position] ?? "")) {
            this.position++;
        }
        if (this.position >= this.source.length) {
            this.supported = false;
            return "";
        }
        if (this.source[this.position] === "{") {
            this.position++;
            return this.parseSequence("}");
        }
        if (this.source[this.position] === "\\") {
            return this.parseCommand();
        }
        const value = this.source[this.position] ?? "";
        this.position++;
        return value;
    }
    parseOptionalArgument() {
        while (this.position < this.source.length && /[ \t]/.test(this.source[this.position] ?? "")) {
            this.position++;
        }
        if (this.source[this.position] !== "[") {
            return undefined;
        }
        const end = this.source.indexOf("]", this.position + 1);
        if (end < 0) {
            this.supported = false;
            return undefined;
        }
        const value = this.source.slice(this.position + 1, end);
        this.position = end + 1;
        return this.renderNested(value);
    }
    readRawGroup() {
        while (this.position < this.source.length && /[ \t]/.test(this.source[this.position] ?? "")) {
            this.position++;
        }
        if (this.source[this.position] !== "{") {
            this.supported = false;
            return undefined;
        }
        const start = ++this.position;
        let depth = 1;
        while (this.position < this.source.length) {
            const character = this.source[this.position];
            if (character === "\\") {
                this.position += 2;
                continue;
            }
            if (character === "{")
                depth++;
            if (character === "}")
                depth--;
            if (depth === 0) {
                const value = this.source.slice(start, this.position);
                this.position++;
                return value;
            }
            this.position++;
        }
        this.supported = false;
        return undefined;
    }
    splitEnvironmentRows(body) {
        return body.split(/\\\\(?:\[[^\]\n]*\])?/);
    }
    parseEnvironment() {
        const environment = this.readRawGroup();
        if (!environment) {
            return "";
        }
        const endMarker = `\\end{${environment}}`;
        const end = this.source.indexOf(endMarker, this.position);
        if (end < 0) {
            this.supported = false;
            return "";
        }
        const body = this.source.slice(this.position, end);
        this.position = end + endMarker.length;
        if (environment === "equation" || environment === "equation*" || environment === "displaymath") {
            return this.renderNested(body).trim();
        }
        if (environment === "aligned" ||
            environment === "align" ||
            environment === "align*" ||
            environment === "alignedat" ||
            environment === "alignat" ||
            environment === "alignat*" ||
            environment === "gather" ||
            environment === "gathered" ||
            environment === "multline" ||
            environment === "multline*" ||
            environment === "split") {
            const alignedAt = ["alignedat", "alignat", "alignat*"].includes(environment);
            const alignedBody = alignedAt ? body.replace(/^\s*\{[^}]*\}/, "") : body;
            return this.splitEnvironmentRows(alignedBody)
                .map((row) => {
                const cells = row.split("&");
                const source = alignedAt
                    ? Array.from({ length: Math.ceil(cells.length / 2) }, (_, index) => cells.slice(index * 2, index * 2 + 2).join("")).join(" ")
                    : cells.join("");
                return this.renderNested(source).trim();
            })
                .filter(Boolean)
                .join("\n");
        }
        if (environment === "cases" || environment === "cases*") {
            const rows = this.splitEnvironmentRows(body)
                .map((row) => row.split("&").map((cell) => this.renderNested(cell, false).trim()))
                .filter((row) => row.some(Boolean));
            return rows
                .map((row, index) => {
                const value = (row[0] ?? "").replace(/,\s*$/, "");
                const condition = row[1] ?? "";
                const delimiter = index === 0 ? "⎧" : index === rows.length - 1 ? "⎩" : "⎨";
                const conditionPrefix = /^(?:if|when|for|otherwise)\b/i.test(condition) ? " " : " if ";
                return `${delimiter} ${value}${condition ? `${conditionPrefix}${condition}` : ""}`;
            })
                .join("\n");
        }
        if (["array", "matrix", "smallmatrix", "pmatrix", "bmatrix", "Bmatrix", "vmatrix", "Vmatrix"].includes(environment)) {
            const matrixBody = environment === "array" ? body.replace(/^\s*\{[^}]*\}/, "") : body;
            return this.renderMatrix(environment, matrixBody);
        }
        this.supported = false;
        return body;
    }
    renderMatrix(environment, body) {
        const matrix = this.splitEnvironmentRows(body)
            .map((row) => row.split("&").map((cell) => this.renderNested(cell, false).trim()))
            .filter((row) => row.some(Boolean));
        const columnCount = Math.max(0, ...matrix.map((row) => row.length));
        const columnWidths = Array.from({ length: columnCount }, (_, column) => Math.max(0, ...matrix.map((row) => visibleWidth(row[column] ?? ""))));
        const rows = matrix.map((row) => Array.from({ length: columnCount }, (_, column) => {
            const cell = row[column] ?? "";
            return `${cell}${PROTECTED_SPACE.repeat(Math.max(0, (columnWidths[column] ?? 0) - visibleWidth(cell)))}`;
        }).join(" │ "));
        let lines;
        if (environment === "array" || environment === "matrix" || environment === "smallmatrix") {
            lines = rows;
        }
        else {
            const delimiters = {
                pmatrix: ["⎛", "⎞", "⎜", "⎟", "⎝", "⎠"],
                bmatrix: ["⎡", "⎤", "⎢", "⎥", "⎣", "⎦"],
                Bmatrix: ["⎧", "⎫", "⎨", "⎬", "⎩", "⎭"],
                vmatrix: ["│", "│", "│", "│", "│", "│"],
                Vmatrix: ["║", "║", "║", "║", "║", "║"],
            };
            const delimiter = delimiters[environment];
            if (!delimiter) {
                this.supported = false;
                return rows.join("\n");
            }
            lines = rows.map((row, index) => {
                const left = index === 0 ? delimiter[0] : index === rows.length - 1 ? delimiter[4] : delimiter[2];
                const right = index === 0 ? delimiter[1] : index === rows.length - 1 ? delimiter[5] : delimiter[3];
                return `${left} ${row} ${right}`;
            });
        }
        if (lines.length <= 1) {
            return lines[0] ?? "";
        }
        const index = this.layoutNodes.push({ type: "matrix", lines, baseline: 0 }) - 1;
        return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
    }
    renderNested(source, stackFractions = true) {
        const rendered = new LatexParser(source, this.layoutNodes, this.display && stackFractions).render();
        if (rendered === undefined) {
            this.supported = false;
            return source;
        }
        return rendered;
    }
}
/**
 * Render a basic LaTeX math expression as terminal-friendly Unicode text.
 * Returns undefined when the expression contains unsupported or malformed syntax.
 */
export function renderLatex(source, options = {}) {
    const layoutNodes = [];
    const rendered = new LatexParser(source, layoutNodes, options.display === true).render();
    if (rendered === undefined) {
        return undefined;
    }
    if (layoutNodes.length === 0) {
        return rendered.replaceAll(PROTECTED_SPACE, " ");
    }
    const lines = renderLayout(rendered, layoutNodes).lines;
    const indentation = Math.min(...lines.filter((line) => line.trim()).map((line) => line.length - line.trimStart().length));
    return lines
        .map((line) => line.slice(indentation).trimEnd())
        .join("\n")
        .trimEnd()
        .replaceAll(PROTECTED_SPACE, " ");
}
//# sourceMappingURL=latex.js.map