import { AGENTS_SCHEMA_PATH, getAgentMap, getGuardConfig } from "../agents.js";
import { getToolQueryRef, parsePropertyRef } from "../refs.js";
import { validateExpression } from "../util/expressions.js";
import { getShape } from "../util/shapes.js";
import { getTool } from "../util/tools.js";
import { findDuplicateIndices, isValidPropertyRef, pushErrors } from "./util.js";
export function validateAIToolConfig(projectSchema, toolConfig, basePath) {
    const parsedToolRef = parsePropertyRef(toolConfig.tool);
    if (!parsedToolRef || parsedToolRef.layerId !== 'local') {
        return;
    }
    const tool = getTool(projectSchema, parsedToolRef.propertyName);
    if (!tool) {
        return {
            type: 'notFound',
            path: basePath.concat(['tool']),
            message: `Missing tool "${toolConfig.tool}"`
        };
    }
}
export async function validateToolQuery(context, tool, basePath) {
    const queryRef = getToolQueryRef(tool);
    if (!queryRef) {
        return;
    }
    const path = basePath.concat(['execution', 'query', 'ref']);
    const parsed = parsePropertyRef(queryRef);
    if (!parsed) {
        return {
            type: 'conflict',
            path,
            message: `Unable to parse property ref "${queryRef}"`
        };
    }
    if (parsed.shapeName !== 'Query' && parsed.shapeName !== 'Mutation') {
        return {
            type: 'conflict',
            path,
            message: 'Tools must be a query or mutation'
        };
    }
    if (!(await isValidPropertyRef(context, parsed))) {
        return {
            type: 'notFound',
            path,
            message: `Missing tool ${parsed.shapeName.toLowerCase()} "${queryRef}"`
        };
    }
}
function validateAgentTransitions(parentPath, inputNames, transitions) {
    const errors = [];
    transitions.forEach((transition, transitionIndex) => {
        if (transition.type === 'suspend') {
            if (!inputNames.has(transition.input)) {
                errors.push({
                    path: [...parentPath, transitionIndex, 'input'],
                    type: 'notFound',
                    message: `Invalid input "${transition.input}"`
                });
            }
        }
        if (transition.condition) {
            const conditionValidation = validateExpression(transition.condition);
            if (!conditionValidation.valid) {
                const common = {
                    path: [...parentPath, transitionIndex, 'expression'],
                    type: 'conflict'
                };
                errors.push(...conditionValidation.errors.map((message) => ({ ...common, message })));
            }
        }
    });
    return errors;
}
export function validateAgents(projectSchema) {
    const errors = [];
    const agents = getAgentMap(projectSchema);
    if (!agents) {
        return errors;
    }
    for (const [agentName, agent] of Object.entries(agents)) {
        const stateNames = new Set();
        const agentPath = [...AGENTS_SCHEMA_PATH, agentName];
        const inputNames = new Set(agent.api.inputs.map((input) => input.name));
        for (const [name, indices] of findDuplicateIndices(agent.api.inputs, 'name')) {
            for (const index of indices) {
                errors.push({
                    path: [...agentPath, 'api', 'inputs', index, 'name'],
                    type: 'conflict',
                    message: `Duplicate input name "${name}"`
                });
            }
        }
        pushErrors(errors, validateAgentTransitions([...agentPath, 'start', 'transitions'], inputNames, agent.start.transitions));
        for (const [stateId, state] of Object.entries(agent.states)) {
            const statePath = [...agentPath, 'states', stateId];
            if (stateNames.has(state.name)) {
                errors.push({
                    path: [...statePath, 'name'],
                    type: 'conflict',
                    message: `Duplicate state name "${state.name}"`
                });
            }
            else {
                stateNames.add(state.name);
            }
            if (state.variables) {
                state.variables.forEach((variable, variableIndex) => {
                    variable.steps.forEach((step, stepIndex) => {
                        const stepPath = [...statePath, 'variables', variableIndex, 'steps', stepIndex];
                        if (step.condition) {
                            const conditionValidation = validateExpression(step.condition);
                            if (!conditionValidation.valid) {
                                const common = {
                                    path: [...stepPath, 'condition'],
                                    type: 'conflict'
                                };
                                errors.push(...conditionValidation.errors.map((message) => ({ ...common, message })));
                            }
                        }
                        const exprValidation = validateExpression(step.expression);
                        if (!exprValidation.valid) {
                            const common = {
                                path: [...stepPath, 'expression'],
                                type: 'conflict'
                            };
                            errors.push(...exprValidation.errors.map((message) => ({ ...common, message })));
                        }
                    });
                });
            }
            if (state.transitions) {
                errors.push(...validateAgentTransitions([...statePath, 'transitions'], inputNames, state.transitions));
            }
            if (state.execution.type === 'generate' || state.execution.type === 'chat') {
                // Validate that memory tools require session memory shape to be defined
                if (state.execution.memoryTools?.length && !agent.memory?.sessionMemory.shape) {
                    errors.push({
                        path: [...statePath, 'execution', 'memoryTools'],
                        type: 'conflict',
                        message: 'Memory tools require session memory shape to be defined'
                    });
                }
                if (!state.execution.tools) {
                    continue;
                }
                const toolConfigsWithEffectiveName = state.execution.tools.map((toolConfig) => {
                    const parsedRef = parsePropertyRef(toolConfig.tool, 'Tool');
                    const toolName = parsedRef?.propertyName ?? toolConfig.tool;
                    return {
                        ...toolConfig,
                        effectiveName: toolConfig.alias ?? toolName
                    };
                });
                for (const [name, indices] of findDuplicateIndices(toolConfigsWithEffectiveName, 'effectiveName')) {
                    for (const index of indices) {
                        errors.push({
                            path: [...statePath, 'execution', 'tools', index, 'tool'],
                            type: 'conflict',
                            message: `Duplicate tool name "${name}"`
                        });
                    }
                }
                state.execution.tools.forEach((tool, toolIndex) => {
                    pushErrors(errors, validateAIToolConfig(projectSchema, tool, [...statePath, 'execution', 'tools', toolIndex]));
                });
                // Validate memoryTools args shape references
                if (state.execution.memoryTools) {
                    state.execution.memoryTools.forEach((memoryTool, memoryToolIndex) => {
                        if (memoryTool.args && !getShape(projectSchema, memoryTool.args)) {
                            errors.push({
                                path: [...statePath, 'execution', 'memoryTools', memoryToolIndex, 'args'],
                                type: 'notFound',
                                message: `Invalid shape "${memoryTool.args}" for memoryTool "${memoryTool.name}"`
                            });
                        }
                    });
                }
                // Validate clientTools parameters shape references
                if (state.execution.clientTools) {
                    state.execution.clientTools.forEach((clientTool, clientToolIndex) => {
                        if (clientTool.parameters && !getShape(projectSchema, clientTool.parameters)) {
                            errors.push({
                                path: [...statePath, 'execution', 'clientTools', clientToolIndex, 'parameters'],
                                type: 'notFound',
                                message: `Invalid shape "${clientTool.parameters}" for clientTool "${clientTool.name}"`
                            });
                        }
                    });
                }
            }
        }
        if (agent.guards) {
            agent.guards.forEach(({ guardId }, index) => {
                if (!getGuardConfig(projectSchema, guardId)) {
                    errors.push({
                        path: [...agentPath, 'guards', index, 'guardId'],
                        type: 'notFound',
                        message: `Invalid guardId "${guardId}"`
                    });
                }
            });
        }
        if (agent.historyStrategies) {
            const seenStrategyNames = new Set();
            for (const [historyStrategyId, historyStrategy] of Object.entries(agent.historyStrategies)) {
                if (seenStrategyNames.has(historyStrategy.name)) {
                    errors.push({
                        path: [...agentPath, 'historyStrategies', historyStrategyId, 'name'],
                        type: 'conflict',
                        message: `Duplicate history strategy name "${historyStrategy.name}"`
                    });
                }
                else {
                    seenStrategyNames.add(historyStrategy.name);
                }
            }
        }
    }
    return errors;
}
