/**
 * setting-class.mjs — single source of truth for which `.aperant/config.json`
 * keys are Class A (must NOT silently default in non-TTY) vs Class B
 * (per-task overridable, safe to default).
 *
 * Class A → "affects tracked files, team workflow, or shared artifacts".
 * Missing-in-non-TTY blocks `apt-tools init` with a `requires_input`
 * envelope; the user (or their agent) must re-run with the named flag or
 * `--yes`.
 *
 * Class B → "local-only, reversible, per-task overridable". Missing values
 * are filled from `default` and recorded under `applied_defaults` for
 * transparency, but never block.
 *
 * Each descriptor carries a `classify(value)` helper used by
 * `schema-migrate.mjs` to detect both the absent-in-existing-config case
 * and the present-but-no-longer-valid case (e.g. a Class A enum gained or
 * lost an option between schema versions).
 *
 * R1 in spec.md drives the contents below — visibility/runtimes/scanNow as
 * Class A; autonomyDefault/crossVerificationTools as Class B.
 *
 * Exported `parseClassFlags(args)` is intentionally separate from
 * `parseInstallFlags` (in commands/init.mjs) so the existing parser stays
 * backward-compatible and this module can be unit-tested in isolation.
 */
import { RUNTIMES } from './runtime-detect.mjs';
/** @typedef {'present'|'absent'|'invalid'} ClassifyResult */
/**
 * @typedef {Object} ClassDescriptor
 * @property {string} key            Dot-path into `.aperant/config.json` (e.g. `share.visibility`).
 * @property {string} shortKey       Short form used in the `requires_explicit` envelope (e.g. `visibility`).
 * @property {string|null} flag      CLI flag pair (e.g. `--team`/`--solo`) or null if no flag covers it.
 * @property {string|number|boolean|string[]} default  Default value applied when no flag was given.
 * @property {Array<string|number|boolean>|null} options  Allowed values (for enums); null = freeform / array shape.
 * @property {string} why            One-line user-facing rationale rendered into the envelope.
 * @property {(value: unknown) => ClassifyResult} classify  `'present'` when value is set + valid, `'absent'` when undefined/null, `'invalid'` otherwise.
 */
/**
 * Class A — settings whose value impacts tracked files or team workflow.
 * Must NOT default silently in non-TTY mode. Missing → block install.
 */
export const CLASS_A_SETTINGS = Object.freeze([
    /** @type {ClassDescriptor} */ ({
        key: 'share.visibility',
        shortKey: 'visibility',
        flag: '--team | --solo',
        default: 'solo',
        options: ['solo', 'team'],
        why: 'Controls whether .aperant/ is gitignored at the project root or governed by the internal allow-list.',
        classify(value) {
            if (value === undefined || value === null || value === '')
                return 'absent';
            return ['solo', 'team'].includes(/** @type {string} */ (value)) ? 'present' : 'invalid';
        },
    }),
    /** @type {ClassDescriptor} */ ({
        key: 'runtimes',
        shortKey: 'runtimes',
        flag: '--<runtime> | --all',
        default: ['claude'],
        options: null, // freeform array of runtime ids
        why: 'Each selected runtime creates a tracked install root (.claude, .codex, .cursor, …).',
        classify(value) {
            if (value === undefined || value === null)
                return 'absent';
            if (!Array.isArray(value) || value.length === 0)
                return 'absent';
            const ids = new Set(RUNTIMES.map((r) => r.id));
            return value.every((v) => typeof v === 'string' && ids.has(v)) ? 'present' : 'invalid';
        },
    }),
    /** @type {ClassDescriptor} */ ({
        key: 'scanNow',
        shortKey: 'scanNow',
        flag: null, // no CLI flag — driven by the runtimes selection in R2
        default: false,
        options: [true, false],
        why: 'Triggers /apt:scan after install, which writes shared registry artifacts.',
        classify(value) {
            if (value === undefined || value === null)
                return 'absent';
            return typeof value === 'boolean' ? 'present' : 'invalid';
        },
    }),
]);
/**
 * Class B — settings safe to default. Always have a `default` applied; the
 * choice is reported under `applied_defaults` so downstream tooling can
 * detect unattended installs.
 */
export const CLASS_B_SETTINGS = Object.freeze([
    /** @type {ClassDescriptor} */ ({
        key: 'autonomy.default',
        shortKey: 'autonomyDefault',
        flag: '--autonomy <0..3>',
        default: 1,
        options: [0, 1, 2, 3],
        why: 'Per-task overridable, no tracked-file impact.',
        classify(value) {
            if (value === undefined || value === null)
                return 'absent';
            return [0, 1, 2, 3].includes(/** @type {number} */ (value)) ? 'present' : 'invalid';
        },
    }),
    /** @type {ClassDescriptor} */ ({
        key: 'multi_model.cross_verification.tools',
        shortKey: 'crossVerificationTools',
        flag: null,
        default: [],
        options: null,
        why: 'Local cross-verification roster; reversible via /apt:setup.',
        classify(value) {
            if (value === undefined || value === null)
                return 'absent';
            return Array.isArray(value) ? 'present' : 'invalid';
        },
    }),
]);
/**
 * Parse the Class A/B flag surface from `args`. Sits ALONGSIDE
 * `parseInstallFlags` — the legacy parser already extracts runtimes +
 * `--target` etc.; this one only adds the new flags introduced by R2.
 *
 * Mutual exclusion (`--team` + `--solo`) and out-of-range autonomy throw
 * `Error` — caller (init.mjs) catches and surfaces the message via the
 * `requires_input` envelope shape.
 *
 * @param {string[]} args
 * @returns {{ visibility?: 'solo'|'team', autonomy?: 0|1|2|3, accept_new_defaults: boolean, yes: boolean }}
 */
export function parseClassFlags(args) {
    const flagSet = new Set(args.filter((a) => a.startsWith('--')));
    const wantsTeam = flagSet.has('--team');
    const wantsSolo = flagSet.has('--solo');
    if (wantsTeam && wantsSolo) {
        throw new Error('Flags --team and --solo are mutually exclusive — pick one.');
    }
    const visibility = wantsTeam ? 'team' : wantsSolo ? 'solo' : undefined;
    let autonomy;
    for (let i = 0; i < args.length; i++) {
        if (args[i] === '--autonomy' && args[i + 1] !== undefined) {
            const raw = args[i + 1];
            if (raw.startsWith('--'))
                continue; // next token is a flag, not a value — skip
            const n = Number.parseInt(raw, 10);
            if (!Number.isInteger(n) || n < 0 || n > 3) {
                throw new Error(`--autonomy must be an integer 0–3; got "${raw}".`);
            }
            autonomy = /** @type {0|1|2|3} */ (n);
        }
    }
    return {
        visibility,
        autonomy,
        accept_new_defaults: flagSet.has('--accept-new-defaults'),
        yes: flagSet.has('--yes'),
    };
}
/**
 * Look up a Class descriptor by `shortKey` or full `key`. Returns null when
 * the key is not classified (template-internal addition).
 *
 * @param {string} keyOrShort
 * @returns {ClassDescriptor|null}
 */
const ALL_SETTINGS = Object.freeze([...CLASS_A_SETTINGS, ...CLASS_B_SETTINGS]);
export function findClassDescriptor(keyOrShort) {
    for (const d of ALL_SETTINGS) {
        if (d.key === keyOrShort || d.shortKey === keyOrShort)
            return d;
    }
    return null;
}
//# sourceMappingURL=setting-class.mjs.map