// tools/scan-orphan-spec-refs.ts — SPEC-582: MCP tool wrapper for orphan spec ID detection
import { z } from 'zod';
import { execFile as execFileCb } from 'node:child_process';
import { promisify } from 'node:util';
import { join } from 'node:path';
import { runOrphanDetection } from '../engine/detectors/orphan-spec-refs.js';
import { compactResult, compactError } from './output-formatter.js';
import { resolveProjectIdOrAutoDetect } from './resolve-project-id.js';
import { handleCreateSpec } from './create-spec.js';
import { handleUpdateStatus } from './update-status.js';
const execFile = promisify(execFileCb);
// ---------------------------------------------------------------------------
// Input schema
// ---------------------------------------------------------------------------
export const ScanOrphanSpecRefsInputSchema = {
    projectPath: z
        .string()
        .max(4096)
        .optional()
        .describe('Absolute path to the project root. Auto-detected from git root when omitted.'),
    range: z
        .enum(['head', 'since-last-release', 'origin-main..head'])
        .optional()
        .default('since-last-release')
        .describe('Commit range to scan. Options: head (last 20 commits), since-last-release (since last v* tag), origin-main..head (unpushed commits). Default: since-last-release.'),
    mode: z
        .enum(['report-only', 'create-retroactive', 'strict'])
        .optional()
        .default('report-only')
        .describe('Execution mode. Options: report-only (list orphans with next steps), create-retroactive (auto-create specs for each orphan and mark done), strict (fail with error payload if any orphan found — suitable for CI). Default: report-only.'),
};
export const ScanOrphanSpecRefsInput = z.object(ScanOrphanSpecRefsInputSchema);
// ---------------------------------------------------------------------------
// Commit type → spec type mapping
// ---------------------------------------------------------------------------
const COMMIT_TYPE_TO_SPEC_TYPE = {
    fix: 'bug',
    feat: 'feature',
    refactor: 'refactor',
    docs: 'feature',
    chore: 'feature',
    perf: 'feature',
    test: 'feature',
    build: 'feature',
    ci: 'feature',
    revert: 'bug',
};
function inferSpecType(subject) {
    const match = /^(\w+)(?:\(|:)/.exec(subject);
    const prefix = match?.[1]?.toLowerCase() ?? 'feat';
    return COMMIT_TYPE_TO_SPEC_TYPE[prefix] ?? 'feature';
}
// ---------------------------------------------------------------------------
// Helpers for create-retroactive mode
// ---------------------------------------------------------------------------
/** Fetch the diff for a commit sha (first-parent, truncated at 4000 chars) */
async function fetchCommitDiff(projectPath, sha) {
    try {
        const { stdout } = await execFile('git', ['show', '--stat', sha], {
            cwd: projectPath,
            maxBuffer: 256 * 1024,
            timeout: 15_000,
        });
        return stdout.slice(0, 4000);
    }
    catch {
        return '';
    }
}
/** Create a spec retroactively for an orphan commit reference */
async function remediateOrphan(projectPath, orphan) {
    const diff = await fetchCommitDiff(projectPath, orphan.commitSha);
    const specIdStr = `SPEC-${orphan.specId}`;
    const description = `Retroactively created from commit ${orphan.commitSha.slice(0, 8)}.\n\n` +
        `Commit: ${orphan.commitSubject}\n\n` +
        (diff ? `Changes:\n\`\`\`\n${diff}\n\`\`\`` : '');
    const createResult = await handleCreateSpec({
        projectPath,
        title: orphan.commitSubject,
        description,
        type: inferSpecType(orphan.commitSubject),
    });
    if (createResult.isError === true) {
        return null;
    }
    // Mark as done since code is already merged
    await handleUpdateStatus({
        projectPath,
        specId: specIdStr,
        status: 'done',
        reviewNotes: `Auto-remediated by scan_orphan_spec_refs for commit ${orphan.commitSha.slice(0, 8)}.`,
    });
    const newSpecPath = join(projectPath, 'planu', 'specs', `${specIdStr}-retroactive`);
    return { specId: orphan.specId, newSpecPath, commitSha: orphan.commitSha };
}
// ---------------------------------------------------------------------------
// Handler
// ---------------------------------------------------------------------------
export async function handleScanOrphanSpecRefs(args) {
    // Resolve project
    const resolved = await resolveProjectIdOrAutoDetect({ projectPath: args.projectPath });
    if (!resolved.ok) {
        return resolved.errorResult;
    }
    const { projectPath } = resolved;
    const range = args.range ?? 'since-last-release';
    const mode = args.mode ?? 'report-only';
    // Run detection
    const { orphans, humanSummary } = await runOrphanDetection(projectPath, range);
    // strict mode: fail immediately if any orphan found
    if (mode === 'strict') {
        if (orphans.length === 0) {
            return compactResult('No orphan SPEC references found');
        }
        const ids = orphans.map((o) => `SPEC-${o.specId}`).join(', ');
        const message = `Orphan SPEC references detected: ${ids}.\n` +
            `Remediate with: scan_orphan_spec_refs({ mode: "create-retroactive" })`;
        return compactError(message);
    }
    // create-retroactive mode: auto-create specs for each orphan
    if (mode === 'create-retroactive') {
        if (orphans.length === 0) {
            return compactResult('No orphan SPEC references found — nothing to remediate.');
        }
        const remediated = [];
        const failed = [];
        for (const orphan of orphans) {
            const entry = await remediateOrphan(projectPath, orphan);
            if (entry !== null) {
                remediated.push(entry);
            }
            else {
                failed.push(orphan.specId);
            }
        }
        const output = {
            orphans,
            remediated,
            humanSummary: remediated.length === orphans.length
                ? `Remediated ${remediated.length} orphan(s). session-context.md will regenerate on next update_status(done).`
                : `Remediated ${remediated.length}/${orphans.length} orphan(s). Failed: ${failed.map((id) => `SPEC-${id}`).join(', ')}.`,
        };
        return compactResult(formatOutput(output));
    }
    // report-only mode (default)
    const output = { orphans, humanSummary };
    return compactResult(formatOutput(output));
}
// ---------------------------------------------------------------------------
// Output formatting
// ---------------------------------------------------------------------------
function formatOutput(output) {
    const lines = [];
    lines.push(`## Orphan Spec Scan`, '');
    lines.push(output.humanSummary);
    if (output.orphans.length > 0) {
        lines.push('', '### Orphan References');
        for (const o of output.orphans) {
            const mark = o.status === 'missing' ? 'MISSING' : 'found';
            lines.push(`- **SPEC-${o.specId}** [${mark}] — \`${o.commitSha.slice(0, 8)}\` ${o.commitSubject}`);
        }
    }
    if (output.remediated && output.remediated.length > 0) {
        lines.push('', '### Remediated');
        for (const r of output.remediated) {
            lines.push(`- SPEC-${r.specId} created from \`${r.commitSha.slice(0, 8)}\``);
        }
    }
    return lines.join('\n');
}
//# sourceMappingURL=scan-orphan-spec-refs.js.map