/**
 * TypeScript verifier — runs `tsc --noEmit` on the realized backend
 * tree, parses error output, and returns structured `VerifyError`s.
 *
 * The first concrete implementation of the `PostEmitVerifier` interface.
 * Used for any TypeScript realize target (the default stack today).
 *
 * Design notes:
 *   - We rely on the realized project's own `node_modules/.bin/tsc`
 *     and `tsconfig.json` (installed by the realize-emit step).
 *     The framework doesn't bundle a tsc — the user's project owns
 *     its own TypeScript version.
 *   - `applies` checks for the presence of `tsconfig.json` in
 *     `<outputDir>/<subpath>/`. Future stacks (e.g. a Java target)
 *     would have their own verifier with their own `applies`.
 *   - Output parsing matches the standard tsc message format:
 *       `path/to/file.ts(line,col): error TSXXXX: Message text.`
 *   - tsc tool-failure (binary missing, install needed) → ok=false
 *     + notes explain. The feedback runner skips the verifier; the
 *     realize pipeline does NOT crash.
 */
import { existsSync } from 'fs';
import { join, relative } from 'path';
import { spawnSync } from 'child_process';
const TSC_LINE_RE = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.+)$/;
/**
 * Resolve the directory tsc should run in. Defaults to
 * `<outputDir>/backend` (the canonical SpecVerse TypeScript target).
 * Caller can override via `ctx.subpath`.
 */
function resolveSubpath(ctx) {
    if (ctx.subpath)
        return join(ctx.outputDir, ctx.subpath);
    return join(ctx.outputDir, 'backend');
}
export const TSC_VERIFIER = {
    id: 'tsc',
    name: 'TypeScript Compiler',
    enabledByDefault: true,
    applies: (ctx) => {
        if (ctx.targetLanguage && ctx.targetLanguage !== 'typescript')
            return false;
        const dir = resolveSubpath(ctx);
        return existsSync(join(dir, 'tsconfig.json'));
    },
    verify: async (ctx) => {
        const dir = resolveSubpath(ctx);
        const start = Date.now();
        const notes = [];
        // Auto-install deps if needed. The realize pipeline emits a
        // scaffolded project tree (package.json + tsconfig.json) but does
        // not run `npm install`, so node_modules/.bin/tsc is missing on
        // first verify. Without auto-install the framework would silently
        // no-op default-on (after the engines 6.52 flip). Run `npm install`
        // once when:
        //   - `package.json` exists
        //   - `node_modules/.bin/tsc` is missing
        //   - `ctx.autoInstall !== false` (defaults to true)
        // Bounded by a 5-min timeout — pathological dep trees fail loudly
        // rather than hang the realize pipeline.
        const localTsc = join(dir, 'node_modules', '.bin', 'tsc');
        const pkgJson = join(dir, 'package.json');
        const autoInstall = ctx.autoInstall !== false;
        if (!existsSync(localTsc) && existsSync(pkgJson) && autoInstall) {
            const installResult = spawnSync('npm', ['install', '--no-audit', '--no-fund', '--silent'], {
                cwd: dir,
                encoding: 'utf8',
                maxBuffer: 50 * 1024 * 1024,
                timeout: 5 * 60_000,
            });
            if (installResult.error || installResult.status !== 0) {
                return {
                    verifierId: 'tsc',
                    durationMs: Date.now() - start,
                    ok: false,
                    errors: [],
                    notes: [
                        `tsc verifier auto-install failed in ${dir}: ${installResult.error?.message ?? `npm exit ${installResult.status}`}. Hint: run \`npm install\` manually before realize, or set ctx.autoInstall=false.`,
                    ],
                };
            }
            notes.push(`auto-installed deps in ${dir} for tsc verifier`);
        }
        // Locate the project-local tsc. Fall back to PATH `npx tsc` only if
        // node_modules/.bin/tsc isn't installed yet.
        let cmd;
        let args;
        if (existsSync(localTsc)) {
            cmd = localTsc;
            args = ['--noEmit'];
        }
        else {
            cmd = 'npx';
            args = ['--no-install', 'tsc', '--noEmit'];
        }
        const result = spawnSync(cmd, args, {
            cwd: dir,
            encoding: 'utf8',
            maxBuffer: 50 * 1024 * 1024, // 50MB — error output on a heavy
            // realize can be megabytes.
        });
        const durationMs = Date.now() - start;
        if (result.error) {
            return {
                verifierId: 'tsc',
                durationMs,
                ok: false,
                errors: [],
                notes: [
                    ...notes,
                    `tsc could not be invoked: ${result.error.message}. ` +
                        `Hint: run \`npm install\` in ${dir} before realizing, or ensure ` +
                        `tsc is on PATH.`,
                ],
            };
        }
        const combined = (result.stdout ?? '') + (result.stderr ?? '');
        const errors = [];
        // tsc reports file paths relative to its CWD (the subpath). Convert
        // those to paths relative to outputDir so the feedback runner's
        // `join(outputDir, file)` resolves correctly.
        const subpathRel = relative(ctx.outputDir, dir);
        const toOutputRel = (file) => subpathRel === '' ? file : `${subpathRel}/${file}`;
        for (const line of combined.split('\n')) {
            const m = TSC_LINE_RE.exec(line.trimEnd());
            if (!m)
                continue;
            errors.push({
                file: toOutputRel(m[1]),
                line: parseInt(m[2], 10),
                col: parseInt(m[3], 10),
                severity: m[4],
                code: m[5],
                message: m[6],
            });
        }
        return {
            verifierId: 'tsc',
            durationMs,
            // ok: true even when errors > 0 — that's the EXPECTED path.
            // ok: false reserved for tool-level failures (binary missing, etc.).
            ok: true,
            errors,
            notes,
        };
    },
};
//# sourceMappingURL=tsc.js.map