import { readFile } from 'fs/promises';
import { ClientError } from './errors.js';
import { createLogger } from './logger.js';
const logger = createLogger('schema-validator');
export async function loadSchemaFromFile(schemaPath) {
    try {
        const content = await readFile(schemaPath, 'utf-8');
        return JSON.parse(content);
    }
    catch (error) {
        if (error.code === 'ENOENT') {
            throw new ClientError(`Schema file not found: ${schemaPath}`);
        }
        if (error instanceof SyntaxError) {
            throw new ClientError(`Invalid JSON in schema file: ${schemaPath}`);
        }
        throw new ClientError(`Failed to read schema file: ${error.message}`);
    }
}
function deepEqual(a, b) {
    if (a === b)
        return true;
    if (typeof a !== typeof b)
        return false;
    if (a === null || b === null)
        return a === b;
    if (Array.isArray(a) && Array.isArray(b)) {
        if (a.length !== b.length)
            return false;
        return a.every((item, index) => deepEqual(item, b[index]));
    }
    if (typeof a === 'object' && typeof b === 'object') {
        const aObj = a;
        const bObj = b;
        const aKeys = Object.keys(aObj).sort();
        const bKeys = Object.keys(bObj).sort();
        if (!deepEqual(aKeys, bKeys))
            return false;
        return aKeys.every((key) => deepEqual(aObj[key], bObj[key]));
    }
    return false;
}
export function validateToolSchema(actual, expected, mode, passedArgs) {
    const result = {
        valid: true,
        errors: [],
        warnings: [],
    };
    if (mode === 'ignore') {
        return result;
    }
    logger.debug(`Validating tool schema in ${mode} mode`);
    if (actual.name !== expected.name) {
        result.errors.push(`Tool name mismatch: expected "${expected.name}", got "${actual.name}"`);
        result.valid = false;
    }
    if (mode === 'strict') {
        if (actual.description !== expected.description) {
            result.errors.push(`Description mismatch: expected "${expected.description}", got "${actual.description}"`);
            result.valid = false;
        }
        if (!deepEqual(actual.inputSchema, expected.inputSchema)) {
            result.errors.push('Input schema does not match exactly');
            result.valid = false;
        }
        if (!deepEqual(actual.outputSchema, expected.outputSchema)) {
            result.errors.push('Output schema does not match exactly');
            result.valid = false;
        }
    }
    else {
        if (actual.description !== expected.description) {
            result.warnings.push(`Description changed: "${expected.description}" → "${actual.description}"`);
        }
        if (expected.inputSchema && actual.inputSchema) {
            const expectedRequired = expected.inputSchema.required || [];
            const actualRequired = actual.inputSchema.required || [];
            const expectedProps = expected.inputSchema.properties || {};
            const actualProps = actual.inputSchema.properties || {};
            const passedArgNames = passedArgs ? Object.keys(passedArgs) : null;
            const hasPassedArgs = passedArgNames && passedArgNames.length > 0;
            for (const field of actualRequired) {
                if (!expectedRequired.includes(field)) {
                    if (!hasPassedArgs || !passedArgNames.includes(field)) {
                        result.errors.push(`New required field "${field}" added (breaking change)`);
                        result.valid = false;
                    }
                }
            }
            if (hasPassedArgs) {
                for (const argName of passedArgNames) {
                    if (!(argName in actualProps)) {
                        result.errors.push(`Argument "${argName}" no longer exists in schema`);
                        result.valid = false;
                        continue;
                    }
                    const expectedProp = expectedProps[argName];
                    const actualProp = actualProps[argName];
                    if (expectedProp && actualProp && expectedProp.type && actualProp.type !== expectedProp.type) {
                        result.errors.push(`Argument "${argName}" type changed: expected ${JSON.stringify(expectedProp.type)}, got ${JSON.stringify(actualProp.type)}`);
                        result.valid = false;
                    }
                }
            }
            else {
                for (const [propName, expectedProp] of Object.entries(expectedProps)) {
                    if (!(propName in actualProps)) {
                        result.errors.push(`Property "${propName}" is missing from input schema`);
                        result.valid = false;
                    }
                    else {
                        const actualProp = actualProps[propName];
                        const expProp = expectedProp;
                        if (actualProp && expProp.type && actualProp.type !== expProp.type) {
                            result.errors.push(`Property "${propName}" type changed: expected ${JSON.stringify(expProp.type)}, got ${JSON.stringify(actualProp.type)}`);
                            result.valid = false;
                        }
                    }
                }
                for (const field of expectedRequired) {
                    if (!actualRequired.includes(field)) {
                        result.errors.push(`Required field "${field}" is no longer required`);
                        result.valid = false;
                    }
                }
            }
            for (const propName of Object.keys(actualProps)) {
                if (!(propName in expectedProps) && !actualRequired.includes(propName)) {
                    result.warnings.push(`New optional field "${propName}" added`);
                }
            }
        }
        else if (expected.inputSchema && !actual.inputSchema) {
            result.errors.push('Input schema was removed');
            result.valid = false;
        }
        if (expected.outputSchema && actual.outputSchema) {
            const expectedRequired = expected.outputSchema.required || [];
            const actualRequired = actual.outputSchema.required || [];
            const expectedProps = expected.outputSchema.properties || {};
            const actualProps = actual.outputSchema.properties || {};
            for (const [propName, expectedProp] of Object.entries(expectedProps)) {
                if (!(propName in actualProps)) {
                    result.errors.push(`Output field "${propName}" was removed (caller expects this field)`);
                    result.valid = false;
                }
                else {
                    const actualProp = actualProps[propName];
                    const expProp = expectedProp;
                    if (actualProp && expProp.type && actualProp.type !== expProp.type) {
                        result.errors.push(`Output field "${propName}" type changed: expected ${JSON.stringify(expProp.type)}, got ${JSON.stringify(actualProp.type)}`);
                        result.valid = false;
                    }
                }
            }
            for (const field of expectedRequired) {
                if (!actualRequired.includes(field) && field in actualProps) {
                    result.warnings.push(`Output field "${field}" changed from required to optional`);
                }
            }
            for (const propName of Object.keys(actualProps)) {
                if (!(propName in expectedProps)) {
                    result.warnings.push(`New output field "${propName}" added`);
                }
            }
            for (const field of actualRequired) {
                if (!expectedRequired.includes(field) && field in expectedProps) {
                    result.warnings.push(`Output field "${field}" changed from optional to required`);
                }
            }
        }
        else if (expected.outputSchema && !actual.outputSchema) {
            result.errors.push('Output schema was removed');
            result.valid = false;
        }
        else if (!expected.outputSchema && actual.outputSchema) {
            result.warnings.push('Output schema was added');
        }
    }
    return result;
}
export function validatePromptSchema(actual, expected, mode, passedArgs) {
    const result = {
        valid: true,
        errors: [],
        warnings: [],
    };
    if (mode === 'ignore') {
        return result;
    }
    logger.debug(`Validating prompt schema in ${mode} mode`);
    if (actual.name !== expected.name) {
        result.errors.push(`Prompt name mismatch: expected "${expected.name}", got "${actual.name}"`);
        result.valid = false;
    }
    if (mode === 'strict') {
        if (actual.description !== expected.description) {
            result.errors.push(`Description mismatch: expected "${expected.description}", got "${actual.description}"`);
            result.valid = false;
        }
        if (!deepEqual(actual.arguments, expected.arguments)) {
            result.errors.push('Arguments do not match exactly');
            result.valid = false;
        }
    }
    else {
        const expectedArgs = expected.arguments || [];
        const actualArgs = actual.arguments || [];
        const expectedRequired = expectedArgs.filter((a) => a.required).map((a) => a.name);
        const actualRequired = actualArgs.filter((a) => a.required).map((a) => a.name);
        const passedArgNames = passedArgs ? Object.keys(passedArgs) : null;
        const hasPassedArgs = passedArgNames && passedArgNames.length > 0;
        for (const argName of actualRequired) {
            if (!expectedRequired.includes(argName)) {
                if (!hasPassedArgs || !passedArgNames.includes(argName)) {
                    result.errors.push(`New required argument "${argName}" added (breaking change)`);
                    result.valid = false;
                }
            }
        }
        if (hasPassedArgs) {
            for (const argName of passedArgNames) {
                const actualArg = actualArgs.find((a) => a.name === argName);
                if (!actualArg) {
                    result.errors.push(`Argument "${argName}" no longer exists in schema`);
                    result.valid = false;
                }
            }
        }
        else {
            for (const argName of expectedRequired) {
                const actualArg = actualArgs.find((a) => a.name === argName);
                if (!actualArg) {
                    result.errors.push(`Required argument "${argName}" is missing`);
                    result.valid = false;
                }
                else if (!actualArg.required) {
                    result.errors.push(`Required argument "${argName}" is no longer required`);
                    result.valid = false;
                }
            }
        }
        for (const arg of actualArgs) {
            const expectedArg = expectedArgs.find((a) => a.name === arg.name);
            if (!expectedArg && !arg.required) {
                result.warnings.push(`New optional argument "${arg.name}" added`);
            }
        }
    }
    return result;
}
export function formatValidationError(result, itemType) {
    const lines = [`Schema validation failed for ${itemType}:`];
    for (const error of result.errors) {
        lines.push(`  - ${error}`);
    }
    if (result.warnings.length > 0) {
        lines.push('Warnings:');
        for (const warning of result.warnings) {
            lines.push(`  - ${warning}`);
        }
    }
    return lines.join('\n');
}
//# sourceMappingURL=schema-validator.js.map