// SPEC-597-EXEMPT: canonical mapping (npm-package↔framework, keyword catalogue)
// — must enumerate framework names by design; no detection logic to migrate.
// engine/api-validation/openapi-impl-validator.ts — Validate OpenAPI contract vs implementation (SPEC-322)
import { readFile } from 'node:fs/promises';
import { glob } from 'glob';
// ---------------------------------------------------------------------------
// Route pattern detectors
// ---------------------------------------------------------------------------
const ROUTE_PATTERNS = [
    // Express: app.get('/path', ...) or router.post('/path')
    {
        regex: /(?:app|router)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/g,
        framework: 'express',
    },
    // Fastify: fastify.get('/path')
    {
        regex: /fastify\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/g,
        framework: 'fastify',
    },
    // Next.js API routes: export async function GET/POST/PUT/DELETE
    {
        regex: /export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s*\(/g,
        framework: 'nextjs',
    },
    // FastAPI: @router.get('/path') or @app.get('/path')
    {
        regex: /@(?:router|app)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/g,
        framework: 'fastapi',
    },
];
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function normalizeMethod(m) {
    return m.toUpperCase();
}
function normalizePath(p) {
    // Convert :param → {param} for comparison with OpenAPI style
    return p.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '{$1}');
}
function pathsMatch(contractPath, implPath) {
    const norm = normalizePath(implPath);
    if (contractPath === norm || contractPath === implPath) {
        return true;
    }
    const contractParts = contractPath.split('/');
    const implParts = norm.split('/');
    if (contractParts.length !== implParts.length) {
        return false;
    }
    return contractParts.every((part, i) => {
        if (part.startsWith('{') && part.endsWith('}')) {
            return true;
        }
        return part === implParts[i];
    });
}
async function scanSourceFilesForRoutes(projectPath) {
    const patterns = [
        `${projectPath}/**/*.ts`,
        `${projectPath}/**/*.tsx`,
        `${projectPath}/**/*.js`,
        `${projectPath}/**/*.jsx`,
        `${projectPath}/**/*.py`,
    ];
    const ignores = ['**/node_modules/**', '**/dist/**', '**/.next/**', '**/build/**'];
    const routes = [];
    const files = await glob(patterns, { ignore: ignores });
    for (const file of files) {
        const content = await readFile(file, 'utf-8').catch(() => '');
        if (!content) {
            continue;
        }
        for (const { regex, framework } of ROUTE_PATTERNS) {
            const re = new RegExp(regex.source, regex.flags);
            let match;
            while ((match = re.exec(content)) !== null) {
                if (framework === 'nextjs') {
                    const relPath = file
                        .replace(projectPath, '')
                        .replace(/\\/g, '/')
                        .replace(/\/app\/|\/pages\/api\//, '/')
                        .replace(/\/route\.(ts|js|tsx|jsx)$/, '')
                        .replace(/\.(ts|js|tsx|jsx)$/, '');
                    routes.push({
                        method: normalizeMethod(match[1] ?? 'GET'),
                        path: relPath || '/',
                        framework,
                        file,
                    });
                }
                else {
                    const rawMethod = match[1] ?? '';
                    const rawPath = match[2] ?? '';
                    if (rawMethod && rawPath) {
                        routes.push({
                            method: normalizeMethod(rawMethod),
                            path: rawPath,
                            framework,
                            file,
                        });
                    }
                }
            }
        }
    }
    // Deduplicate by method+path (multiple patterns may match the same route)
    const seen = new Set();
    return routes.filter((r) => {
        const key = `${r.method}:${r.path}`;
        if (seen.has(key)) {
            return false;
        }
        seen.add(key);
        return true;
    });
}
// ---------------------------------------------------------------------------
// OpenAPI contract reader
// ---------------------------------------------------------------------------
async function readOpenApiDoc(contractPath) {
    const content = await readFile(contractPath, 'utf-8');
    if (contractPath.endsWith('.json')) {
        return JSON.parse(content);
    }
    // Minimal YAML fallback: strip comments and attempt JSON parse
    const stripped = content
        .split('\n')
        .filter((line) => !line.trim().startsWith('#'))
        .join('\n');
    try {
        return JSON.parse(stripped);
    }
    catch {
        return {};
    }
}
// ---------------------------------------------------------------------------
// Main validator
// ---------------------------------------------------------------------------
const HTTP_METHODS_LIST = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'];
export async function validateOpenApiContract(contractPath, projectPath) {
    const doc = await readOpenApiDoc(contractPath);
    const implRoutes = await scanSourceFilesForRoutes(projectPath);
    const contractEndpoints = [];
    const paths = doc.paths ?? {};
    for (const [apiPath, pathItem] of Object.entries(paths)) {
        for (const method of HTTP_METHODS_LIST) {
            if (pathItem[method] !== undefined) {
                contractEndpoints.push({ path: apiPath, method: method.toUpperCase() });
            }
        }
    }
    const missingEndpoints = [];
    for (const endpoint of contractEndpoints) {
        const found = implRoutes.some((r) => r.method === endpoint.method && pathsMatch(endpoint.path, r.path));
        if (!found) {
            missingEndpoints.push({
                path: endpoint.path,
                method: endpoint.method,
                severity: 'critical',
                message: `Endpoint ${endpoint.method} ${endpoint.path} is documented in the contract but no implementation was found in the project source files.`,
            });
        }
    }
    const undocumentedEndpoints = [];
    for (const route of implRoutes) {
        const found = contractEndpoints.some((e) => e.method === route.method && pathsMatch(e.path, route.path));
        if (!found) {
            undocumentedEndpoints.push({
                path: route.path,
                method: route.method,
                severity: 'warning',
                message: `Endpoint ${route.method} ${route.path} is implemented (found in ${route.file}) but is not documented in the API contract.`,
            });
        }
    }
    const findings = [...missingEndpoints, ...undocumentedEndpoints];
    const contractInfo = doc.info ?? {};
    return {
        contractFormat: 'openapi',
        contractVersion: contractInfo.version ?? 'unknown',
        contractTitle: contractInfo.title ?? 'Unknown API',
        totalContractEndpoints: contractEndpoints.length,
        totalImplEndpoints: implRoutes.length,
        missingEndpoints,
        undocumentedEndpoints,
        schemaDrift: [],
        breakingChanges: [],
        findings,
        summary: buildSummary(missingEndpoints.length, undocumentedEndpoints.length),
    };
}
function buildSummary(missing, undocumented) {
    if (missing === 0 && undocumented === 0) {
        return 'Contract and implementation are fully aligned. No issues found.';
    }
    const parts = [];
    if (missing > 0) {
        parts.push(`${missing} endpoint(s) documented in the contract but missing from implementation`);
    }
    if (undocumented > 0) {
        parts.push(`${undocumented} endpoint(s) implemented but not documented in the contract`);
    }
    return parts.join('; ') + '.';
}
//# sourceMappingURL=openapi-impl-validator.js.map