import { z } from 'zod';
import { addNode as manipAddNode, removeNode as manipRemoveNode, renameNode as manipRenameNode, addConnection as manipAddConnection, removeConnection as manipRemoveConnection, setNodePosition as manipSetNodePosition, setNodeLabel as manipSetNodeLabel, } from './manipulation/index.js';
import { findIsolatedNodes } from './query.js';
export const modifyParamsSchemas = {
    addNode: z.object({
        nodeId: z.string({ required_error: 'nodeId is required' }),
        nodeType: z.string({ required_error: 'nodeType is required' }),
        x: z.number().optional(),
        y: z.number().optional(),
    }),
    removeNode: z.object({
        nodeId: z.string({ required_error: 'nodeId is required' }),
    }),
    renameNode: z.object({
        oldId: z.string({ required_error: 'oldId is required' }),
        newId: z.string({ required_error: 'newId is required' }),
    }),
    addConnection: z.object({
        from: z.string({ required_error: 'from is required (format: "node.port")' }),
        to: z.string({ required_error: 'to is required (format: "node.port")' }),
    }),
    removeConnection: z.object({
        from: z.string({ required_error: 'from is required (format: "node.port")' }),
        to: z.string({ required_error: 'to is required (format: "node.port")' }),
    }),
    setNodePosition: z.object({
        nodeId: z.string({ required_error: 'nodeId is required' }),
        x: z.number({ required_error: 'x is required', invalid_type_error: 'x must be a number' }),
        y: z.number({ required_error: 'y is required', invalid_type_error: 'y must be a number' }),
    }),
    setNodeLabel: z.object({
        nodeId: z.string({ required_error: 'nodeId is required' }),
        label: z.string({ required_error: 'label is required' }),
    }),
};
export function validateModifyParams(operation, params) {
    const schema = modifyParamsSchemas[operation];
    if (!schema) {
        return { success: false, error: `Unknown operation: ${operation}` };
    }
    const result = schema.safeParse(params);
    if (!result.success) {
        const messages = result.error.issues.map((i) => i.message).join('; ');
        return { success: false, error: `${operation} params invalid: ${messages}` };
    }
    return { success: true };
}
export function applyModifyOperation(ast, operation, params) {
    const p = params;
    const warnings = [];
    const extraData = {};
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- AST manipulation functions use loose typing
    let modifiedAST = ast;
    switch (operation) {
        case 'addNode': {
            const nodeId = p.nodeId;
            const nodeType = p.nodeType;
            const nodeTypeExists = modifiedAST.nodeTypes.some((nt) => nt.name === nodeType || nt.functionName === nodeType);
            if (!nodeTypeExists) {
                warnings.push(`Node type "${nodeType}" is not defined in the file. ` +
                    `The node will be added but may not render until the type is defined.`);
            }
            let autoX = typeof p.x === 'number' ? p.x : undefined;
            let autoY = typeof p.y === 'number' ? p.y : undefined;
            if (autoX === undefined || autoY === undefined) {
                const positions = modifiedAST.instances
                    .map((inst) => inst.config)
                    .filter((c) => c !== undefined &&
                    c !== null &&
                    typeof c.x === 'number' &&
                    typeof c.y === 'number');
                if (positions.length > 0) {
                    const maxX = Math.max(...positions.map((pos) => pos.x));
                    if (autoX === undefined)
                        autoX = maxX + 180;
                    if (autoY === undefined)
                        autoY = 0;
                }
                else {
                    if (autoX === undefined)
                        autoX = 0;
                    if (autoY === undefined)
                        autoY = 0;
                }
            }
            modifiedAST = manipAddNode(modifiedAST, {
                type: 'NodeInstance',
                id: nodeId,
                nodeType,
                config: { x: autoX, y: autoY },
            });
            break;
        }
        case 'removeNode': {
            const nodeId = p.nodeId;
            const removedConnections = modifiedAST.connections
                .filter((c) => c.from.node === nodeId || c.to.node === nodeId)
                .map((c) => ({
                from: `${c.from.node}.${c.from.port}`,
                to: `${c.to.node}.${c.to.port}`,
            }));
            modifiedAST = manipRemoveNode(modifiedAST, nodeId);
            if (removedConnections.length > 0) {
                extraData.removedConnections = removedConnections;
            }
            break;
        }
        case 'renameNode': {
            modifiedAST = manipRenameNode(modifiedAST, p.oldId, p.newId);
            break;
        }
        case 'addConnection': {
            const from = p.from;
            const to = p.to;
            const [fromNode, fromPort] = from.split('.');
            const [toNode, toPort] = to.split('.');
            if (!fromPort || !toPort) {
                throw new Error('Connection format must be "node.port" (e.g., "Start.execute")');
            }
            const validNodes = [
                'Start',
                'Exit',
                ...modifiedAST.instances.map((i) => i.id),
            ];
            if (!validNodes.includes(fromNode)) {
                throw new Error(`Source node "${fromNode}" not found. Available: ${validNodes.join(', ')}`);
            }
            if (!validNodes.includes(toNode)) {
                throw new Error(`Target node "${toNode}" not found. Available: ${validNodes.join(', ')}`);
            }
            if (fromNode !== 'Start' && fromNode !== 'Exit') {
                const inst = modifiedAST.instances.find((i) => i.id === fromNode);
                const nt = modifiedAST.nodeTypes.find((t) => t.name === inst?.nodeType);
                if (nt && !nt.outputs[fromPort]) {
                    throw new Error(`Node "${fromNode}" has no output "${fromPort}". Available: ${Object.keys(nt.outputs).join(', ')}`);
                }
            }
            if (toNode !== 'Start' && toNode !== 'Exit') {
                const inst = modifiedAST.instances.find((i) => i.id === toNode);
                const nt = modifiedAST.nodeTypes.find((t) => t.name === inst?.nodeType);
                if (nt && !nt.inputs[toPort]) {
                    throw new Error(`Node "${toNode}" has no input "${toPort}". Available: ${Object.keys(nt.inputs).join(', ')}`);
                }
            }
            modifiedAST = manipAddConnection(modifiedAST, from, to);
            if (modifiedAST.options?.autoConnect) {
                modifiedAST = { ...modifiedAST, options: { ...modifiedAST.options, autoConnect: undefined } };
                warnings.push('autoConnect was disabled because connections were manually modified');
            }
            break;
        }
        case 'removeConnection': {
            modifiedAST = manipRemoveConnection(modifiedAST, p.from, p.to);
            if (modifiedAST.options?.autoConnect) {
                modifiedAST = { ...modifiedAST, options: { ...modifiedAST.options, autoConnect: undefined } };
                warnings.push('autoConnect was disabled because connections were manually modified');
            }
            const newlyIsolated = findIsolatedNodes(modifiedAST);
            if (newlyIsolated.length > 0) {
                extraData.newlyIsolatedNodes = newlyIsolated;
            }
            break;
        }
        case 'setNodePosition': {
            modifiedAST = manipSetNodePosition(modifiedAST, p.nodeId, p.x, p.y);
            break;
        }
        case 'setNodeLabel': {
            modifiedAST = manipSetNodeLabel(modifiedAST, p.nodeId, p.label);
            break;
        }
        default:
            throw new Error(`Unknown operation: ${operation}`);
    }
    return { ast: modifiedAST, warnings, extraData };
}
//# sourceMappingURL=modify-operation.js.map