/**
 * commands/personas.mjs — `apt-tools personas <sub>` (C48).
 *
 * Deterministic CLI surface for the apt:personas skill. This command never
 * calls an LLM. The SKILL.md body invokes the host LLM to author personas +
 * classify tiers, then hands the structured payload to this CLI for
 * persistence + run tracking.
 *
 * Subcommand menu:
 *
 *   apt-tools personas scan <project-dir>
 *     → emit the role-scan envelope (read-only). The SKILL.md body injects
 *       this into the LLM prompt alongside the tier rubric.
 *
 *   apt-tools personas rubric <project-dir>
 *     → emit the tier-rubric prompt body. Inputs come from product-
 *       context.json + role-scan; the CLI composes them into the exact text
 *       the SKILL.md body hands to the host LLM.
 *
 *   apt-tools personas read <project-dir>
 *     → emit the current `.aperant/personas.json` envelope (or null if
 *       missing). Used by --from-app flows and by `/apt:docs` to render
 *       personas into README / ABOUT.
 *
 *   apt-tools personas write <project-dir>
 *       --mode <fresh | extend | from-app>
 *       [--payload <path>]
 *       [--force]
 *     → persist personas. `--payload` points to a JSON file containing a
 *       Persona[] array — the SKILL.md body writes it to a tmp path so the
 *       CLI can consume it without argv explosion.
 *
 *   apt-tools personas record-run <project-dir>
 *       --status <pending | running | completed | failed>
 *       [--candidates-found N] [--phase <slug>] [--error <msg>]
 *     → append an entry to `.aperant/personas-runs.json` (D-09).
 *
 * Return shape: always `{ status, command, ... }`. On refused writes (D-10
 * guard hit without --force), exits 0 with `status: 'refused'` so the
 * SKILL.md body can pick up the message and ask the user — we do NOT exit 1
 * here, that would break auto-mode branches.
 */
import { existsSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { runRoleScan } from '../personas/role-scan.mjs';
import { FRAMEWORK_LOCAL_TEAM_ID, PERSONAS_ENVELOPE_VERSION, personasPaths, readFromApp, readPersonas, readRuns, recordRun, resolveProjectId, writeExtend, writeFresh, } from '../personas/sidecar.mjs';
import { buildTierPrompt, buildTierRubricPrompt } from '../personas/tier-rubric.mjs';
import { getFlagValue, resolveProjectDir } from '../util/common-flags.mjs';
import { err, ok } from '../util/result.mjs';
/**
 * @param {string | undefined} subcommand
 * @param {string[] | undefined} extraArgs
 */
export function cmdPersonas(subcommand, extraArgs) {
    const args = extraArgs ?? [];
    if (!subcommand) {
        return err('Usage: apt-tools personas <scan|rubric|read|write|record-run> <project-dir> [flags]\n' +
            '  scan            emit role-scan findings\n' +
            '  rubric          emit tier-assignment prompt body (rubric + inputs)\n' +
            '  read            emit current .aperant/personas.json envelope\n' +
            '  write           persist personas (--mode fresh|extend|from-app, --payload <path>, --force)\n' +
            '  record-run      append entry to .aperant/personas-runs.json');
    }
    switch (subcommand) {
        case 'scan':
            return cmdScan(args);
        case 'rubric':
            return cmdRubric(args);
        case 'read':
            return cmdRead(args);
        case 'write':
            return cmdWrite(args);
        case 'record-run':
            return cmdRecordRun(args);
        default:
            return err(`apt-tools personas: unknown subcommand "${subcommand}". ` +
                'Known: scan, rubric, read, write, record-run');
    }
}
/**
 * scan — role-scan findings. Read-only, no files written.
 *
 * @param {string[]} args
 */
function cmdScan(args) {
    const projectDir = resolveProjectDir(args);
    const result = runRoleScan(projectDir);
    return ok({
        status: 'ok',
        command: 'personas-scan',
        projectDir,
        team_id: FRAMEWORK_LOCAL_TEAM_ID,
        project_id: resolveProjectId(projectDir),
        result,
    });
}
/**
 * rubric — emit the tier-rubric prompt body. Inputs come from the project's
 * product-context.json (C44); the role-scan is combined in-line so the
 * SKILL.md body can hand a single text block to the host LLM.
 *
 * @param {string[]} args
 */
function cmdRubric(args) {
    const projectDir = resolveProjectDir(args);
    const productContextPath = resolve(projectDir, '.aperant', 'product-context.json');
    /** @type {any} */
    let productContext = null;
    if (existsSync(productContextPath)) {
        try {
            productContext = JSON.parse(readFileSync(productContextPath, 'utf-8'));
        }
        catch {
            productContext = null;
        }
    }
    const scan = runRoleScan(projectDir);
    const inputs = {
        productName: productContext?.productName ?? 'Unnamed Project',
        productDescription: productContext?.productDescription ?? '',
        targetAudience: productContext?.targetAudience ?? '',
        coreValue: productContext?.productDescription ?? '',
        uniqueRoleStrings: scan.unique_role_strings,
        roleFindings: scan.findings,
        features: Array.isArray(productContext?.features) ? productContext.features : [],
    };
    const prompt = buildTierPrompt(inputs);
    const rubricOnly = buildTierRubricPrompt();
    return ok({
        status: 'ok',
        command: 'personas-rubric',
        projectDir,
        product_context_present: Boolean(productContext),
        inputs,
        rubric: rubricOnly,
        prompt,
    });
}
/**
 * read — emit the current personas envelope.
 *
 * @param {string[]} args
 */
function cmdRead(args) {
    const projectDir = resolveProjectDir(args);
    const envelope = readPersonas(projectDir);
    const runs = readRuns(projectDir);
    return ok({
        status: 'ok',
        command: 'personas-read',
        projectDir,
        paths: personasPaths(projectDir),
        envelope,
        runs_count: runs.runs.length,
    });
}
/**
 * write — persist personas. The payload is passed via a file path because
 * persona ui_json blobs can be multi-KB, and argv limits are unfriendly.
 *
 * @param {string[]} args
 */
function cmdWrite(args) {
    const projectDir = resolveProjectDir(args);
    const flags = new Set(args.filter((a) => a.startsWith('--') && !hasValueAfter(args, a)));
    const mode = getFlagValue(args, '--mode') ?? 'extend';
    const payloadPath = getFlagValue(args, '--payload');
    const force = flags.has('--force');
    if (mode !== 'fresh' && mode !== 'extend' && mode !== 'from-app') {
        return err(`personas write: unknown --mode "${mode}" (known: fresh, extend, from-app)`);
    }
    if (mode === 'from-app') {
        const result = readFromApp(projectDir);
        return ok({
            status: result.status === 'ok' ? 'ok' : 'not-found',
            command: 'personas-write',
            mode,
            projectDir,
            message: result.message,
            envelope: result.envelope,
        });
    }
    if (!payloadPath) {
        return err(`personas write --mode ${mode} requires --payload <path-to-json>`);
    }
    const abs = resolve(payloadPath);
    if (!existsSync(abs)) {
        return err(`personas write: payload not found at ${abs}`);
    }
    let personas;
    try {
        const raw = readFileSync(abs, 'utf-8');
        personas = JSON.parse(raw);
    }
    catch (e) {
        return err(`personas write: unable to parse payload at ${abs}: ${errMsg(e)}`);
    }
    if (!Array.isArray(personas)) {
        return err('personas write: payload must be a JSON array of Persona objects');
    }
    if (mode === 'fresh') {
        const result = writeFresh(projectDir, personas, { force });
        return ok({
            status: result.status,
            command: 'personas-write',
            mode,
            projectDir,
            schema_version: PERSONAS_ENVELOPE_VERSION,
            guard_reason: result.guardReason,
            envelope: result.envelope,
            written_path: personasPaths(projectDir).personas,
        });
    }
    // mode === 'extend'
    const result = writeExtend(projectDir, personas);
    return ok({
        status: 'ok',
        command: 'personas-write',
        mode,
        projectDir,
        schema_version: PERSONAS_ENVELOPE_VERSION,
        added_count: result.added.length,
        skipped_count: result.skipped.length,
        envelope: result.envelope,
        written_path: personasPaths(projectDir).personas,
    });
}
/**
 * record-run — append an entry to `.aperant/personas-runs.json` (D-09).
 *
 * @param {string[]} args
 */
function cmdRecordRun(args) {
    const projectDir = resolveProjectDir(args);
    const status = getFlagValue(args, '--status');
    if (!status || !['pending', 'running', 'completed', 'failed'].includes(status)) {
        return err('personas record-run: --status is required (one of pending|running|completed|failed)');
    }
    const candidatesFoundRaw = getFlagValue(args, '--candidates-found');
    const candidatesFound = candidatesFoundRaw ? Number.parseInt(candidatesFoundRaw, 10) : null;
    const phase = getFlagValue(args, '--phase');
    const errorMsg = getFlagValue(args, '--error');
    const id = getFlagValue(args, '--id');
    const startedAtRaw = getFlagValue(args, '--started-at');
    const completedAtRaw = getFlagValue(args, '--completed-at');
    /** @type {Record<string, unknown>} */
    const payload = {
        status: /** @type {'pending'|'running'|'completed'|'failed'} */ (status),
    };
    if (id)
        payload.id = id;
    if (phase)
        payload.current_phase = phase;
    if (errorMsg)
        payload.error_message = errorMsg;
    if (startedAtRaw) {
        const n = Number.parseInt(startedAtRaw, 10);
        if (Number.isFinite(n))
            payload.started_at = n;
    }
    if (completedAtRaw) {
        const n = Number.parseInt(completedAtRaw, 10);
        if (Number.isFinite(n))
            payload.completed_at = n;
    }
    if (candidatesFound !== null && Number.isFinite(candidatesFound)) {
        payload.candidates_found = candidatesFound;
    }
    const run = recordRun(projectDir, /** @type {any} */ (payload));
    return ok({
        status: 'ok',
        command: 'personas-record-run',
        projectDir,
        run,
        path: personasPaths(projectDir).runs,
    });
}
/**
 * Detect whether a flag is being used as a key/value pair so we can skip it
 * in the flag-set membership check.
 *
 * @param {string[]} args
 * @param {string} flag
 * @returns {boolean}
 */
function hasValueAfter(args, flag) {
    const idx = args.indexOf(flag);
    if (idx < 0)
        return false;
    const next = args[idx + 1];
    if (!next)
        return false;
    return !next.startsWith('--');
}
/**
 * @param {unknown} err
 * @returns {string}
 */
function errMsg(err) {
    if (err && typeof err === 'object' && 'message' in err) {
        const m = /** @type {{ message: unknown }} */ (err).message;
        return typeof m === 'string' ? m : String(m);
    }
    return String(err);
}
//# sourceMappingURL=personas.mjs.map