/**
 * commands/bootstrap.mjs — `apt-tools bootstrap <project-dir>` (C47).
 *
 * Thin CLI wrapper over the deterministic bootstrap helpers
 * (bootstrap/recon.mjs, bootstrap/detect-mode.mjs,
 * bootstrap/product-context-sidecar.mjs). Invoked by the apt:bootstrap SKILL.md
 * body (for recon + mode detection + sidecar write) and by apt-tools init
 * (auto-chained at install completion — D-04).
 *
 * Design principle: this command is DETERMINISTIC. It does NOT synthesize
 * PROJECT.md prose. That's the SKILL.md body's job, which spawns the host LLM
 * with the recon result + mode signal + (optional existing PROJECT.md) as
 * context and produces the filled-in 7-section markdown. This CLI either:
 *
 *   - reports recon + mode (subcommand `detect`), or
 *   - writes the derived product-context.json sidecar from a PROJECT.md that
 *     already exists on disk (subcommand `derive-sidecar`), or
 *   - writes an empty-section PROJECT.md scaffold from the template
 *     (subcommand `scaffold`) — used on greenfield when the LLM has not
 *     produced prose yet.
 *
 * Subcommands:
 *
 *   apt-tools bootstrap detect <project-dir>
 *     → { mode, signals, rationale, recon }
 *     Combined recon + mode-detect envelope. No files written.
 *
 *   apt-tools bootstrap derive-sidecar <project-dir> [--force]
 *     → reads <project-dir>/PROJECT.md, writes <project-dir>/.aperant/
 *       product-context.json (D-01). Fails if PROJECT.md is missing.
 *
 *   apt-tools bootstrap scaffold <project-dir> [--force]
 *     → copies templates/project-md.md to <project-dir>/PROJECT.md with
 *       placeholder text. Refuses to overwrite an existing PROJECT.md
 *       unless --force is passed.
 *
 *   apt-tools bootstrap run <project-dir> [--force-greenfield | --force-brownfield]
 *                                          [--skip-sidecar] [--force]
 *     → one-shot: detect → (if no PROJECT.md) scaffold → derive-sidecar.
 *       Used by apt-tools init's auto-chain; returns a single envelope with
 *       all three stages.
 */
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { detectMode } from '../bootstrap/detect-mode.mjs';
import { deriveSidecar } from '../bootstrap/product-context-sidecar.mjs';
import { runRecon } from '../bootstrap/recon.mjs';
import { resolveProjectDir } from '../util/common-flags.mjs';
import { atomicWriteJson } from '../util/io.mjs';
import { err, ok } from '../util/result.mjs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/** Path to the framework-shipped PROJECT.md template. */
const TEMPLATE_PATH = resolve(__dirname, '..', '..', '..', 'templates', 'project-md.md');
/**
 * @param {string | undefined} subcommand
 * @param {string[] | undefined} extraArgs
 */
export function cmdBootstrap(subcommand, extraArgs) {
    const args = extraArgs ?? [];
    if (!subcommand) {
        return err('Usage: apt-tools bootstrap <detect|derive-sidecar|scaffold|run> <project-dir> [flags]\n' +
            'Flags: --force-greenfield, --force-brownfield, --skip-sidecar, --force');
    }
    switch (subcommand) {
        case 'detect':
            return cmdDetect(args);
        case 'derive-sidecar':
            return cmdDeriveSidecar(args);
        case 'scaffold':
            return cmdScaffold(args);
        case 'run':
            return cmdRun(args);
        default:
            return err(`apt-tools bootstrap: unknown subcommand "${subcommand}". ` +
                'Known: detect, derive-sidecar, scaffold, run');
    }
}
/**
 * detect — recon + mode detection. Applies --force-greenfield / --force-brownfield
 * overrides to the heuristic output (D-03).
 *
 * @param {string[]} args
 */
