/*
 * Copyright 2026, Jaime Terrats.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { computeDynamicWidth, computeGraphLayout } from '../utils/dagre-layout.js';
import { determineSide, distributeConnectionPoints, routeEdge, sideCenter } from '../utils/edge-router.js';
export function renderDiagram(data, format) {
    if (format === 'mermaid') {
        return renderMermaid(data);
    }
    if (format === 'drawio') {
        return renderDrawio(data);
    }
    return renderMaxGraphJson(data);
}
// ─── Mermaid rendering ──────────────────────────────────────────────────────
function renderMermaid(data) {
    switch (data.type) {
        case 'sequence':
            return renderSequenceMermaid(data);
        case 'class':
            return renderClassMermaid(data);
        case 'er':
            return renderErMermaid(data);
        case 'architecture':
        case 'flowchart':
        default:
            return renderFlowchartMermaid(data);
    }
}
function renderFlowchartMermaid(data) {
    const direction = data.direction ?? 'TB';
    const lines = [`flowchart ${direction}`];
    if (data.title) {
        lines.unshift(`---\ntitle: ${data.title}\n---`);
    }
    const groups = new Map();
    const ungrouped = [];
    for (const node of data.nodes) {
        if (node.group) {
            const list = groups.get(node.group) ?? [];
            list.push(node);
            groups.set(node.group, list);
        }
        else {
            ungrouped.push(node);
        }
    }
    for (const node of ungrouped) {
        lines.push(`  ${formatFlowchartNode(node)}`);
    }
    for (const [groupName, nodes] of groups) {
        lines.push(`  subgraph ${groupName}`);
        for (const node of nodes) {
            lines.push(`    ${formatFlowchartNode(node)}`);
        }
        lines.push('  end');
    }
    for (const edge of data.edges) {
        lines.push(`  ${formatFlowchartEdge(edge)}`);
    }
    return lines.join('\n');
}
function sanitizeMermaidLabel(label) {
    // Mermaid does not allow quotes or special chars inside curly/bracket delimiters.
    // Strip or escape problematic characters that cause parse errors.
    return label.replace(/"/g, "'").replace(/[{}]/g, '');
}
function formatFlowchartNode(node) {
    const label = sanitizeMermaidLabel(node.label);
    switch (node.shape) {
        case 'rounded':
            return `${node.id}("${label}")`;
        case 'circle':
            return `${node.id}(("${label}"))`;
        case 'diamond':
            return `${node.id}{"${label}"}`;
        case 'hexagon':
            return `${node.id}{{"${label}"}}`;
        case 'stadium':
            return `${node.id}(["${label}"])`;
        case 'rectangle':
        default:
            return `${node.id}["${label}"]`;
    }
}
function formatFlowchartEdge(edge) {
    const arrow = buildArrow(edge);
    const label = edge.label ? `|${edge.label}|` : '';
    return `${edge.from} ${arrow}${label} ${edge.to}`;
}
function buildArrow(edge) {
    const head = edge.arrowhead === 'none' ? '---' : '-->';
    switch (edge.style) {
        case 'dashed':
            return edge.arrowhead === 'none' ? '-.-' : '-.->';
        case 'dotted':
            return edge.arrowhead === 'none' ? '-.-' : '-.->';
        case 'thick':
            return edge.arrowhead === 'none' ? '===' : '==>';
        case 'solid':
        default:
            return head;
    }
}
function renderSequenceMermaid(data) {
    const lines = ['sequenceDiagram'];
    if (data.title) {
        lines.unshift(`---\ntitle: ${data.title}\n---`);
    }
    if (data.participants && data.participants.length > 0) {
        for (const p of data.participants) {
            lines.push(`  participant ${p}`);
        }
    }
    if (data.messages) {
        for (const msg of data.messages) {
            const arrow = sequenceArrow(msg.type);
            lines.push(`  ${msg.from}${arrow}${msg.to}: ${msg.label}`);
        }
    }
    return lines.join('\n');
}
function sequenceArrow(type) {
    switch (type) {
        case 'async':
            return '-)';
        case 'reply':
            return '-->>)';
        case 'sync':
        default:
            return '->>';
    }
}
function renderClassMermaid(data) {
    const lines = ['classDiagram'];
    if (data.title) {
        lines.unshift(`---\ntitle: ${data.title}\n---`);
    }
    for (const node of data.nodes) {
        lines.push(`  class ${node.id} {`);
        lines.push(`    ${node.label}`);
        lines.push('  }');
    }
    for (const edge of data.edges) {
        const label = edge.label ? ` : ${edge.label}` : '';
        lines.push(`  ${edge.from} --> ${edge.to}${label}`);
    }
    return lines.join('\n');
}
function renderErMermaid(data) {
    const lines = ['erDiagram'];
    if (data.title) {
        lines.unshift(`---\ntitle: ${data.title}\n---`);
    }
    for (const edge of data.edges) {
        const label = edge.label ?? 'relates-to';
        lines.push(`  ${edge.from} ||--o{ ${edge.to} : "${label}"`);
    }
    return lines.join('\n');
}
// ─── maxGraph JSON rendering ────────────────────────────────────────────────
function renderMaxGraphJson(data) {
    const json = buildMaxGraphJson(data);
    return JSON.stringify(json, null, 2);
}
function buildMaxGraphJson(data) {
    const NODE_WIDTH = 120;
    const NODE_HEIGHT = 60;
    const H_SPACING = 180;
    const V_SPACING = 100;
    const isVertical = !data.direction || data.direction === 'TB' || data.direction === 'BT';
    const nodePositions = new Map();
    const nodes = data.nodes.map((node, index) => {
        const x = isVertical ? (index % 4) * H_SPACING : index * H_SPACING;
        const y = isVertical ? Math.floor(index / 4) * V_SPACING : (index % 3) * V_SPACING;
        nodePositions.set(node.id, { x, y });
        const mgNode = {
            id: node.id,
            value: node.label,
            x,
            y,
            width: NODE_WIDTH,
            height: NODE_HEIGHT,
        };
        if (node.style) {
            mgNode.style = node.style;
        }
        if (node.group) {
            mgNode.parent = node.group;
        }
        return mgNode;
    });
    const edges = data.edges.map((edge, index) => {
        const mgEdge = {
            id: `e${index}`,
            source: edge.from,
            target: edge.to,
        };
        if (edge.label) {
            mgEdge.value = edge.label;
        }
        if (edge.style) {
            mgEdge.style = edgeStyleToMaxGraph(edge.style);
        }
        return mgEdge;
    });
    return { nodes, edges };
}
function edgeStyleToMaxGraph(style) {
    switch (style) {
        case 'dashed':
            return 'dashed=1';
        case 'dotted':
            return 'dashed=1;dashPattern=1 2';
        case 'thick':
            return 'strokeWidth=3';
        default:
            return '';
    }
}
// ─── drawio XML rendering ──────────────────────────────────────────────────────
async function renderDrawio(data) {
    if (data.type === 'er') {
        return renderErDrawio(data);
    }
    return renderFlowchartDrawio(data);
}
function xmlEscape(str) {
    return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&apos;');
}
function getErTableColors(group) {
    switch (group) {
        case 'custom':
        case 'managed':
            return { fill: '#fff8e1', stroke: '#f57f17' };
        case 'junction':
            return { fill: '#eceff1', stroke: '#546e7a' };
        case 'extension':
            return { fill: '#e8f5e9', stroke: '#2e7d32' };
        case 'integration':
            return { fill: '#fce4ec', stroke: '#c62828' };
        case 'standard':
        default:
            return { fill: '#e3f2fd', stroke: '#1565c0' };
    }
}
// eslint-disable-next-line complexity
async function renderErDrawio(data) {
    const BASE_TABLE_WIDTH = 260;
    const HEADER_HEIGHT = 30;
    const ROW_HEIGHT = 24;
    const MARKER_WIDTH = 36;
    // Build LayoutEdge array for dynamic width computation
    const layoutEdges = data.edges.map((e) => ({ from: e.from, to: e.to }));
    // Compute dynamic width per node based on connection degree
    const nodeWidths = new Map();
    for (const node of data.nodes) {
        const dynamicWidth = computeDynamicWidth(node.id, layoutEdges, BASE_TABLE_WIDTH, 80, 3);
        nodeWidths.set(node.id, dynamicWidth);
    }
    // Compute table height for a node
    const computeTableHeight = (node) => {
        const lines = node.label.split('\n');
        let headerEnd = 0;
        for (let li = 0; li < lines.length; li++) {
            if (lines[li].startsWith('PK ') || lines[li].startsWith('FK ') || (li > 1 && !lines[li].startsWith('('))) {
                headerEnd = li;
                break;
            }
            if (li === lines.length - 1)
                headerEnd = lines.length;
        }
        const headerLineCount = lines.slice(0, headerEnd).length;
        const headerHeight = headerLineCount > 1 ? 44 : HEADER_HEIGHT;
        const fieldCount = lines.slice(headerEnd).length;
        return headerHeight + ROW_HEIGHT * fieldCount;
    };
    // Phase 1: Dagre layout engine — hub entities naturally get central ranks in TB mode
    const layoutNodes = data.nodes.map((node) => ({
        id: node.id,
        width: nodeWidths.get(node.id) ?? BASE_TABLE_WIDTH,
        height: computeTableHeight(node),
    }));
    const positions = await computeGraphLayout(layoutNodes, layoutEdges, {
        rankdir: 'TB',
        nodesep: 100,
        ranksep: 180,
        marginx: 60,
        marginy: 60,
        align: 'UL',
    });
    const nodePositions = new Map();
    for (const node of data.nodes) {
        const pos = positions.get(node.id);
        if (pos) {
            nodePositions.set(node.id, { x: pos.x, y: pos.y });
        }
        else {
            nodePositions.set(node.id, { x: 0, y: 0 });
        }
    }
    const sortedNodes = [...data.nodes];
    // Build obstacle rectangles for edge routing (one per table)
    const nodeRects = new Map();
    for (const node of sortedNodes) {
        const pos = nodePositions.get(node.id) ?? { x: 0, y: 0 };
        const w = nodeWidths.get(node.id) ?? BASE_TABLE_WIDTH;
        const h = computeTableHeight(node);
        nodeRects.set(node.id, { x: pos.x, y: pos.y, width: w, height: h });
    }
    // Compute edge routes using the orthogonal router
    const edgeRoutes = new Map();
    const obstacles = [...nodeRects.values()];
    // Pre-compute connection sides and distribute ports per node
    const nodeEdgeInfos = new Map();
    for (let ei = 0; ei < data.edges.length; ei++) {
        const edge = data.edges[ei];
        const srcRect = nodeRects.get(edge.from);
        const tgtRect = nodeRects.get(edge.to);
        if (!srcRect || !tgtRect)
            continue;
        const tgtCenter = { x: tgtRect.x + tgtRect.width / 2, y: tgtRect.y + tgtRect.height / 2 };
        const srcCenter = { x: srcRect.x + srcRect.width / 2, y: srcRect.y + srcRect.height / 2 };
        const srcInfo = nodeEdgeInfos.get(edge.from) ?? [];
        srcInfo.push({ edgeIndex: ei, targetCenter: tgtCenter, isSource: true });
        nodeEdgeInfos.set(edge.from, srcInfo);
        const tgtInfo = nodeEdgeInfos.get(edge.to) ?? [];
        tgtInfo.push({ edgeIndex: ei, targetCenter: srcCenter, isSource: false });
        nodeEdgeInfos.set(edge.to, tgtInfo);
    }
    // Distribute connection points and route edges
    const edgePorts = new Map();
    for (const [nodeId, edgeInfos] of nodeEdgeInfos) {
        const rect = nodeRects.get(nodeId);
        if (!rect)
            continue;
        const connEdges = edgeInfos.map((info) => ({
            targetCenter: info.targetCenter,
            side: determineSide(rect, info.targetCenter),
        }));
        const ports = distributeConnectionPoints(rect, connEdges);
        for (let i = 0; i < edgeInfos.length; i++) {
            const info = edgeInfos[i];
            const key = `${info.edgeIndex}`;
            const existing = edgePorts.get(key) ?? { source: { x: 0, y: 0 }, target: { x: 0, y: 0 } };
            if (info.isSource) {
                existing.source = ports[i];
            }
            else {
                existing.target = ports[i];
            }
            edgePorts.set(key, existing);
        }
    }
    // Route each edge
    for (let ei = 0; ei < data.edges.length; ei++) {
        const edge = data.edges[ei];
        const srcRect = nodeRects.get(edge.from);
        const tgtRect = nodeRects.get(edge.to);
        if (!srcRect || !tgtRect)
            continue;
        const ports = edgePorts.get(`${ei}`);
        const sourcePort = ports?.source ??
            sideCenter(srcRect, determineSide(srcRect, { x: tgtRect.x + tgtRect.width / 2, y: tgtRect.y + tgtRect.height / 2 }));
        const targetPort = ports?.target ??
            sideCenter(tgtRect, determineSide(tgtRect, { x: srcRect.x + srcRect.width / 2, y: srcRect.y + srcRect.height / 2 }));
        // Exclude source and target from obstacles for this edge
        const edgeObstacles = obstacles.filter((r) => r !== srcRect && r !== tgtRect);
        const result = routeEdge(srcRect, tgtRect, sourcePort, targetPort, edgeObstacles);
        edgeRoutes.set(ei, result.points);
    }
    const tableCellIds = new Map();
    const cells = [];
    for (let i = 0; i < sortedNodes.length; i++) {
        const node = sortedNodes[i];
        const pos = nodePositions.get(node.id) ?? { x: 0, y: 0 };
        const x = pos.x;
        const y = pos.y;
        const tableWidth = nodeWidths.get(node.id) ?? BASE_TABLE_WIDTH;
        const fieldWidth = tableWidth - MARKER_WIDTH;
        const lines = node.label.split('\n');
        // First line(s) before PK/FK are header (name + optional API name)
        let headerEnd = 0;
        for (let li = 0; li < lines.length; li++) {
            if (lines[li].startsWith('PK ') || lines[li].startsWith('FK ') || (li > 1 && !lines[li].startsWith('('))) {
                headerEnd = li;
                break;
            }
            if (li === lines.length - 1)
                headerEnd = lines.length;
        }
        const headerLines = lines.slice(0, headerEnd);
        const tableName = headerLines.join('&#10;') || node.id;
        const fields = lines.slice(headerEnd);
        const headerSize = headerLines.length > 1 ? 44 : HEADER_HEIGHT;
        const tableHeight = headerSize + ROW_HEIGHT * fields.length;
        const tableId = `t_${i}`;
        tableCellIds.set(node.id, tableId);
        const colors = getErTableColors(node.group);
        const tableStyle = `shape=table;startSize=${headerSize};container=1;collapsible=0;` +
            'childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;verticalAlign=top;fontSize=11;' +
            `fillColor=${colors.fill};strokeColor=${colors.stroke};fontColor=#000000;resizable=0;`;
        cells.push(`      <mxCell id="${tableId}" value="${xmlEscape(tableName)}" ` + `style="${tableStyle}" vertex="1" parent="1">`);
        cells.push(`        <mxGeometry x="${x}" y="${y}" width="${tableWidth}" height="${tableHeight}" as="geometry" />`);
        cells.push('      </mxCell>');
        for (let fi = 0; fi < fields.length; fi++) {
            const fieldLine = fields[fi];
            const rowId = `${tableId}_r${fi}`;
            const markerId = `${rowId}_m`;
            const fieldId = `${rowId}_f`;
            // Parse marker (PK/FK) and field name
            let marker = '';
            let fieldName = fieldLine;
            if (fieldLine.startsWith('PK ')) {
                marker = 'PK';
                fieldName = fieldLine.slice(3);
            }
            else if (fieldLine.startsWith('FK ')) {
                marker = 'FK';
                fieldName = fieldLine.slice(3);
            }
            // Row container
            const rowStyle = 'shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;' +
                'fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];' +
                'portConstraint=eastwest;fontSize=10;';
            cells.push(`      <mxCell id="${rowId}" value="" style="${rowStyle}" vertex="1" parent="${tableId}">`);
            cells.push(`        <mxGeometry y="${headerSize + fi * ROW_HEIGHT}" width="${tableWidth}" height="${ROW_HEIGHT}" as="geometry" />`);
            cells.push('      </mxCell>');
            // Marker cell
            const markerStyle = 'shape=partialRectangle;overflow=hidden;connectable=0;fillColor=none;' +
                'top=0;left=0;bottom=0;right=1;fontStyle=1;fontSize=10;align=center;';
            cells.push(`      <mxCell id="${markerId}" value="${xmlEscape(marker)}" style="${markerStyle}" vertex="1" parent="${rowId}">`);
            cells.push(`        <mxGeometry width="${MARKER_WIDTH}" height="${ROW_HEIGHT}" as="geometry" />`);
            cells.push('      </mxCell>');
            // Field cell
            const fieldStyle = 'shape=partialRectangle;overflow=hidden;connectable=0;fillColor=none;' +
                'top=0;left=1;bottom=0;right=0;fontSize=10;align=left;spacingLeft=4;';
            cells.push(`      <mxCell id="${fieldId}" value="${xmlEscape(fieldName)}" style="${fieldStyle}" vertex="1" parent="${rowId}">`);
            cells.push(`        <mxGeometry x="${MARKER_WIDTH}" width="${fieldWidth}" height="${ROW_HEIGHT}" as="geometry" />`);
            cells.push('      </mxCell>');
        }
    }
    // Find PK row for a given table to use as source connection point
    const findPkRowId = (tableIndex) => {
        const tableId = `t_${tableIndex}`;
        return `${tableId}_r0`;
    };
    // Edges — connect PK row (source) to FK row (target) for clean routing
    // No explicit exit/entry coordinates — let draw.io auto-route via portConstraint=eastwest
    for (let ei = 0; ei < data.edges.length; ei++) {
        const edge = data.edges[ei];
        const edgeId = `e_${ei}`;
        // Source: connect from the PK row of the source table
        const sourceTableId = tableCellIds.get(edge.from);
        const sourceIndex = sortedNodes.findIndex((n) => n.id === edge.from);
        const sourceRowId = sourceIndex >= 0 ? findPkRowId(sourceIndex) : sourceTableId ?? edge.from;
        // Phase 5: Enhanced FK-row connection with priority-based matching
        let targetRowId = tableCellIds.get(edge.to) ?? edge.to;
        const targetTableId = tableCellIds.get(edge.to);
        if (targetTableId) {
            const targetNode = sortedNodes.find((n) => n.id === edge.to);
            if (targetNode) {
                const tLines = targetNode.label.split('\n');
                let tHeaderEnd = 0;
                for (let li = 0; li < tLines.length; li++) {
                    if (tLines[li].startsWith('PK ') || tLines[li].startsWith('FK ') || (li > 1 && !tLines[li].startsWith('('))) {
                        tHeaderEnd = li;
                        break;
                    }
                    if (li === tLines.length - 1)
                        tHeaderEnd = tLines.length;
                }
                const tFields = tLines.slice(tHeaderEnd);
                const srcNorm = edge.from.toLowerCase().replace(/[^a-z0-9]/g, '');
                // Priority 1: Exact FK field name contains source table name
                let matchedRowIndex = -1;
                for (let fi = 0; fi < tFields.length; fi++) {
                    if (tFields[fi].startsWith('FK ')) {
                        const fieldNorm = tFields[fi].toLowerCase().replace(/[^a-z0-9]/g, '');
                        if (fieldNorm.includes(srcNorm)) {
                            matchedRowIndex = fi;
                            break;
                        }
                    }
                }
                // Priority 2: Edge label matches a FK field name
                if (matchedRowIndex < 0 && edge.label) {
                    const labelNorm = edge.label.toLowerCase().replace(/[^a-z0-9]/g, '');
                    for (let fi = 0; fi < tFields.length; fi++) {
                        if (tFields[fi].startsWith('FK ')) {
                            const fieldNorm = tFields[fi].toLowerCase().replace(/[^a-z0-9]/g, '');
                            if (fieldNorm.includes(labelNorm) || labelNorm.includes(fieldNorm)) {
                                matchedRowIndex = fi;
                                break;
                            }
                        }
                    }
                }
                // Priority 3: First FK row fallback
                if (matchedRowIndex < 0) {
                    for (let fi = 0; fi < tFields.length; fi++) {
                        if (tFields[fi].startsWith('FK ')) {
                            matchedRowIndex = fi;
                            break;
                        }
                    }
                }
                if (matchedRowIndex >= 0) {
                    targetRowId = `${targetTableId}_r${matchedRowIndex}`;
                }
            }
        }
        // Phase 3: Cardinality markers derived from edge metadata and node classification
        const { startArrow, endArrow } = determineCardinality(edge, sortedNodes);
        // Phase 2: orthogonalEdgeStyle — no hardcoded exit/entry coordinates
        const edgeStyle = 'edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;' +
            'rounded=1;strokeColor=#5C5C5C;fontSize=10;' +
            `startArrow=${startArrow};startSize=12;endArrow=${endArrow};endSize=12;` +
            'labelBackgroundColor=#FFFFFF;fontColor=#333333;';
        const labelAttr = edge.label ? ` value="${xmlEscape(edge.label)}"` : ' value=""';
        // Include routed waypoints in the edge geometry
        const waypoints = edgeRoutes.get(ei);
        if (waypoints && waypoints.length > 2) {
            // Intermediate waypoints (exclude first/last which are source/target ports)
            const intermediatePoints = waypoints.slice(1, -1);
            const pointsXml = intermediatePoints
                .map((p) => `          <mxPoint x="${Math.round(p.x)}" y="${Math.round(p.y)}" />`)
                .join('\n');
            cells.push(`      <mxCell id="${edgeId}"${labelAttr} style="${edgeStyle}" edge="1" source="${sourceRowId}" target="${targetRowId}" parent="1">`);
            cells.push('        <mxGeometry relative="1" as="geometry">');
            cells.push('          <Array as="points">');
            cells.push(pointsXml);
            cells.push('          </Array>');
            cells.push('        </mxGeometry>');
            cells.push('      </mxCell>');
        }
        else {
            cells.push(`      <mxCell id="${edgeId}"${labelAttr} style="${edgeStyle}" edge="1" source="${sourceRowId}" target="${targetRowId}" parent="1">`);
            cells.push('        <mxGeometry relative="1" as="geometry" />');
            cells.push('      </mxCell>');
        }
    }
    return wrapDrawioXml(cells, data.title);
}
/**
 * Phase 3: Determine cardinality markers based on edge metadata and target node classification.
 */
