import {wrapFragment} from "../buildCommon";
import defineFunction from "../defineFunction";
import {MathNode} from "../mathMLTree";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
import {assertSymbolNodeType} from "../parseNode";
import ParseError from "../ParseError";
import {makeEm} from "../units";

import type Parser from "../Parser";
import type {ParseNode, AnyParseNode} from "../parseNode";

const cdArrowFunctionName: Record<string, string> = {
    ">": "\\\\cdrightarrow",
    "<": "\\\\cdleftarrow",
    "=": "\\\\cdlongequal",
    "A": "\\uparrow",
    "V": "\\downarrow",
    "|": "\\Vert",
    ".": "no arrow",
};

const newCell = (): ParseNode<"styling"> => {
    // Create an empty cell, to be filled below with parse nodes.
    // The parseTree from this module must be constructed like the
    // one created by parseArray(), so an empty CD cell must
    // be a ParseNode<"styling">. And CD is always displaystyle.
    return {type: "styling", body: [], mode: "math", style: "display"};
};

const isStartOfArrow = (node: AnyParseNode) => {
    return (node.type === "textord" && node.text === "@");
};

const isLabelEnd = (node: AnyParseNode, endChar: string): boolean => {
    return ((node.type === "mathord" || node.type === "atom") &&
        node.text === endChar);
};

function cdArrow(
    arrowChar: string,
    labels: ParseNode<"ordgroup">[],
    parser: Parser
): AnyParseNode {
    // Return a parse tree of an arrow and its labels.
    // This acts in a way similar to a macro expansion.
    const funcName = cdArrowFunctionName[arrowChar];
    switch (funcName) {
        case "\\\\cdrightarrow":
        case "\\\\cdleftarrow":
            return parser.callFunction(
                funcName, [labels[0]], [labels[1]]
            );
        case "\\uparrow":
        case "\\downarrow": {
            const leftLabel = parser.callFunction(
                "\\\\cdleft", [labels[0]], []
            );
            const bareArrow: ParseNode<"atom"> = {
                type: "atom",
                text: funcName,
                mode: "math",
                family: "rel",
            };
            const sizedArrow = parser.callFunction("\\Big", [bareArrow], []);
            const rightLabel = parser.callFunction(
                "\\\\cdright", [labels[1]], []
            );
            const arrowGroup: ParseNode<"ordgroup"> = {
                type: "ordgroup",
                mode: "math",
                body: [leftLabel, sizedArrow, rightLabel],
            };
            return parser.callFunction("\\\\cdparent", [arrowGroup], []);
        }
        case "\\\\cdlongequal":
            return parser.callFunction("\\\\cdlongequal", [], []);
        case "\\Vert": {
            const arrow: ParseNode<"textord"> = {type: "textord", text: "\\Vert", mode: "math"};
            return parser.callFunction("\\Big", [arrow], []);
        }
        default:
            return {type: "textord", text: " ", mode: "math"};
    }
}

export function parseCD(parser: Parser): ParseNode<"array"> {
    // Get the array's parse nodes with \\ temporarily mapped to \cr.
    const parsedRows: AnyParseNode[][] = [];
    parser.gullet.beginGroup();
    parser.gullet.macros.set("\\cr", "\\\\\\relax");
    parser.gullet.beginGroup();
    while (true) {  // eslint-disable-line no-constant-condition
        // Get the parse nodes for the next row.
        parsedRows.push(parser.parseExpression(false, "\\\\"));
        parser.gullet.endGroup();
        parser.gullet.beginGroup();
        const next = parser.fetch().text;
        if (next === "&" || next === "\\\\") {
            parser.consume();
        } else if (next === "\\end") {
            if (parsedRows[parsedRows.length - 1].length === 0) {
                parsedRows.pop(); // final row ended in \\
            }
            break;
        } else {
            throw new ParseError("Expected \\\\ or \\cr or \\end",
                                 parser.nextToken);
        }
    }

    let row: ParseNode<"styling">[] = [];
    const body: ParseNode<"styling">[][] = [row];

    // Loop thru the parse nodes. Collect them into cells and arrows.
    for (let i = 0; i < parsedRows.length; i++) {
        // Start a new row.
        const rowNodes = parsedRows[i];
        // Create the first cell.
        let cell = newCell();
