/**
 * Run-paths helper — computes the structured per-case output layout
 * and writes navigation aids (README, manifest, score) into it.
 *
 * Layout:
 *   runs/{kind}/{case}/{YYYY-MM-DD-HHMM}_{label}/
 *     README.md            ← human-readable orientation
 *     manifest.json        ← machine-readable run config
 *     score.json           ← scoring detail (optional)
 *     validate.txt         ← `spv validate` output on the canonical spec
 *
 *     input/               ← what we analysed (source) or what was requested (NL)
 *     facts/               ← structural pre-pass output (analyse only)
 *       facts.md           ← markdown injected into the prompt as preamble
 *       facts.json         ← SpecVerseFacts JSON when an adapter ran (optional)
 *     prompts/             ← assembled LLM prompts (system + user, per pass)
 *     llm-output/          ← raw LLM responses (before code-fence extraction)
 *     specs/               ← spec progression — same shape as `spv init`'s specs/
 *       draft.specly       ← always present; written after the analyse/create pass
 *       verified.specly    ← present iff verify ran
 *       optimized.specly   ← present iff optimize ran (future)
 *       main.specly        ← real file copy of the latest stage; canonical "final"
 *     project/             ← empty until materialized via spv init + spv realize
 *       README.md          ← recipe for manual realize
 *       (after realize:)
 *       specs/main.specly + manifests/ + generated/code/ ...
 *
 * No symlinks anywhere — `specs/main.specly` is a real-file copy of
 * the latest stage's output. Survives moves/copies/zips intact.
 *
 * Originally lived in `specverse-demo-self/harness/lib/run-paths.mjs`;
 * promoted into `@specverse/engines/ai` as part of TODO #21 so end-user
 * `spv ai analyse` / `spv ai create` commands and the eval harness
 * share one writer.
 */
import { mkdirSync, writeFileSync } from 'fs';
import { join } from 'path';
// ─────────────────────────────────────────────────────────────────────
// Path helpers
// ─────────────────────────────────────────────────────────────────────
/** Folder name for one per-case run. */
export function buildRunFolder({ prompt, providerId, modelId, label }) {
    const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 16).replace('T', '-');
    const compact = stamp.replace(/-(\d\d)-(\d\d)$/, '-$1$2');
    if (label)
        return `${compact}_${label}`;
    const safeModel = (modelId || 'default').replace(/[^a-zA-Z0-9-]/g, '-');
    const tail = [prompt, providerId, safeModel].filter(Boolean).join('_');
    return `${compact}_${tail}`;
}
/** Absolute path for one per-case run folder. */
export function caseRunPath(rootDir, kind, caseName, runFolder) {
    return join(rootDir, 'runs', kind, caseName, runFolder);
}
/** Standard per-run subdir paths. */
export const SUBDIRS = ['input', 'facts', 'prompts', 'llm-output', 'specs', 'project'];
export const inputDir = (caseRunDir) => join(caseRunDir, 'input');
export const factsDir = (caseRunDir) => join(caseRunDir, 'facts');
export const promptsDir = (caseRunDir) => join(caseRunDir, 'prompts');
export const llmOutputDir = (caseRunDir) => join(caseRunDir, 'llm-output');
export const specsDir = (caseRunDir) => join(caseRunDir, 'specs');
export const projectDir = (caseRunDir) => join(caseRunDir, 'project');
/** Pre-create all subdirs at the start of a run. Idempotent. */
export function ensureSubdirs(caseRunDir) {
    mkdirSync(caseRunDir, { recursive: true });
    for (const sub of SUBDIRS) {
        mkdirSync(join(caseRunDir, sub), { recursive: true });
    }
}
/**
 * Write a stage's spec to specs/{stage}.specly AND copy into specs/main.specly.
 * No symlinks: main.specly is always a real file, overwritten by each new stage.
 * Returns the path to the stage-specific file.
 */