function cmdDetect(args) {
    const projectDir = resolveProjectDir(args);
    const flags = new Set(args.filter((a) => a.startsWith('--')));
    const recon = runRecon(projectDir);
    const detected = detectMode(projectDir);
    let finalMode = detected.mode;
    let overridden = null;
    if (flags.has('--force-greenfield') && flags.has('--force-brownfield')) {
        return err('bootstrap detect: --force-greenfield and --force-brownfield are mutually exclusive');
    }
    if (flags.has('--force-greenfield')) {
        finalMode = 'greenfield';
        overridden = 'user-forced greenfield';
    }
    else if (flags.has('--force-brownfield')) {
        finalMode = 'brownfield';
        overridden = 'user-forced brownfield';
    }
    return ok({
        status: 'ok',
        command: 'bootstrap-detect',
        projectDir,
        mode: finalMode,
        detected_mode: detected.mode,
        override: overridden,
        signals: detected.signals,
        rationale: detected.rationale,
        recon,
    });
}
/**
 * derive-sidecar — read <project-dir>/PROJECT.md, transform to product-
 * context.json (D-01).
 *
 * @param {string[]} args
 */
function cmdDeriveSidecar(args) {
    const projectDir = resolveProjectDir(args);
    const flags = new Set(args.filter((a) => a.startsWith('--')));
    const projectMdPath = join(projectDir, 'PROJECT.md');
    const sidecarPath = join(projectDir, '.aperant', 'product-context.json');
    if (!existsSync(projectMdPath)) {
        return err(`bootstrap derive-sidecar: ${projectMdPath} does not exist`);
    }
    if (existsSync(sidecarPath) && !flags.has('--force')) {
        // Still allow the regeneration — we're derived. The --force flag here
        // is reserved for future "write with the freshly-derived content even
        // if it would lose manual edits" semantics. Today the file is always
        // regenerated; the flag is accepted for interface stability.
    }
    const markdown = readFileSync(projectMdPath, 'utf-8');
    const packageJsonPath = join(projectDir, 'package.json');
    let packageName = null;
    let packageDescription = null;
    if (existsSync(packageJsonPath)) {
        try {
            const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
            packageName = typeof pkg?.name === 'string' ? pkg.name : null;
            packageDescription = typeof pkg?.description === 'string' ? pkg.description : null;
        }
        catch {
            /* best-effort */
        }
    }
    const sidecar = deriveSidecar(markdown, { packageName, packageDescription });
    atomicWriteJson(sidecarPath, sidecar);
    return ok({
        status: 'ok',
        command: 'bootstrap-derive-sidecar',
        projectDir,
        projectMd: projectMdPath,
        sidecarPath,
        sidecar,
    });
}
/**
 * scaffold — copy templates/project-md.md into <project-dir>/PROJECT.md with
 * placeholders intact. Refuses to overwrite without --force.
 *
 * @param {string[]} args
 */
function cmdScaffold(args) {
    const projectDir = resolveProjectDir(args);
    const flags = new Set(args.filter((a) => a.startsWith('--')));
    const target = join(projectDir, 'PROJECT.md');
    if (existsSync(target) && !flags.has('--force')) {
        return ok({
            status: 'skipped',
            command: 'bootstrap-scaffold',
            projectDir,
            target,
            reason: 'PROJECT.md already exists — pass --force to overwrite',
        });
    }
    if (!existsSync(TEMPLATE_PATH)) {
        return err(`bootstrap scaffold: template not found at ${TEMPLATE_PATH}`);
    }
    // Capture the pre-write existence state so `overwrote` is accurate.
    // After writeFileSync the file always exists, so a post-write check
    // would return true even on a brand-new file with --force. (LOG-001)
    const alreadyExisted = existsSync(target);
    const body = readFileSync(TEMPLATE_PATH, 'utf-8');
    writeFileSync(target, body, 'utf-8');
    return ok({
        status: 'ok',
        command: 'bootstrap-scaffold',
        projectDir,
        target,
        template: TEMPLATE_PATH,
        overwrote: alreadyExisted && flags.has('--force'),
    });
}
/**
 * run — one-shot bootstrap orchestration used by apt-tools init. Each
 * sub-stage is independently reportable in the output envelope so the
 * SKILL.md body (or a parent agent) can see exactly what happened.
 *
 * Stage contract:
 *   1. detect  — always runs; produces mode + signals + recon.
 *   2. scaffold — only when PROJECT.md is missing (or --force is passed).
 *   3. derive-sidecar — only when PROJECT.md exists (scaffold result OR
 *      already-present), AND --skip-sidecar is NOT set.
 *
 * Neither scaffold nor derive-sidecar fail the overall run on an individual
 * issue — they append warnings to the envelope so the caller can decide
 * whether to treat it as fatal.
 *
 * @param {string[]} args
 */
