import { SVG_TO_SHAPE_MAPPER } from "../constants.js";
import { nanoid } from "nanoid";
import { createArrowSkeletonFromSVG, createContainerSkeletonFromSVG, createLineSkeletonFromSVG, createTextSkeletonFromSVG, } from "../elementSkeleton.js";
// Currently mermaid supported these 6 arrow types, the names are taken from mermaidParser.LINETYPE
const SEQUENCE_ARROW_TYPES = {
    0: "SOLID",
    1: "DOTTED",
    3: "SOLID_CROSS",
    4: "DOTTED_CROSS",
    5: "SOLID_OPEN",
    6: "DOTTED_OPEN",
    24: "SOLID_POINT",
    25: "DOTTED_POINT",
};
const MESSAGE_TYPE = {
    SOLID: 0,
    DOTTED: 1,
    NOTE: 2,
    SOLID_CROSS: 3,
    DOTTED_CROSS: 4,
    SOLID_OPEN: 5,
    DOTTED_OPEN: 6,
    LOOP_START: 10,
    LOOP_END: 11,
    ALT_START: 12,
    ALT_ELSE: 13,
    ALT_END: 14,
    OPT_START: 15,
    OPT_END: 16,
    ACTIVE_START: 17,
    ACTIVE_END: 18,
    PAR_START: 19,
    PAR_AND: 20,
    PAR_END: 21,
    RECT_START: 22,
    RECT_END: 23,
    SOLID_POINT: 24,
    DOTTED_POINT: 25,
    AUTONUMBER: 26,
    CRITICAL_START: 27,
    CRITICAL_OPTION: 28,
    CRITICAL_END: 29,
    BREAK_START: 30,
    BREAK_END: 31,
    PAR_OVER_START: 32,
};
const getStrokeStyle = (type) => {
    let strokeStyle;
    switch (type) {
        case MESSAGE_TYPE.SOLID:
        case MESSAGE_TYPE.SOLID_CROSS:
        case MESSAGE_TYPE.SOLID_OPEN:
        case MESSAGE_TYPE.SOLID_POINT:
            strokeStyle = "solid";
            break;
        case MESSAGE_TYPE.DOTTED:
        case MESSAGE_TYPE.DOTTED_CROSS:
        case MESSAGE_TYPE.DOTTED_OPEN:
        case MESSAGE_TYPE.DOTTED_POINT:
            strokeStyle = "dotted";
            break;
        default:
            strokeStyle = "solid";
            break;
    }
    return strokeStyle;
};
const attachSequenceNumberToArrow = (node, arrow) => {
    const showSequenceNumber = !!node.nextElementSibling?.classList.contains("sequenceNumber");
    if (showSequenceNumber) {
        const text = node.nextElementSibling?.textContent;
        if (!text) {
            throw new Error("sequence number not present");
        }
        const height = 30;
        const yOffset = height / 2;
        const xOffset = 10;
        const sequenceNumber = {
            type: "rectangle",
            x: arrow.startX - xOffset,
            y: arrow.startY - yOffset,
            label: { text, fontSize: 14 },
            bgColor: "#e9ecef",
            height,
            subtype: "sequence",
        };
        Object.assign(arrow, { sequenceNumber });
    }
};
const createActorSymbol = (rootNode, text, opts) => {
    if (!rootNode) {
        throw "root node not found";
    }
    const groupId = nanoid();
    const children = Array.from(rootNode.children);
    const nodeElements = [];
    children.forEach((child, index) => {
        const id = `${opts?.id}-${index}`;
        let ele;
        switch (child.tagName) {
            case "line":
                const startX = Number(child.getAttribute("x1"));
                const startY = Number(child.getAttribute("y1"));
                const endX = Number(child.getAttribute("x2"));
                const endY = Number(child.getAttribute("y2"));
                ele = createLineSkeletonFromSVG(child, startX, startY, endX, endY, { groupId, id });
                break;
            case "text":
                ele = createTextSkeletonFromSVG(child, text, {
                    groupId,
                    id,
                });
                break;
            case "circle":
                ele = createContainerSkeletonFromSVG(child, "ellipse", {
                    label: child.textContent ? { text: child.textContent } : undefined,
                    groupId,
                    id,
                });
            default:
                ele = createContainerSkeletonFromSVG(child, SVG_TO_SHAPE_MAPPER[child.tagName], {
                    label: child.textContent ? { text: child.textContent } : undefined,
                    groupId,
                    id,
                });
        }
        nodeElements.push(ele);
    });
    return nodeElements;
};
const parseActor = (actors, containerEl) => {
    const actorTopNodes = Array.from(containerEl.querySelectorAll(".actor-top"));
    const actorBottomNodes = Array.from(containerEl.querySelectorAll(".actor-bottom"));
    const nodes = [];
    const lines = [];
    Object.values(actors).forEach((actor, index) => {
        const topRootNode = actorTopNodes.find((actorNode) => actorNode.getAttribute("name") === actor.name);
        const bottomRootNode = actorBottomNodes.find((actorNode) => actorNode.getAttribute("name") === actor.name);
        if (!topRootNode || !bottomRootNode) {
            throw "root not found";
        }
        const text = actor.description;
        if (actor.type === "participant") {
            // creating top actor node element
            const topNodeElement = createContainerSkeletonFromSVG(topRootNode, "rectangle", { id: `${actor.name}-top`, label: { text }, subtype: "actor" });
            if (!topNodeElement) {
                throw "Top Node element not found!";
            }
            nodes.push([topNodeElement]);
            // creating bottom actor node element
            const bottomNodeElement = createContainerSkeletonFromSVG(bottomRootNode, "rectangle", { id: `${actor.name}-bottom`, label: { text }, subtype: "actor" });
            nodes.push([bottomNodeElement]);
            // Get the line connecting the top and bottom nodes. As per the DOM, the line is rendered as sibling parent of top root node
            const lineNode = topRootNode?.parentElement
                ?.previousElementSibling;
            if (lineNode?.tagName !== "line") {
                throw "Line not found";
            }
            const startX = Number(lineNode.getAttribute("x1"));
            if (!topNodeElement.height) {
                throw "Top node element height is null";
            }
            const startY = topNodeElement.y + topNodeElement.height;
            // Make sure lines don't overlap with the nodes, in mermaid it overlaps but isn't visible as its pushed back and containers are non transparent
            const endY = bottomNodeElement.y;
            const endX = Number(lineNode.getAttribute("x2"));
            const line = createLineSkeletonFromSVG(lineNode, startX, startY, endX, endY);
            lines.push(line);
        }
        else if (actor.type === "actor") {
            const topNodeElement = createActorSymbol(topRootNode, text, {
                id: `${actor.name}-top`,
            });
            nodes.push(topNodeElement);
            const bottomNodeElement = createActorSymbol(bottomRootNode, text, {
                id: `${actor.name}-bottom`,
            });
            nodes.push(bottomNodeElement);
            // Get the line connecting the top and bottom nodes. As per the DOM, the line is rendered as sibling of the actor root element
            const lineNode = topRootNode.previousElementSibling;
            if (lineNode?.tagName !== "line") {
                throw "Line not found";
            }
            const startX = Number(lineNode.getAttribute("x1"));
            const startY = Number(lineNode.getAttribute("y1"));
            const endX = Number(lineNode.getAttribute("x2"));
            // Make sure lines don't overlap with the nodes, in mermaid it overlaps but isn't visible as its pushed back and containers are non transparent
            const bottomEllipseNode = bottomNodeElement.find((node) => node.type === "ellipse");
            if (bottomEllipseNode) {
                const endY = bottomEllipseNode.y;
                const line = createLineSkeletonFromSVG(lineNode, startX, startY, endX, endY);
                lines.push(line);
            }
        }
    });
    return { nodes, lines };
};
const computeArrows = (messages, containerEl) => {
    const arrows = [];
    const arrowNodes = Array.from(containerEl.querySelectorAll('[class*="messageLine"]'));
    const supportedMessageTypes = Object.keys(SEQUENCE_ARROW_TYPES);
    const arrowMessages = messages.filter((message) => supportedMessageTypes.includes(message.type.toString()));
    arrowNodes.forEach((arrowNode, index) => {
        const message = arrowMessages[index];
        const messageType = SEQUENCE_ARROW_TYPES[message.type];
        const arrow = createArrowSkeletonFromSVG(arrowNode, {
            label: message?.message,
            strokeStyle: getStrokeStyle(message.type),
            endArrowhead: messageType === "SOLID_OPEN" || messageType === "DOTTED_OPEN"
                ? null
                : "arrow",
        });
        attachSequenceNumberToArrow(arrowNode, arrow);
        arrows.push(arrow);
    });
    return arrows;
};
const computeNotes = (messages, containerEl) => {
    const noteNodes = Array.from(containerEl.querySelectorAll(".note")).map((node) => node.parentElement);
    const noteText = messages.filter((message) => message.type === MESSAGE_TYPE.NOTE);
    const notes = [];
    noteNodes.forEach((node, index) => {
        if (!node) {
            return;
        }
        const rect = node.firstChild;
        const text = noteText[index].message;
        const note = createContainerSkeletonFromSVG(rect, "rectangle", {
            label: { text },
            subtype: "note",
        });
        notes.push(note);
    });
    return notes;
};
const parseActivations = (containerEl) => {
    const activationNodes = Array.from(containerEl.querySelectorAll(`[class*=activation]`));
    const activations = [];
    activationNodes.forEach((node) => {
        const rect = createContainerSkeletonFromSVG(node, "rectangle", {
            label: { text: "" },
            subtype: "activation",
        });
        activations.push(rect);
    });
    return activations;
};
const parseLoops = (messages, containerEl) => {
    const lineNodes = Array.from(containerEl.querySelectorAll(".loopLine"));
    const lines = [];
    const texts = [];
    const nodes = [];
    lineNodes.forEach((node) => {
        const startX = Number(node.getAttribute("x1"));
        const startY = Number(node.getAttribute("y1"));
        const endX = Number(node.getAttribute("x2"));
        const endY = Number(node.getAttribute("y2"));
        const line = createLineSkeletonFromSVG(node, startX, startY, endX, endY);
        line.strokeStyle = "dotted";
        line.strokeColor = "#adb5bd";
        line.strokeWidth = 2;
        lines.push(line);
    });
    const loopTextNodes = Array.from(containerEl.querySelectorAll(".loopText"));
    const criticalMessages = messages
        .filter((message) => message.type === MESSAGE_TYPE.CRITICAL_START)
        .map((message) => message.message);
    loopTextNodes.forEach((node) => {
        const text = node.textContent || "";
        const textElement = createTextSkeletonFromSVG(node, text);
        // The text is rendered between [ ] in DOM hence getting the text excluding the [ ]
        const rawText = text.match(/\[(.*?)\]/)?.[1] || "";
        const isCritical = criticalMessages.includes(rawText);
        // For critical label the coordinates are not accurate in mermaid as there is
        // no padding left hence shifting the text next to critical label by 16px (font size)
        if (isCritical) {
            textElement.x += 16;
        }
        texts.push(textElement);
    });
    const labelBoxes = Array.from(containerEl?.querySelectorAll(".labelBox"));
    const labelTextNode = Array.from(containerEl?.querySelectorAll(".labelText"));
    labelBoxes.forEach((labelBox, index) => {
        const text = labelTextNode[index]?.textContent || "";
        const container = createContainerSkeletonFromSVG(labelBox, "rectangle", {
            label: { text },
        });
        container.strokeColor = "#adb5bd";
        container.bgColor = "#e9ecef";
        // So width is calculated based on label
        container.width = undefined;
        nodes.push(container);
    });
    return { lines, texts, nodes };
};
const computeHighlights = (containerEl) => {
    const rects = Array.from(containerEl.querySelectorAll(".rect"))
        // Only drawing specifically for highlights as the same selector is for grouping as well. For grouping we
        // draw it ourselves
        .filter((node) => node.parentElement?.tagName !== "g");
    const nodes = [];
    rects.forEach((rect) => {
        const node = createContainerSkeletonFromSVG(rect, "rectangle", {
            label: { text: "" },
            subtype: "highlight",
        });
        nodes.push(node);
    });
    return nodes;
};
export const parseMermaidSequenceDiagram = (diagram, containerEl) => {
    diagram.parse();
    // Get mermaid parsed data from parser shared variable `yy`
    //@ts-ignore
    const mermaidParser = diagram.parser.yy;
    const nodes = [];
    const groups = mermaidParser.getBoxes();
    const bgHightlights = computeHighlights(containerEl);
    const actorData = mermaidParser.getActors();
    const { nodes: actors, lines } = parseActor(actorData, containerEl);
    const messages = mermaidParser.getMessages();
    const arrows = computeArrows(messages, containerEl);
    const notes = computeNotes(messages, containerEl);
    const activations = parseActivations(containerEl);
    const loops = parseLoops(messages, containerEl);
    nodes.push(bgHightlights);
    nodes.push(...actors);
    nodes.push(notes);
    nodes.push(activations);
    return { type: "sequence", lines, arrows, nodes, loops, groups };
};