export function writeStageSpec(caseRunDir, stage, content) {
    mkdirSync(specsDir(caseRunDir), { recursive: true });
    const stagePath = join(specsDir(caseRunDir), `${stage}.specly`);
    const mainPath = join(specsDir(caseRunDir), 'main.specly');
    writeFileSync(stagePath, content);
    writeFileSync(mainPath, content);
    return stagePath;
}
/**
 * Keep a verify pass's output only if it's no worse than the draft.
 *
 * The verify pass (`verify-analyse` / `verify-create`) is an LLM-only "review
 * and improve this" call with no programmatic gate — so it can return prose
 * ("I need write permission…"), hoist events to the top level, or drop
 * required fields, turning a valid draft into an invalid spec WITHOUT throwing.
 * The runners' `try/catch` only fired on a thrown exception, so that returned-
 * but-broken output sailed through and clobbered the good draft.
 *
 * Contract: the caller has already written `verifiedSpec` to `specs/main.specly`
 * (via `writeStageSpec(dir, 'verified', verifiedSpec)`). This validates it; if
 * it fails AND the draft validates, it restores the draft as the canonical spec
 * and reports `reverted: true`. If both fail, it keeps the verify output (no
 * worse) and restores it at `main.specly`. `validate` is the caller's
 * `runValidate` (which validates whatever is at `specs/main.specly`).
 */
export function gateVerifyAgainstDraft(caseRunDir, draftSpec, verifiedSpec, validate) {
    const mainPath = join(specsDir(caseRunDir), 'main.specly');
    const verifiedValidation = validate(mainPath); // verified is already at main.specly
    if (verifiedValidation.ok) {
        return { finalSpec: verifiedSpec, finalStage: 'verified', validation: verifiedValidation, reverted: false };
    }
    // Verify produced an invalid spec — see whether the draft is valid.
    writeStageSpec(caseRunDir, 'draft', draftSpec); // restore draft as canonical, then validate it
    const draftValidation = validate(mainPath);
    if (draftValidation.ok) {
        return { finalSpec: draftSpec, finalStage: 'draft', validation: draftValidation, reverted: true };
    }
    // Both invalid — verify is no worse; restore it as canonical.
    writeStageSpec(caseRunDir, 'verified', verifiedSpec);
    return { finalSpec: verifiedSpec, finalStage: 'verified', validation: verifiedValidation, reverted: false };
}
/**
 * Write a project/README.md. Three shapes depending on `realized`:
 *   - null/undefined → recipe form (project/ is empty)
 *   - { ok: true }   → "materialized" — describes layout + how to run
 *   - { ok: false }  → "realize failed" — captures error + retry recipe
 */