function cmdRun(args) {
    const projectDir = resolveProjectDir(args);
    const flags = new Set(args.filter((a) => a.startsWith('--')));
    if (flags.has('--force-greenfield') && flags.has('--force-brownfield')) {
        return err('bootstrap run: --force-greenfield and --force-brownfield are mutually exclusive');
    }
    const recon = runRecon(projectDir);
    const detected = detectMode(projectDir);
    let mode = detected.mode;
    let override = null;
    if (flags.has('--force-greenfield')) {
        mode = 'greenfield';
        override = 'user-forced greenfield';
    }
    else if (flags.has('--force-brownfield')) {
        mode = 'brownfield';
        override = 'user-forced brownfield';
    }
    const warnings = [];
    const projectMdPath = join(projectDir, 'PROJECT.md');
    const sidecarPath = join(projectDir, '.aperant', 'product-context.json');
    let scaffold = null;
    if (!existsSync(projectMdPath) || flags.has('--force')) {
        if (!existsSync(TEMPLATE_PATH)) {
            warnings.push(`template not found at ${TEMPLATE_PATH} — scaffold skipped`);
        }
        else {
            const body = readFileSync(TEMPLATE_PATH, 'utf-8');
            writeFileSync(projectMdPath, body, 'utf-8');
            scaffold = { status: 'ok', target: projectMdPath, template: TEMPLATE_PATH };
        }
    }
    else {
        scaffold = { status: 'skipped', target: projectMdPath, reason: 'PROJECT.md already exists' };
    }
    let sidecar = null;
    if (!flags.has('--skip-sidecar')) {
        if (existsSync(projectMdPath)) {
            try {
                const markdown = readFileSync(projectMdPath, 'utf-8');
                let packageName = null;
                let packageDescription = null;
                const packageJsonPath = join(projectDir, 'package.json');
                if (existsSync(packageJsonPath)) {
                    try {
                        const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
                        packageName = typeof pkg?.name === 'string' ? pkg.name : null;
                        packageDescription = typeof pkg?.description === 'string' ? pkg.description : null;
                    }
                    catch {
                        /* best-effort */
                    }
                }
                const derived = deriveSidecar(markdown, { packageName, packageDescription });
                atomicWriteJson(sidecarPath, derived);
                sidecar = { status: 'ok', path: sidecarPath, sidecar: derived };
            }
            catch (err) {
                const msg = err instanceof Error ? err.message : String(err);
                warnings.push(`sidecar derivation failed: ${msg}`);
                sidecar = { status: 'error', error: msg };
            }
        }
        else {
            warnings.push('PROJECT.md does not exist — sidecar derivation skipped');
            sidecar = { status: 'skipped', reason: 'PROJECT.md missing' };
        }
    }
    return ok({
        status: 'ok',
        command: 'bootstrap-run',
        projectDir,
        mode,
        detected_mode: detected.mode,
        override,
        signals: detected.signals,
        rationale: detected.rationale,
        recon,
        scaffold,
        sidecar,
        warnings,
    });
}
//# sourceMappingURL=bootstrap.mjs.map