import { nanoid } from "nanoid";
import { computeEdgePositions, getTransformAttr } from "../utils.js";
import { createArrowSkeletion, createContainerSkeletonFromSVG, createLineSkeletonFromSVG, createTextSkeleton, } from "../elementSkeleton.js";
// Taken from mermaidParser.relationType
const RELATION_TYPE = {
    AGGREGATION: 0,
    EXTENSION: 1,
    COMPOSITION: 2,
    DEPENDENCY: 3,
    LOLLIPOP: 4,
};
// Taken from mermaidParser.lineType
const LINE_TYPE = {
    LINE: 0,
    DOTTED_LINE: 1,
};
// This is the offset to update the arrow head postition for rendering in excalidraw as mermaid calculates the position until the start of arrowhead
const MERMAID_ARROW_HEAD_OFFSET = 16;
const getStrokeStyle = (type) => {
    let lineType;
    switch (type) {
        case LINE_TYPE.LINE:
            lineType = "solid";
            break;
        case LINE_TYPE.DOTTED_LINE:
            lineType = "dotted";
            break;
        default:
            lineType = "solid";
    }
    return lineType;
};
const getArrowhead = (type) => {
    let arrowhead;
    switch (type) {
        case RELATION_TYPE.AGGREGATION:
            arrowhead = "diamond_outline";
            break;
        case RELATION_TYPE.COMPOSITION:
            arrowhead = "diamond";
            break;
        case RELATION_TYPE.EXTENSION:
            arrowhead = "triangle_outline";
            break;
        case "none":
            arrowhead = null;
            break;
        case RELATION_TYPE.DEPENDENCY:
        default:
            arrowhead = "arrow";
            break;
    }
    return arrowhead;
};
const parseClasses = (classes, containerEl) => {
    const nodes = [];
    const lines = [];
    const text = [];
    Object.values(classes).forEach((classNode) => {
        const { domId, id: classId } = classNode;
        const groupId = nanoid();
        const domNode = containerEl.querySelector(`[data-id=${classId}]`);
        if (!domNode) {
            throw Error(`DOM Node with id ${domId} not found`);
        }
        const { transformX, transformY } = getTransformAttr(domNode);
        const container = createContainerSkeletonFromSVG(domNode.firstChild, "rectangle", { id: classId, groupId });
        container.x += transformX;
        container.y += transformY;
        container.metadata = { classId };
        nodes.push(container);
        const lineNodes = Array.from(domNode.querySelectorAll(".divider"));
        lineNodes.forEach((lineNode) => {
            const startX = Number(lineNode.getAttribute("x1"));
            const startY = Number(lineNode.getAttribute("y1"));
            const endX = Number(lineNode.getAttribute("x2"));
            const endY = Number(lineNode.getAttribute("y2"));
            const line = createLineSkeletonFromSVG(lineNode, startX, startY, endX, endY, {
                groupId,
                id: nanoid(),
            });
            line.startX += transformX;
            line.startY += transformY;
            line.endX += transformX;
            line.endY += transformY;
            line.metadata = { classId };
            lines.push(line);
        });
        const labelNodes = domNode.querySelector(".label")?.children;
        if (!labelNodes) {
            throw "label nodes not found";
        }
        Array.from(labelNodes).forEach((node) => {
            const label = node.textContent;
            if (!label) {
                return;
            }
            const id = nanoid();
            const { transformX: textTransformX, transformY: textTransformY } = getTransformAttr(node);
            const boundingBox = node.getBBox();
            const offsetY = 10;
            const textElement = createTextSkeleton(transformX + textTransformX, transformY + textTransformY + offsetY, label, {
                width: boundingBox.width,
                height: boundingBox.height,
                id,
                groupId,
                metadata: { classId },
            });
            text.push(textElement);
        });
    });
    return { nodes, lines, text };
};
// update arrow position by certain offset for triangle and diamond arrow head types
// as mermaid calculates the position until the start of arrowhead
// for reference - https://github.com/mermaid-js/mermaid/issues/5417
const adjustArrowPosition = (direction, arrow) => {
    // The arrowhead shapes where we need to update the position by a 16px offset
    const arrowHeadShapes = ["triangle_outline", "diamond", "diamond_outline"];
    const shouldUpdateStartArrowhead = arrow.startArrowhead && arrowHeadShapes.includes(arrow.startArrowhead);
    const shouldUpdateEndArrowhead = arrow.endArrowhead && arrowHeadShapes.includes(arrow.endArrowhead);
    if (!shouldUpdateEndArrowhead && !shouldUpdateStartArrowhead) {
        return arrow;
    }
    if (shouldUpdateStartArrowhead) {
        if (direction === "LR") {
            arrow.startX -= MERMAID_ARROW_HEAD_OFFSET;
        }
        else if (direction === "RL") {
            arrow.startX += MERMAID_ARROW_HEAD_OFFSET;
        }
        else if (direction === "TB") {
            arrow.startY -= MERMAID_ARROW_HEAD_OFFSET;
        }
        else if (direction === "BT") {
            arrow.startY += MERMAID_ARROW_HEAD_OFFSET;
        }
    }
    if (shouldUpdateEndArrowhead) {
        if (direction === "LR") {
            arrow.endX += MERMAID_ARROW_HEAD_OFFSET;
        }
        else if (direction === "RL") {
            arrow.endX -= MERMAID_ARROW_HEAD_OFFSET;
        }
        else if (direction === "TB") {
            arrow.endY += MERMAID_ARROW_HEAD_OFFSET;
        }
        else if (direction === "BT") {
            arrow.endY -= MERMAID_ARROW_HEAD_OFFSET;
        }
    }
    return arrow;
};
const parseRelations = (relations, classNodes, containerEl, direction) => {
    const edges = containerEl.querySelector(".edgePaths")?.children;
    if (!edges) {
        throw new Error("No Edges found!");
    }
    const arrows = [];
    const text = [];
    relations.forEach((relationNode, index) => {
        const { id1, id2, relation } = relationNode;
        const node1 = classNodes.find((node) => node.id === id1);
        const node2 = classNodes.find((node) => node.id === id2);
        const strokeStyle = getStrokeStyle(relation.lineType);
        const startArrowhead = getArrowhead(relation.type1);
        const endArrowhead = getArrowhead(relation.type2);
        const edgePositionData = computeEdgePositions(edges[index]);
        const arrowSkeletion = createArrowSkeletion(edgePositionData.startX, edgePositionData.startY, edgePositionData.endX, edgePositionData.endY, {
            strokeStyle,
            startArrowhead,
            endArrowhead,
            label: relationNode.title ? { text: relationNode.title } : undefined,
            start: { type: "rectangle", id: node1.id },
            end: { type: "rectangle", id: node2.id },
        });
        const arrow = adjustArrowPosition(direction, arrowSkeletion);
        arrows.push(arrow);
        // Add cardianlities and Multiplicities
        const { relationTitle1, relationTitle2 } = relationNode;
        const offsetX = 20;
        const offsetY = 15;
        const directionOffset = 15;
        let x;
        let y;
        if (relationTitle1 && relationTitle1 !== "none") {
            switch (direction) {
                case "TB":
                    x = arrow.startX - offsetX;
                    if (arrow.endX < arrow.startX) {
                        x -= directionOffset;
                    }
                    y = arrow.startY + offsetY;
                    break;
                case "BT":
                    x = arrow.startX + offsetX;
                    if (arrow.endX > arrow.startX) {
                        x += directionOffset;
                    }
                    y = arrow.startY - offsetY;
                    break;
                case "LR":
                    x = arrow.startX + offsetX;
                    y = arrow.startY + offsetY;
                    if (arrow.endY > arrow.startY) {
                        y += directionOffset;
                    }
                    break;
                case "RL":
                    x = arrow.startX - offsetX;
                    y = arrow.startY - offsetY;
                    if (arrow.startY > arrow.endY) {
                        y -= directionOffset;
                    }
                    break;
                default:
                    x = arrow.startX - offsetX;
                    y = arrow.startY + offsetY;
            }
            const relationTitleElement = createTextSkeleton(x, y, relationTitle1, {
                fontSize: 16,
            });
            text.push(relationTitleElement);
        }
        if (relationTitle2 && relationTitle2 !== "none") {
            switch (direction) {
                case "TB":
                    x = arrow.endX + offsetX;
                    if (arrow.endX < arrow.startX) {
                        x += directionOffset;
                    }
                    y = arrow.endY - offsetY;
                    break;
                case "BT":
                    x = arrow.endX - offsetX;
                    if (arrow.endX > arrow.startX) {
                        x -= directionOffset;
                    }
                    y = arrow.endY + offsetY;
                    break;
                case "LR":
                    x = arrow.endX - offsetX;
                    y = arrow.endY - offsetY;
                    if (arrow.endY > arrow.startY) {
                        y -= directionOffset;
                    }
                    break;
                case "RL":
                    x = arrow.endX + offsetX;
                    y = arrow.endY + offsetY;
                    if (arrow.startY > arrow.endY) {
                        y += directionOffset;
                    }
                    break;
                default:
                    x = arrow.endX + offsetX;
                    y = arrow.endY - offsetY;
            }
            const relationTitleElement = createTextSkeleton(x, y, relationTitle2, {
                fontSize: 16,
            });
            text.push(relationTitleElement);
        }
    });
    return { arrows, text };
};
const parseNotes = (notes, containerEl, classNodes) => {
    const noteContainers = [];
    const connectors = [];
    notes.forEach((note) => {
        const { id, text, class: classId } = note;
        const node = containerEl.querySelector(`#${id}`);
        if (!node) {
            throw new Error(`Node with id ${id} not found!`);
        }
        const { transformX, transformY } = getTransformAttr(node);
        const rect = node.firstChild;
        const container = createContainerSkeletonFromSVG(rect, "rectangle", {
            id,
            subtype: "note",
            label: { text },
        });
        Object.assign(container, {
            x: container.x + transformX,
            y: container.y + transformY,
        });
        noteContainers.push(container);
        if (classId) {
            const classNode = classNodes.find((node) => node.id === classId);
            if (!classNode) {
                throw new Error(`class node with id ${classId} not found!`);
            }
            const startX = container.x + (container.width || 0) / 2;
            const startY = container.y + (container.height || 0);
            const endX = startX;
            const endY = classNode.y;
            const connector = createArrowSkeletion(startX, startY, endX, endY, {
                strokeStyle: "dotted",
                startArrowhead: null,
                endArrowhead: null,
                start: { id: container.id, type: "rectangle" },
                end: { id: classNode.id, type: "rectangle" },
            });
            connectors.push(connector);
        }
    });
    return { notes: noteContainers, connectors };
};
export const parseMermaidClassDiagram = (diagram, containerEl) => {
    diagram.parse();
    //@ts-ignore
    const mermaidParser = diagram.parser.yy;
    const direction = mermaidParser.getDirection();
    const nodes = [];
    const lines = [];
    const text = [];
    const classNodes = [];
    const namespaces = mermaidParser.getNamespaces();
    const classes = mermaidParser.getClasses();
    if (Object.keys(classes).length) {
        const classData = parseClasses(classes, containerEl);
        nodes.push(classData.nodes);
        lines.push(...classData.lines);
        text.push(...classData.text);
        classNodes.push(...classData.nodes);
    }
    const relations = mermaidParser.getRelations();
    const { arrows, text: relationTitles } = parseRelations(relations, classNodes, containerEl, direction);
    const { notes, connectors } = parseNotes(mermaidParser.getNotes(), containerEl, classNodes);
    nodes.push(notes);
    arrows.push(...connectors);
    text.push(...relationTitles);
    return { type: "class", nodes, lines, arrows, text, namespaces };
};