function determineCardinality(edge, nodes) {
    const targetNode = nodes.find((n) => n.id === edge.to);
    // 1:1 extension detection
    if (
    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
    edge.label?.includes('1:1') ||
        targetNode?.group === 'extension' ||
        targetNode?.id.toLowerCase().includes('extension')) {
        return { startArrow: 'ERmandOne', endArrow: 'ERmandOne' };
    }
    // Optional 1:1 (dashed style)
    if (edge.style === 'dashed') {
        return { startArrow: 'ERmandOne', endArrow: 'ERzeroToOne' };
    }
    // Weak optional / 0..N (dotted style)
    if (edge.style === 'dotted') {
        return { startArrow: 'ERmandOne', endArrow: 'ERzeroToMany' };
    }
    // N:M junction detection
    if (targetNode?.group === 'junction') {
        return { startArrow: 'ERoneToMany', endArrow: 'ERoneToMany' };
    }
    // Default: 1:N mandatory FK
    return { startArrow: 'ERmandOne', endArrow: 'ERoneToMany' };
}
function renderFlowchartDrawio(data) {
    const NODE_WIDTH = 120;
    const NODE_HEIGHT = 60;
    const H_SPACING = 200;
    const V_SPACING = 120;
    const isVertical = !data.direction || data.direction === 'TB' || data.direction === 'BT';
    const cols = Math.max(1, Math.ceil(Math.sqrt(data.nodes.length)));
    const cells = [];
    const nodeIds = new Map();
    for (let i = 0; i < data.nodes.length; i++) {
        const node = data.nodes[i];
        const cellId = `t_${i}`;
        nodeIds.set(node.id, cellId);
        const col = isVertical ? i % cols : i;
        const row = isVertical ? Math.floor(i / cols) : i % cols;
        const x = col * H_SPACING;
        const y = row * V_SPACING;
        let style = 'rounded=1;whiteSpace=wrap;html=1;';
        if (node.shape === 'diamond') {
            style = 'rhombus;whiteSpace=wrap;html=1;';
        }
        else if (node.shape === 'circle') {
            style = 'ellipse;whiteSpace=wrap;html=1;';
        }
        else if (node.shape === 'rectangle') {
            style = 'rounded=0;whiteSpace=wrap;html=1;';
        }
        cells.push(`      <mxCell id="${cellId}" value="${xmlEscape(node.label)}" style="${style}" vertex="1" parent="1">`);
        cells.push(`        <mxGeometry x="${x}" y="${y}" width="${NODE_WIDTH}" height="${NODE_HEIGHT}" as="geometry" />`);
        cells.push('      </mxCell>');
    }
    for (let ei = 0; ei < data.edges.length; ei++) {
        const edge = data.edges[ei];
        const edgeId = `e_${ei}`;
        const sourceId = nodeIds.get(edge.from) ?? edge.from;
        const targetId = nodeIds.get(edge.to) ?? edge.to;
        let edgeStyle = 'rounded=1;orthogonalLoop=1;';
        if (edge.style === 'dashed') {
            edgeStyle += 'dashed=1;';
        }
        else if (edge.style === 'dotted') {
            edgeStyle += 'dashed=1;dashPattern=1 2;';
        }
        const labelAttr = edge.label ? ` value="${xmlEscape(edge.label)}"` : ' value=""';
        cells.push(`      <mxCell id="${edgeId}"${labelAttr} style="${edgeStyle}" edge="1" source="${sourceId}" target="${targetId}" parent="1">`);
        cells.push('        <mxGeometry relative="1" as="geometry" />');
        cells.push('      </mxCell>');
    }
    return wrapDrawioXml(cells, data.title);
}
function wrapDrawioXml(cells, title) {
    const diagramName = title ? xmlEscape(title) : 'Diagram';
    const lines = [
        '<mxfile>',
        `  <diagram name="${diagramName}">`,
        '    <mxGraphModel>',
        '      <root>',
        '      <mxCell id="0" />',
        '      <mxCell id="1" parent="0" />',
        ...cells,
        '      </root>',
        '    </mxGraphModel>',
        '  </diagram>',
        '</mxfile>',
    ];
    return lines.join('\n');
}
//# sourceMappingURL=diagram-renderer.js.map