"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValidationWarning = exports.ValidationError = exports.validateNodeConfig = exports.setSchemaBaseDirs = void 0;
exports.validateWorkflow = validateWorkflow;
const get_1 = __importDefault(require("lodash/get"));
const n8n_workflow_1 = require("n8n-workflow");
const display_options_1 = require("./display-options");
const input_resolver_1 = require("./input-resolver");
const schema_validator_1 = require("./schema-validator");
const node_types_1 = require("../constants/node-types");
const string_utils_1 = require("../workflow-builder/string-utils");
var schema_validator_2 = require("./schema-validator");
Object.defineProperty(exports, "setSchemaBaseDirs", { enumerable: true, get: function () { return schema_validator_2.setSchemaBaseDirs; } });
Object.defineProperty(exports, "validateNodeConfig", { enumerable: true, get: function () { return schema_validator_2.validateNodeConfig; } });
class ValidationError {
    constructor(code, message, nodeName, parameterName, violationLevel) {
        this.code = code;
        this.message = message;
        this.nodeName = nodeName;
        this.parameterName = parameterName;
        this.violationLevel = violationLevel;
    }
}
exports.ValidationError = ValidationError;
class ValidationWarning {
    constructor(code, message, nodeName, parameterPath, originalName, violationLevel) {
        this.code = code;
        this.message = message;
        this.nodeName = nodeName;
        this.parameterPath = parameterPath;
        this.originalName = originalName;
        this.violationLevel = violationLevel;
    }
}
exports.ValidationWarning = ValidationWarning;
function isTriggerNode(type) {
    return (type.includes('Trigger') ||
        type.includes('trigger') ||
        type.includes('Webhook') ||
        type.includes('webhook') ||
        type.includes('Schedule') ||
        type.includes('schedule') ||
        type.includes('Poll') ||
        type.includes('poll'));
}
const AI_CONNECTION_TYPES = [
    'ai_languageModel',
    'ai_memory',
    'ai_tool',
    'ai_outputParser',
    'ai_embedding',
    'ai_vectorStore',
    'ai_retriever',
    'ai_document',
    'ai_textSplitter',
    'ai_reranker',
];
const AI_CONNECTION_TO_SUBNODE_FIELD = {
    ai_languageModel: 'model',
    ai_memory: 'memory',
    ai_tool: 'tools',
    ai_outputParser: 'outputParser',
    ai_embedding: 'embedding',
    ai_vectorStore: 'vectorStore',
    ai_retriever: 'retriever',
    ai_document: 'documentLoader',
    ai_textSplitter: 'textSplitter',
    ai_reranker: 'reranker',
};
const AI_ARRAY_TYPES = new Set(['ai_tool']);
function reconstructSubnodesFromConnections(targetNodeName, json) {
    const subnodes = {};
    const nodesByName = new Map();
    for (const node of json.nodes) {
        if (node.name) {
            nodesByName.set(node.name, node);
        }
    }
    for (const [sourceNodeName, nodeConnections] of Object.entries(json.connections)) {
        for (const connType of AI_CONNECTION_TYPES) {
            const aiConns = nodeConnections[connType];
            if (!aiConns || !Array.isArray(aiConns))
                continue;
            for (const outputs of aiConns) {
                if (!outputs)
                    continue;
                for (const conn of outputs) {
                    if (conn.node === targetNodeName) {
                        const subnodeField = AI_CONNECTION_TO_SUBNODE_FIELD[connType];
                        if (!subnodeField)
                            continue;
                        const sourceNode = nodesByName.get(sourceNodeName);
                        if (!sourceNode)
                            continue;
                        const subnodeConfig = {
                            type: sourceNode.type,
                            version: sourceNode.typeVersion,
                            parameters: sourceNode.parameters ?? {},
                        };
                        if (AI_ARRAY_TYPES.has(connType)) {
                            const existing = subnodes[subnodeField];
                            if (Array.isArray(existing)) {
                                existing.push(subnodeConfig);
                            }
                            else {
                                subnodes[subnodeField] = [subnodeConfig];
                            }
                        }
                        else {
                            subnodes[subnodeField] = subnodeConfig;
                        }
                    }
                }
            }
        }
    }
    return Object.keys(subnodes).length > 0 ? subnodes : undefined;
}
function hasAiConnectionToParent(nodeName, json) {
    const nodeConnections = json.connections[nodeName];
    if (!nodeConnections)
        return false;
    for (const connType of AI_CONNECTION_TYPES) {
        const aiConns = nodeConnections[connType];
        if (aiConns && Array.isArray(aiConns)) {
            for (const outputs of aiConns) {
                if (outputs && outputs.length > 0) {
                    return true;
                }
            }
        }
    }
    return false;
}
function isToolSubnode(nodeName, json) {
    const nodeConnections = json.connections[nodeName];
    if (!nodeConnections)
        return false;
    const toolConns = nodeConnections.ai_tool;
    if (toolConns && Array.isArray(toolConns)) {
        for (const outputs of toolConns) {
            if (outputs && outputs.length > 0) {
                return true;
            }
        }
    }
    return false;
}
function findDisconnectedNodes(json) {
    const hasIncoming = new Set();
    for (const [_sourceName, nodeConnections] of Object.entries(json.connections)) {
        if (nodeConnections.main) {
            for (const outputs of nodeConnections.main) {
                if (outputs) {
                    for (const connection of outputs) {
                        hasIncoming.add(connection.node);
                    }
                }
            }
        }
    }
    const disconnected = [];
    for (const node of json.nodes) {
        if (!node.name)
            continue;
        if (hasIncoming.has(node.name))
            continue;
        if (isTriggerNode(node.type))
            continue;
        if ((0, node_types_1.isStickyNoteType)(node.type))
            continue;
        if (hasAiConnectionToParent(node.name, json))
            continue;
        disconnected.push(node.name);
    }
    return disconnected;
}
function validateWorkflow(workflowOrJson, options = {}) {
    const json = 'toJSON' in workflowOrJson ? workflowOrJson.toJSON() : workflowOrJson;
    const errors = [];
    const warnings = [];
    if (!options.allowNoTrigger) {
        const hasTrigger = json.nodes.some((node) => isTriggerNode(node.type));
        if (!hasTrigger) {
            warnings.push(new ValidationWarning('MISSING_TRIGGER', 'Workflow has no trigger node. It will need to be started manually.'));
        }
    }
    if (!options.allowDisconnectedNodes) {
        const disconnected = findDisconnectedNodes(json);
        for (const nodeName of disconnected) {
            warnings.push(new ValidationWarning('DISCONNECTED_NODE', `Node '${nodeName}' is not connected to any input. It will not receive data.`, nodeName));
        }
    }
    if (options.strictMode) {
        for (const node of json.nodes) {
            if ((0, node_types_1.isHttpRequestType)(node.type)) {
                if (!node.parameters?.url && !node.parameters?.requestUrl) {
                    warnings.push(new ValidationWarning('MISSING_PARAMETER', `HTTP Request node '${node.name}' may be missing URL parameter`, node.name));
                }
            }
        }
    }
    if (options.validateSchema !== false) {
        for (const node of json.nodes) {
            const version = typeof node.typeVersion === 'string'
                ? parseFloat(node.typeVersion)
                : (node.typeVersion ?? 1);
            const config = {};
            if (node.parameters !== undefined) {
                config.parameters = node.parameters;
            }
            const nodeWithSubnodes = node;
            if (nodeWithSubnodes.subnodes !== undefined) {
                config.subnodes = nodeWithSubnodes.subnodes;
            }
            else if (node.name) {
                const reconstructed = reconstructSubnodesFromConnections(node.name, json);
                if (reconstructed) {
                    config.subnodes = reconstructed;
                }
            }
            const isToolNode = node.name ? isToolSubnode(node.name, json) : false;
            const schemaResult = (0, schema_validator_1.validateNodeConfig)(node.type, version, config, { isToolNode });
            if (!schemaResult.valid) {
                for (const error of schemaResult.errors) {
                    let message = error.message;
                    if (error.path === 'subnodes' &&
                        message.includes('Unknown field') &&
                        options.nodeTypesProvider) {
                        const nodeType = options.nodeTypesProvider.getByNameAndVersion(node.type, version);
                        const validInputs = nodeType?.description?.builderHint?.inputs;
                        if (validInputs) {
                            const validSubnodes = Object.keys(validInputs)
                                .map((k) => AI_CONNECTION_TO_SUBNODE_FIELD[k])
                                .filter(Boolean);
                            if (validSubnodes.length > 0) {
                                message = message.replace(/Unknown field\(s\) at "subnodes": (.+)\./, `Invalid subnode(s) $1. This node only accepts: ${validSubnodes.join(', ')}.`);
                            }
                        }
                    }
                    warnings.push(new ValidationWarning('INVALID_PARAMETER', `Node "${node.name}": ${message}`, node.name));
                }
            }
        }
    }
    if (options.nodeTypesProvider) {
        checkNodeInputIndices(json, options.nodeTypesProvider, warnings);
        validateSubnodeParameters(json, options.nodeTypesProvider, warnings);
        validateParentSupportsInputs(json, options.nodeTypesProvider, warnings);
        validateRequiredInputsConnected(json, options.nodeTypesProvider, errors);
        validateOutputUsage(json, options.nodeTypesProvider, warnings);
        validatePlaceholderSlots(json, options.nodeTypesProvider, errors);
    }
    validateSwitchFallbackOutputConnections(json, warnings);
    checkMergeNodeInputCount(json, warnings);
    return {
        valid: errors.length === 0,
        errors,
        warnings,
    };
}
function checkMergeNodeInputCount(json, warnings) {
    const connectionsByDest = (0, n8n_workflow_1.mapConnectionsByDestination)(json.connections);
    for (const node of json.nodes) {
        if (!node.name)
            continue;
        if (node.type !== 'n8n-nodes-base.merge')
            continue;
        const numberInputsParam = node.parameters?.numberInputs;
        const declaredInputs = typeof numberInputsParam === 'number' ? numberInputsParam : 2;
        const incomingMain = connectionsByDest[node.name]?.main;
        if (!incomingMain)
            continue;
        let maxConnectedIndex = -1;
        for (let i = 0; i < incomingMain.length; i++) {
            const slot = incomingMain[i];
            if (Array.isArray(slot) && slot.length > 0) {
                maxConnectedIndex = i;
            }
        }
        if (maxConnectedIndex >= declaredInputs) {
            warnings.push(new ValidationWarning('INVALID_INPUT_INDEX', `Merge node '${node.name}' has a connection to input index ${maxConnectedIndex} but 'numberInputs' is ${declaredInputs}. Set 'numberInputs' to ${maxConnectedIndex + 1} so every branch is accepted.`, node.name, 'numberInputs', undefined, 'major'));
        }
    }
}
const AI_CONNECTION_TO_SDK_FUNCTION = {
    ai_languageModel: 'languageModel()',
    ai_memory: 'memory()',
    ai_tool: 'tool()',
    ai_outputParser: 'outputParser()',
    ai_embedding: 'embeddings()',
    ai_vectorStore: 'vectorStore()',
    ai_retriever: 'retriever()',
    ai_document: 'documentLoader()',
    ai_textSplitter: 'textSplitter()',
    ai_reranker: 'reranker()',
};
function checkDisplayOptionsMatch(subnodeParams, displayOptions) {
    const mismatches = [];
    if (!displayOptions.show)
        return { matches: true, mismatches };
    for (const [paramName, expectedValues] of Object.entries(displayOptions.show)) {
        if (!expectedValues)
            continue;
        const actualValue = subnodeParams[paramName];
        if (!expectedValues.includes(actualValue)) {
            mismatches.push({
                param: paramName,
                expected: expectedValues,
                actual: actualValue,
            });
        }
    }
    return { matches: mismatches.length === 0, mismatches };
}
function validateSubnodeParameters(json, nodeTypesProvider, warnings) {
    const nodesByName = new Map();
    for (const node of json.nodes) {
        if (node.name) {
            nodesByName.set(node.name, node);
        }
    }
    const connectionsByDest = (0, n8n_workflow_1.mapConnectionsByDestination)(json.connections);
    for (const parentNode of json.nodes) {
        if (!parentNode.name)
            continue;
        const parentNodeType = nodeTypesProvider.getByNameAndVersion(parentNode.type, typeof parentNode.typeVersion === 'string'
            ? parseFloat(parentNode.typeVersion)
            : (parentNode.typeVersion ?? 1));
        const builderHintInputs = parentNodeType?.description?.builderHint?.inputs;
        if (!builderHintInputs)
            continue;
        for (const [connectionType, inputConfig] of Object.entries(builderHintInputs)) {
            if (!connectionType.startsWith('ai_'))
                continue;
            if (!inputConfig?.displayOptions?.show)
                continue;
            const incomingConnections = connectionsByDest[parentNode.name]?.[connectionType];
            if (!incomingConnections)
                continue;
            for (const connList of incomingConnections) {
                if (!connList)
                    continue;
                for (const conn of connList) {
                    const subnodeName = conn.node;
                    const subnode = nodesByName.get(subnodeName);
                    if (!subnode?.parameters)
                        continue;
                    const { matches, mismatches } = checkDisplayOptionsMatch(subnode.parameters, inputConfig.displayOptions);
                    if (!matches) {
                        const subnodeOwnsAnyParam = mismatches.some((m) => m.actual !== undefined);
                        if (!subnodeOwnsAnyParam)
                            continue;
                        const sdkFn = AI_CONNECTION_TO_SDK_FUNCTION[connectionType] || connectionType;
                        const mismatchDetails = mismatches
                            .map((m) => `${m.param}='${String(m.actual)}' (expected: ${m.expected.map((v) => `'${String(v)}'`).join(' or ')})`)
                            .join(', ');
                        warnings.push(new ValidationWarning('SUBNODE_PARAMETER_MISMATCH', `'${subnodeName}' is connected to '${parentNode.name}' using ${sdkFn} but has ${mismatchDetails}. Update parameters to match the SDK function used.`, subnodeName, mismatches[0]?.param));
                    }
                }
            }
        }
    }
}
function buildConditionSummary(displayOptions, parentParams) {
    if (!displayOptions.show)
        return '';
    const parts = [];
    for (const [paramName, expectedValues] of Object.entries(displayOptions.show)) {
        if (!expectedValues)
            continue;
        const actual = (0, get_1.default)(parentParams, paramName);
        const expectedStr = expectedValues.map((v) => `'${String(v)}'`).join(' or ');
        parts.push(`${paramName} should be ${expectedStr} (currently '${String(actual)}')`);
    }
    return parts.length > 0 ? `Required: ${parts.join(', ')}.` : '';
}
function buildTriggeringConditionSummary(displayOptions, parentParams) {
    if (!displayOptions.show)
        return '';
    const parts = [];
    for (const [paramName, _expectedValues] of Object.entries(displayOptions.show)) {
        const actual = (0, get_1.default)(parentParams, paramName);
        parts.push(`${paramName}='${String(actual)}'`);
    }
    return parts.join(', ');
}
function validateParentSupportsInputs(json, nodeTypesProvider, warnings) {
    const nodesByName = new Map();
    for (const node of json.nodes) {
        if (node.name) {
            nodesByName.set(node.name, node);
        }
    }
    const connectionsByDest = (0, n8n_workflow_1.mapConnectionsByDestination)(json.connections);
    for (const parentNode of json.nodes) {
        if (!parentNode.name)
            continue;
        const version = typeof parentNode.typeVersion === 'string'
            ? parseFloat(parentNode.typeVersion)
            : (parentNode.typeVersion ?? 1);
        const parentNodeType = nodeTypesProvider.getByNameAndVersion(parentNode.type, version);
        const builderHintInputs = parentNodeType?.description?.builderHint?.inputs;
        if (!builderHintInputs)
            continue;
        const parentContext = {
            parameters: (parentNode.parameters ?? {}),
            nodeVersion: version,
            rootParameters: (parentNode.parameters ?? {}),
        };
        for (const [connectionType, inputConfig] of Object.entries(builderHintInputs)) {
            if (!connectionType.startsWith('ai_'))
                continue;
            if (!inputConfig?.displayOptions)
                continue;
            const parentSupportsInput = (0, display_options_1.matchesDisplayOptions)(parentContext, inputConfig.displayOptions);
            if (parentSupportsInput)
                continue;
            const incomingConnections = connectionsByDest[parentNode.name]?.[connectionType];
            if (!incomingConnections)
                continue;
            for (const connList of incomingConnections) {
                if (!connList)
                    continue;
                for (const conn of connList) {
                    const subnodeName = conn.node;
                    const subnodeField = AI_CONNECTION_TO_SUBNODE_FIELD[connectionType] || connectionType;
                    const conditionDetails = buildConditionSummary(inputConfig.displayOptions, (parentNode.parameters ?? {}));
                    warnings.push(new ValidationWarning('UNSUPPORTED_SUBNODE_INPUT', `'${parentNode.name}' has a ${subnodeField} subnode ('${subnodeName}') connected, but its current configuration does not accept one. ${conditionDetails} These parameters must be set on '${parentNode.name}' itself, NOT on the ${subnodeField} subnode. Alternatively, remove the ${subnodeField} connection if this capability isn't needed.`, parentNode.name, undefined, undefined, 'major'));
                }
            }
        }
    }
}
function validateRequiredInputsConnected(json, nodeTypesProvider, errors) {
    const connectionsByDest = (0, n8n_workflow_1.mapConnectionsByDestination)(json.connections);
    for (const parentNode of json.nodes) {
        if (!parentNode.name)
            continue;
        const version = typeof parentNode.typeVersion === 'string'
            ? parseFloat(parentNode.typeVersion)
            : (parentNode.typeVersion ?? 1);
        const parentNodeType = nodeTypesProvider.getByNameAndVersion(parentNode.type, version);
        const builderHintInputs = parentNodeType?.description?.builderHint?.inputs;
        if (!builderHintInputs)
            continue;
        const parentContext = {
            parameters: (parentNode.parameters ?? {}),
            nodeVersion: version,
            rootParameters: (parentNode.parameters ?? {}),
        };
        for (const [connectionType, inputConfig] of Object.entries(builderHintInputs)) {
            if (!connectionType.startsWith('ai_'))
                continue;
            if (!inputConfig?.required)
                continue;
            if (inputConfig.displayOptions) {
                const conditionsMet = (0, display_options_1.matchesDisplayOptions)(parentContext, inputConfig.displayOptions);
                if (!conditionsMet)
                    continue;
            }
            const incoming = connectionsByDest[parentNode.name]?.[connectionType];
            const hasConnection = Array.isArray(incoming) && incoming.some((slot) => Array.isArray(slot) && slot.length > 0);
            if (hasConnection)
                continue;
            const subnodeField = AI_CONNECTION_TO_SUBNODE_FIELD[connectionType] || connectionType;
            const triggerDetails = inputConfig.displayOptions
                ? ` (triggered by ${buildTriggeringConditionSummary(inputConfig.displayOptions, (parentNode.parameters ?? {}))})`
                : '';
            const alternative = inputConfig.displayOptions
                ? ` Either connect a ${subnodeField} subnode, or change those parameters to remove the requirement.`
                : '';
            errors.push(new ValidationError('MISSING_REQUIRED_INPUT', `'${parentNode.name}' requires a ${subnodeField} subnode connected to its ${connectionType} input${triggerDetails}, but none is connected.${alternative}`, parentNode.name, undefined, 'major'));
        }
    }
}
function describeOutputWiring(connectionType, target = false) {
    if (connectionType === 'main')
        return target ? '.to(...)' : 'wired with .to()';
    const field = AI_CONNECTION_TO_SUBNODE_FIELD[connectionType];
    if (field)
        return target ? `subnodes.${field}` : `attached as subnodes.${field}`;
    return target ? connectionType : `connected via ${connectionType}`;
}
function findEnabledAlternativeOutput(outputsHint, ctx, excludeType) {
    for (const [type, cfg] of Object.entries(outputsHint)) {
        if (type === excludeType)
            continue;
        if (!cfg)
            continue;
        if (cfg.displayOptions && !(0, display_options_1.matchesDisplayOptions)(ctx, cfg.displayOptions)) {
            continue;
        }
        return type;
    }
    return undefined;
}
function validateOutputUsage(json, nodeTypesProvider, warnings) {
    for (const sourceNode of json.nodes) {
        if (!sourceNode.name)
            continue;
        const outgoing = json.connections[sourceNode.name];
        if (!outgoing)
            continue;
        const version = typeof sourceNode.typeVersion === 'string'
            ? parseFloat(sourceNode.typeVersion)
            : (sourceNode.typeVersion ?? 1);
        const nodeType = nodeTypesProvider.getByNameAndVersion(sourceNode.type, version);
        const outputsHint = nodeType?.description?.builderHint?.outputs;
        if (!outputsHint)
            continue;
        const ctx = {
            parameters: (sourceNode.parameters ?? {}),
            nodeVersion: version,
            rootParameters: (sourceNode.parameters ?? {}),
        };
        for (const [connectionType, cfg] of Object.entries(outputsHint)) {
            if (!cfg?.displayOptions)
                continue;
            const edges = outgoing[connectionType];
            if (!edges)
                continue;
            const hasEdges = edges.some((slot) => Array.isArray(slot) && slot.length > 0);
            if (!hasEdges)
                continue;
            const enabled = (0, display_options_1.matchesDisplayOptions)(ctx, cfg.displayOptions);
            if (enabled)
                continue;
            const conditionDetails = buildConditionSummary(cfg.displayOptions, (sourceNode.parameters ?? {}));
            const usedWiring = describeOutputWiring(connectionType);
            const enabledAlt = findEnabledAlternativeOutput(outputsHint, ctx, connectionType);
            const altSuggestion = enabledAlt
                ? ` To use this node as-is, attach it as ${describeOutputWiring(enabledAlt, true)} of a parent (it exposes ${enabledAlt} in this configuration).`
                : '';
            warnings.push(new ValidationWarning('INVALID_OUTPUT_FOR_MODE', `'${sourceNode.name}' is ${usedWiring} but its current parameters disable that output. ${conditionDetails}${altSuggestion}`, sourceNode.name, undefined, undefined, 'major'));
        }
    }
}
function isRecord(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function getSwitchRulesCount(parameters) {
    const rules = parameters?.rules;
    if (!isRecord(rules))
        return 0;
    const values = rules.values;
    if (Array.isArray(values))
        return values.length;
    const legacyRules = rules.rules;
    if (Array.isArray(legacyRules))
        return legacyRules.length;
    return 0;
}
function getSwitchFallbackOutput(parameters) {
    const options = parameters?.options;
    if (!isRecord(options))
        return undefined;
    return options.fallbackOutput;
}
function hasOutputConnections(outputs, outputIndex) {
    const output = outputs[outputIndex];
    return Array.isArray(output) && output.length > 0;
}
function validateSwitchFallbackOutputConnections(json, warnings) {
    for (const sourceNode of json.nodes) {
        if (!sourceNode.name || sourceNode.type !== 'n8n-nodes-base.switch')
            continue;
        const mode = sourceNode.parameters?.mode;
        if (mode !== undefined && mode !== 'rules')
            continue;
        const outgoing = json.connections[sourceNode.name];
        const mainOutputs = outgoing?.main;
        if (!Array.isArray(mainOutputs))
            continue;
        const rulesCount = getSwitchRulesCount(sourceNode.parameters);
        const fallbackOutput = getSwitchFallbackOutput(sourceNode.parameters);
        if (fallbackOutput === 'extra')
            continue;
        for (let outputIndex = rulesCount; outputIndex < mainOutputs.length; outputIndex++) {
            if (!hasOutputConnections(mainOutputs, outputIndex))
                continue;
            const isErrorOutput = sourceNode.onError === 'continueErrorOutput' && outputIndex === rulesCount;
            if (isErrorOutput)
                continue;
            warnings.push(new ValidationWarning('SWITCH_FALLBACK_OUTPUT_DISABLED', `Switch node '${sourceNode.name}' has a connection from output ${outputIndex}, but rules mode only creates fallback output ${rulesCount} when options.fallbackOutput is set to 'extra'. Set options.fallbackOutput to 'extra' before wiring a catch-all branch, or route unmatched items to an existing rule output with a numeric fallbackOutput value.`, sourceNode.name, 'options.fallbackOutput', undefined, 'major'));
        }
    }
}
function validatePlaceholderSlots(json, nodeTypesProvider, errors) {
    for (const node of json.nodes) {
        if (!node.name || !node.parameters)
            continue;
        const version = typeof node.typeVersion === 'string' ? parseFloat(node.typeVersion) : (node.typeVersion ?? 1);
        const nodeType = nodeTypesProvider.getByNameAndVersion(node.type, version);
        const properties = nodeType?.description?.properties;
        if (!properties)
            continue;
        const params = node.parameters;
        for (const prop of properties) {
            if (prop.builderHint?.placeholderSupported !== false)
                continue;
            const value = params[prop.name];
            if (!(0, string_utils_1.containsPlaceholderMarker)(value))
                continue;
            errors.push(new ValidationError('INVALID_PARAMETER', `Node "${node.name}": placeholder() is not supported for parameter '${prop.name}'. Use a literal value or expr() instead.`, node.name, prop.name));
        }
    }
}
function checkNodeInputIndices(json, nodeTypesProvider, warnings) {
    const nodesByName = new Map();
    for (const node of json.nodes) {
        if (node.name) {
            nodesByName.set(node.name, node);
        }
    }
    const warnedInputs = new Set();
    for (const [_sourceName, nodeConnections] of Object.entries(json.connections)) {
        const mainConnections = nodeConnections.main;
        if (!mainConnections || !Array.isArray(mainConnections))
            continue;
        for (const outputs of mainConnections) {
            if (!outputs)
                continue;
            for (const conn of outputs) {
                const targetNodeName = conn.node;
                const targetInputIndex = conn.index;
                const targetNode = nodesByName.get(targetNodeName);
                if (!targetNode)
                    continue;
                const version = typeof targetNode.typeVersion === 'string'
                    ? parseFloat(targetNode.typeVersion)
                    : (targetNode.typeVersion ?? 1);
                const mainInputCount = (0, input_resolver_1.resolveMainInputCount)(nodeTypesProvider, targetNode.type, version);
                if (mainInputCount === undefined)
                    continue;
                if (targetInputIndex >= mainInputCount) {
                    const warnKey = `${targetNodeName}:${targetInputIndex}`;
                    if (!warnedInputs.has(warnKey)) {
                        warnedInputs.add(warnKey);
                        warnings.push(new ValidationWarning('INVALID_INPUT_INDEX', `Connection to '${targetNodeName}' uses input index ${targetInputIndex}, but node only has ${mainInputCount} input(s) (indices 0-${mainInputCount - 1})`, targetNodeName));
                    }
                }
            }
        }
    }
}
//# sourceMappingURL=index.js.map