// engine/api-validation/graphql-schema-validator.ts — Validate GraphQL schema vs resolvers (SPEC-322)
import { readFile } from 'node:fs/promises';
import { glob } from 'glob';
// ---------------------------------------------------------------------------
// GraphQL SDL parser (minimal — extracts Query/Mutation/Subscription fields)
// ---------------------------------------------------------------------------
const OPERATION_TYPE_REGEX = /type\s+(Query|Mutation|Subscription)\s*\{([^}]+)\}/gs;
const FIELD_NAME_REGEX = /^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:\([^)]*\))?\s*:/m;
function parseGraphQLOperations(sdl) {
    const operations = [];
    let typeMatch;
    const typeRegex = new RegExp(OPERATION_TYPE_REGEX.source, OPERATION_TYPE_REGEX.flags);
    while ((typeMatch = typeRegex.exec(sdl)) !== null) {
        const kind = typeMatch[1];
        const body = typeMatch[2] ?? '';
        const lines = body.split('\n');
        for (const line of lines) {
            const fieldMatch = FIELD_NAME_REGEX.exec(line);
            if (fieldMatch?.[1]) {
                operations.push({ name: fieldMatch[1], kind });
            }
        }
    }
    return operations;
}
// ---------------------------------------------------------------------------
// Resolver scanner
// ---------------------------------------------------------------------------
const RESOLVER_PATTERNS = [
    // Apollo: { Query: { resolverName() } }
    /['"`]([a-zA-Z_][a-zA-Z0-9_]*)['"`]\s*:\s*(?:async\s+)?(?:function|\(|[a-zA-Z_])/g,
    // Direct assignment: const resolverNameResolver = async () =>
    /(?:const|let)\s+([a-zA-Z_][a-zA-Z0-9_]*Resolver)\s*=/g,
    // Object method shorthand: resolverName(parent, args, ctx)
    /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*(?:parent|root|_),?\s*(?:args|_args)/g,
];
async function scanSourceFilesForResolvers(projectPath) {
    const patterns = [
        `${projectPath}/**/*.ts`,
        `${projectPath}/**/*.tsx`,
        `${projectPath}/**/*.js`,
        `${projectPath}/**/*.jsx`,
    ];
    const ignores = ['**/node_modules/**', '**/dist/**', '**/.next/**', '**/build/**'];
    const resolvers = [];
    const seen = new Set();
    const files = await glob(patterns, { ignore: ignores });
    for (const file of files) {
        const content = await readFile(file, 'utf-8').catch(() => '');
        if (!content) {
            continue;
        }
        if (!content.includes('resolver') &&
            !content.includes('Query') &&
            !content.includes('Mutation')) {
            continue;
        }
        for (const pattern of RESOLVER_PATTERNS) {
            const re = new RegExp(pattern.source, pattern.flags);
            let match;
            while ((match = re.exec(content)) !== null) {
                const name = match[1];
                if (name && !seen.has(name)) {
                    seen.add(name);
                    resolvers.push({ name, file });
                }
            }
        }
    }
    return resolvers;
}
// ---------------------------------------------------------------------------
// Main validator
// ---------------------------------------------------------------------------
export async function validateGraphQLSchema(schemaPath, projectPath) {
    const sdl = await readFile(schemaPath, 'utf-8');
    const operations = parseGraphQLOperations(sdl);
    const resolvers = await scanSourceFilesForResolvers(projectPath);
    const resolverNames = new Set(resolvers.map((r) => r.name));
    const missingEndpoints = [];
    const undocumentedEndpoints = [];
    for (const op of operations) {
        if (!resolverNames.has(op.name)) {
            missingEndpoints.push({
                path: `${op.kind}.${op.name}`,
                method: op.kind,
                severity: 'critical',
                message: `GraphQL ${op.kind} "${op.name}" is defined in the schema but no resolver implementation was detected in the project source files.`,
            });
        }
    }
    const opNames = new Set(operations.map((o) => o.name));
    for (const resolver of resolvers) {
        if (!opNames.has(resolver.name) && isLikelyOperationResolver(resolver.name)) {
            undocumentedEndpoints.push({
                path: resolver.name,
                method: 'resolver',
                severity: 'info',
                message: `Resolver "${resolver.name}" found in ${resolver.file} does not correspond to any operation in the GraphQL schema.`,
            });
        }
    }
    const findings = [...missingEndpoints, ...undocumentedEndpoints];
    return {
        contractFormat: 'graphql',
        contractVersion: 'SDL',
        contractTitle: 'GraphQL Schema',
        totalContractEndpoints: operations.length,
        totalImplEndpoints: resolvers.length,
        missingEndpoints,
        undocumentedEndpoints,
        schemaDrift: [],
        breakingChanges: [],
        findings,
        summary: buildSummary(missingEndpoints.length, undocumentedEndpoints.length),
    };
}
function isLikelyOperationResolver(name) {
    return /^(?:get|create|update|delete|list|find|add|remove|fetch|load|save|set)[A-Z]/.test(name);
}
function buildSummary(missing, undocumented) {
    if (missing === 0 && undocumented === 0) {
        return 'GraphQL schema and resolver implementations are fully aligned.';
    }
    const parts = [];
    if (missing > 0) {
        parts.push(`${missing} operation(s) in schema have no resolver implementation`);
    }
    if (undocumented > 0) {
        parts.push(`${undocumented} resolver(s) found that are not in the schema`);
    }
    return parts.join('; ') + '.';
}
//# sourceMappingURL=graphql-schema-validator.js.map