/*
 * 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 { readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { Messages } from '@salesforce/core';
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
import { parseXml, serializeXml, getElementsByTagName, getAttribute, setAttribute, getFirstChild, getFloatAttr, } from '../../../utils/xml-parser.js';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@jterrats/setup-agents', 'setup-agents.diagram.migrate');
function collectTables(cells) {
    const tables = new Map();
    for (const cell of cells) {
        const style = getAttribute(cell, 'style');
        if (style.includes('shape=table') && getAttribute(cell, 'vertex') === '1' && getAttribute(cell, 'parent') === '1') {
            const geo = getFirstChild(cell, 'mxGeometry');
            if (geo) {
                const id = getAttribute(cell, 'id');
                tables.set(id, { id, geo, w: getFloatAttr(geo, 'width', 260), h: getFloatAttr(geo, 'height', 200) });
                if (!style.includes('verticalAlign='))
                    setAttribute(cell, 'style', style + ';verticalAlign=top');
            }
        }
    }
    return tables;
}
function collectRows(cells, tableMap) {
    const rowMap = new Map();
    for (const cell of cells) {
        const style = getAttribute(cell, 'style');
        const parentId = getAttribute(cell, 'parent');
        const cellId = getAttribute(cell, 'id');
        if (!style.includes('shape=tableRow') || !tableMap.has(parentId))
            continue;
        let marker = '';
        let field = '';
        for (const child of cells) {
            if (getAttribute(child, 'parent') !== cellId)
                continue;
            if (!getAttribute(child, 'style').includes('partialRectangle'))
                continue;
            const childGeo = getFirstChild(child, 'mxGeometry');
            const childW = childGeo ? getFloatAttr(childGeo, 'width') : 0;
            if (childW <= 40)
                marker = getAttribute(child, 'value');
            else
                field = getAttribute(child, 'value');
        }
        const rows = rowMap.get(parentId) ?? [];
        rows.push({ id: cellId, marker, field });
        rowMap.set(parentId, rows);
    }
    return rowMap;
}
function findParentTable(cellId, cells, tableMap) {
    for (const cell of cells) {
        if (getAttribute(cell, 'id') !== cellId)
            continue;
        const parent = getAttribute(cell, 'parent');
        if (tableMap.has(parent))
            return parent;
        if (parent && parent !== '1' && parent !== '0')
            return findParentTable(parent, cells, tableMap);
    }
    return null;
}
function getTableName(tableId, cells) {
    for (const cell of cells) {
        if (getAttribute(cell, 'id') === tableId)
            return getAttribute(cell, 'value').split('&#10;')[0].split('\n')[0];
    }
    return tableId;
}
function migrateEdges(cells, tableMap, rowMap) {
    let count = 0;
    for (const cell of cells) {
        if (getAttribute(cell, 'edge') !== '1')
            continue;
        const sourceId = getAttribute(cell, 'source');
        const targetId = getAttribute(cell, 'target');
        let style = getAttribute(cell, 'style');
        style = style
            .replace(/edgeStyle=[^;]+;?/g, '')
            .replace(/rounded=[^;]+;?/g, '')
            .replace(/jettySize=[^;]+;?/g, '')
            .replace(/orthogonalLoop=[^;]+;?/g, '')
            .replace(/labelBackgroundColor=[^;]+;?/g, '')
            .replace(/fontColor=[^;]+;?/g, '');
        style =
            'edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;rounded=1;labelBackgroundColor=#FFFFFF;fontColor=#333333;' +
                style;
        setAttribute(cell, 'style', style);
        if (tableMap.has(sourceId)) {
            const pkRow = (rowMap.get(sourceId) ?? []).find((r) => r.marker === 'PK');
            if (pkRow)
                setAttribute(cell, 'source', pkRow.id);
        }
        const resolvedSrc = tableMap.has(sourceId) ? sourceId : findParentTable(sourceId, cells, tableMap);
        if (tableMap.has(targetId) && resolvedSrc) {
            const tgtRows = rowMap.get(targetId) ?? [];
            const srcNorm = getTableName(resolvedSrc, cells)
                .toLowerCase()
                .replace(/[^a-z0-9]/g, '');
            const fkRow = tgtRows.find((r) => r.marker === 'FK' &&
                r.field
                    .toLowerCase()
                    .replace(/[^a-z0-9]/g, '')
                    .includes(srcNorm)) ?? tgtRows.find((r) => r.marker === 'FK');
            if (fkRow)
                setAttribute(cell, 'target', fkRow.id);
        }
        count++;
    }
    return count;
}
async function relayoutTables(tableMap, cells, nodesep, ranksep) {
    const nodes = [...tableMap.values()].map((t) => ({ id: t.id, width: t.w, height: t.h }));
    const edges = [];
    for (const cell of cells) {
        if (getAttribute(cell, 'edge') !== '1')
            continue;
        const src = getAttribute(cell, 'source');
        const tgt = getAttribute(cell, 'target');
        const srcT = tableMap.has(src) ? src : findParentTable(src, cells, tableMap);
        const tgtT = tableMap.has(tgt) ? tgt : findParentTable(tgt, cells, tableMap);
        if (srcT && tgtT && srcT !== tgtT && !edges.some((e) => e.from === srcT && e.to === tgtT)) {
            edges.push({ from: srcT, to: tgtT });
        }
    }
    const { computeGraphLayout } = await import('../../../utils/dagre-layout.js');
    const positions = await computeGraphLayout(nodes, edges, {
        rankdir: 'TB',
        nodesep,
        ranksep,
        marginx: 40,
        marginy: 40,
    });
    let count = 0;
    for (const [id, pos] of positions) {
        const table = tableMap.get(id);
        if (table) {
            setAttribute(table.geo, 'x', String(pos.x));
            setAttribute(table.geo, 'y', String(pos.y));
            count++;
        }
    }
    return count;
}
export default class DiagramMigrate extends SfCommand {
    static summary = messages.getMessage('summary');
    static description = messages.getMessage('description');
    static examples = messages.getMessages('examples');
    static flags = {
        file: Flags.string({ char: 'f', summary: messages.getMessage('flags.file.summary'), required: true }),
        relayout: Flags.boolean({ summary: messages.getMessage('flags.relayout.summary'), default: false }),
        'node-sep': Flags.integer({ summary: messages.getMessage('flags.node-sep.summary'), default: 100 }),
        'rank-sep': Flags.integer({ summary: messages.getMessage('flags.rank-sep.summary'), default: 180 }),
    };
    async run() {
        const { flags } = await this.parse(DiagramMigrate);
        const filePath = resolve(process.cwd(), flags.file);
        const xml = readFileSync(filePath, 'utf-8');
        const doc = parseXml(xml);
        const diagrams = getElementsByTagName(doc, 'diagram');
        let totalEdgesMigrated = 0;
        let totalTablesRepositioned = 0;
        for (const diagram of diagrams) {
            const cells = getElementsByTagName(diagram, 'mxCell');
            const tableMap = collectTables(cells);
            const rowMap = collectRows(cells, tableMap);
            totalEdgesMigrated += migrateEdges(cells, tableMap, rowMap);
            if (flags.relayout && tableMap.size > 1) {
                // eslint-disable-next-line no-await-in-loop
                totalTablesRepositioned += await relayoutTables(tableMap, cells, flags['node-sep'], flags['rank-sep']);
            }
        }
        writeFileSync(filePath, serializeXml(doc));
        this.log(messages.getMessage('info.done', [
            filePath,
            String(totalEdgesMigrated),
            String(totalTablesRepositioned),
            String(diagrams.length),
        ]));
        return {
            file: filePath,
            pages: diagrams.length,
            tablesRepositioned: totalTablesRepositioned,
            edgesMigrated: totalEdgesMigrated,
        };
    }
}
//# sourceMappingURL=migrate.js.map