export function writeProjectReadme(caseRunDir, opts) {
    const { caseName, realized = null } = opts;
    const lines = [];
    mkdirSync(projectDir(caseRunDir), { recursive: true });
    if (realized?.ok) {
        lines.push(`# ${caseName} — project (materialized)`);
        lines.push('');
        lines.push('Materialized from `../specs/main.specly` via `spv init` + `spv realize all`.');
        lines.push('Equivalent shell commands:');
        lines.push('');
        lines.push('```bash');
        lines.push('rm -rf project/');
        lines.push('spv init project --template default');
        lines.push('cp specs/main.specly project/specs/main.specly');
        lines.push('cd project && spv realize all specs/main.specly');
        lines.push('```');
        lines.push('');
        lines.push('Layout (standard `spv init` output):');
        lines.push('- `specs/main.specly` — the canonical spec from this run');
        lines.push('- `manifests/implementation.yaml` — Fastify + Prisma + React (default template)');
        lines.push('- `generated/code/backend/` — Fastify + Prisma + generated CLI');
        lines.push('- `generated/code/frontend/` — React app');
        lines.push('- `generated/code/tools/` — MCP server, VSCode extension');
        lines.push('');
        lines.push('To run: `cd project && npm install && npm run dev:backend` (terminal 1)');
        lines.push('and `npm run dev:frontend` (terminal 2).');
    }
    else if (realized && !realized.ok) {
        lines.push(`# ${caseName} — project (realize failed)`);
        lines.push('');
        lines.push('The realize step was attempted but failed:');
        lines.push('');
        lines.push('```');
        lines.push(realized.error || '(unknown error)');
        lines.push('```');
        lines.push('');
        lines.push('Manual recipe to retry:');
        lines.push('');
        lines.push('```bash');
        lines.push('rm -rf project/');
        lines.push('spv init project --template default');
        lines.push('cp specs/main.specly project/specs/main.specly');
        lines.push('cd project && spv realize all specs/main.specly');
        lines.push('```');
    }
    else {
        lines.push(`# ${caseName} — project (empty until materialized)`);
        lines.push('');
        lines.push('This directory becomes a SpecVerse project once you materialize the');
        lines.push('canonical spec. Recipe:');
        lines.push('');
        lines.push('```bash');
        lines.push('rm -rf project/         # clear this README-only stub');
        lines.push('spv init project --template default');
        lines.push('cp specs/main.specly project/specs/main.specly');
        lines.push('cd project && spv realize all specs/main.specly');
        lines.push('```');
        lines.push('');
        lines.push('After realize completes, `generated/code/` contains the produced');
        lines.push('backend / frontend / tools — equivalent to what `spv init` + `spv');
        lines.push('realize` would have built from this spec ground-up.');
        lines.push('');
        lines.push('`spv ai analyse` and `spv ai create` can do this automatically with `--realize`.');
    }
    lines.push('');
    writeFileSync(join(projectDir(caseRunDir), 'README.md'), lines.join('\n'));
}
/** Write manifest.json — machine-readable run config + result. */
export function writeManifest(caseRunDir, manifest) {
    mkdirSync(caseRunDir, { recursive: true });
    writeFileSync(join(caseRunDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
}
/** Write score.json. */
export function writeScore(caseRunDir, score) {
    mkdirSync(caseRunDir, { recursive: true });
    writeFileSync(join(caseRunDir, 'score.json'), JSON.stringify(score, null, 2));
}
/** Write a human-readable README.md orienting a future reader to the run. */
export function writeReadme(caseRunDir, info) {
    const lines = [];
    const c = info.caseEntry;
    const m = info.manifest;
    const s = info.score;
    lines.push(`# ${c.name} — ${info.runFolder}`);
    lines.push('');
    // What's being analysed/created
    lines.push(`## What's being ${info.kind === 'create' ? 'created' : 'analysed'}`);
    if (c.upstream) {
        const up = c.upstream;
        lines.push(`**Source:** ${up.repo || '(no repo declared)'}` +
            (up.pin_sha ? ` @ \`${up.pin_sha.slice(0, 8)}\`` : '') +
            (up.pin_date ? ` (${up.pin_date})` : ''));
    }
    if (c.stack)
        lines.push(`**Stack:** ${c.stack}`);
    if (c.class)
        lines.push(`**Class:** ${c.class}` +
            (c.class === 2 ? ' (hand-written upstream, not SpecVerse-realized)' : ''));
    if (c.nl_request) {
        const firstLine = c.nl_request.trim().split('\n')[0];
        lines.push(`**Request:** "${firstLine.slice(0, 200)}${firstLine.length > 200 ? '…' : ''}"`);
    }
    if (c.domain_notes) {
        const firstSentence = c.domain_notes.trim().split(/\.\s/)[0] + '.';
        lines.push(`**Domain:** ${firstSentence.replace(/\n/g, ' ').slice(0, 200)}`);
    }
    lines.push('');
    // Run config
    lines.push('## Run configuration');
    lines.push(`- **Started:** ${m.startedAt}`);
    lines.push(`- **Provider:** ${m.provider} (${m.model})`);
    lines.push(`- **Prompt:** ${m.prompt}`);
    if (m.workflow)
        lines.push(`- **Workflow:** ${m.workflow}`);
    if (m.factsPath)
        lines.push(`- **Structural facts:** \`${m.factsPath}\``);
    if (m.verify)
        lines.push(`- **Verify pass:** enabled (two-stage)`);
    if (m.realize)
        lines.push(`- **Realize pass:** enabled (project/ materialized)`);
    if (m.wallTimeMs != null)
        lines.push(`- **Wall time:** ${(m.wallTimeMs / 1000).toFixed(1)}s`);
    lines.push('');
    // Results
    lines.push('## Results');
    if (m.validateFinal)
        lines.push(`- \`spv validate\` final: **${m.validateFinal === 'OK' ? '✓ pass' : '✗ ' + m.validateFinal}**`);
    if (s?.validity) {
        const order = ['parse', 'schema', 'infer', 'realize', 'tsc', 'tests'];
        const bits = order.filter(k => s.validity[k] !== undefined)
            .map(k => `${k}=${s.validity[k] ? '✓' : '✗'}`).join(' · ');
        lines.push(`- Validity battery: ${bits}`);
    }
    const r = s?.round_trip ?? s?.domain_coverage;
    if (r) {
        let total = 0, matched = 0;
        for (const dim of ['entities', 'relationships', 'lifecycles', 'operations']) {
            const x = r[dim];
            if (x?.expected !== undefined) {
                total += x.expected;
                matched += x.matched || 0;
            }
        }
        lines.push(`- Domain coverage: ${matched}/${total} (${total ? ((matched / total) * 100).toFixed(0) : 0}%)`);
        for (const dim of ['entities', 'relationships', 'lifecycles', 'operations']) {
            const x = r[dim];
            if (x?.expected !== undefined) {
                lines.push(`  - ${dim}: ${x.matched || 0}/${x.expected}` +
                    (x.missing?.length ? ` (missing: ${x.missing.join(', ')})` : '') +
                    (x.extra?.length ? ` (extra: ${x.extra.join(', ')})` : ''));
            }
        }
    }
    lines.push('');
    // Layout
    lines.push('## Layout');
    lines.push('```');
    lines.push('├── README.md          this file');
    lines.push('├── manifest.json      machine-readable run config');
    lines.push('├── score.json         scoring detail');
    lines.push('├── validate.txt       `spv validate` output on specs/main.specly');
    lines.push('├── input/             ' + (info.kind === 'analyse' ? 'source under analysis' : 'requirements.md (the NL request)'));
    if (info.kind === 'analyse') {
        lines.push('├── facts/             structural pre-pass output');
        lines.push('│   ├── facts.md       markdown injected as preamble');
        lines.push('│   └── facts.json     SpecVerseFacts (when adapter ran)');
    }
    lines.push('├── prompts/           assembled LLM prompts (system + user, per pass)');
    lines.push('├── llm-output/        raw LLM responses (before code-fence extraction)');
    lines.push('├── specs/             spec progression');
    lines.push('│   ├── draft.specly       after first pass');
    if (m.verify)
        lines.push('│   ├── verified.specly    after verify pass');
    lines.push('│   └── main.specly        canonical (= latest stage)');
    lines.push('└── project/           SpecVerse project shape; populated when --realize runs');
    lines.push('```');
    lines.push('');
    // Source re-indexing tip — how to re-run gitnexus natively
    if (info.kind === 'analyse') {
        lines.push('## Re-index the source with gitnexus (optional)');
        lines.push('');
        lines.push('The structural index (`.gitnexus/`) is excluded from `input/` to keep');
        lines.push('the run dir lean. To inspect the call graph or re-extract facts:');
        lines.push('');
        lines.push('```bash');
        lines.push('cd input/');
        lines.push('gitnexus analyze .');
        lines.push('gitnexus cypher --repo $(basename $PWD) "MATCH (c:Class) RETURN c.name LIMIT 20"');
        lines.push('```');
        lines.push('');
    }
    // See also
    lines.push('## See also');
    const caseDirRel = `../../../../corpus/${info.kind}/${c.name}`;
    lines.push(`- Case definition: \`${caseDirRel}/case.yaml\``);
    if (c.expected_spec_path) {
        const baselineRel = c.expected_spec_path.split(`/corpus/${info.kind}/${c.name}/`)[1];
        if (baselineRel) {
            lines.push(`- Frozen baseline: \`${caseDirRel}/${baselineRel}\``);
        }
    }
    if (m.experimentRunId) {
        lines.push(`- Experiment-batch report: \`../../../../reports/${m.experimentRunId}.json\``);
    }
    lines.push('');
    mkdirSync(caseRunDir, { recursive: true });
    writeFileSync(join(caseRunDir, 'README.md'), lines.join('\n'));
}
//# sourceMappingURL=run-paths.js.map