/**
 * result.mjs — D-01 (d) command-result helpers.
 *
 * Every Layer 1 cmdX(opts) function returns one of two `CommandResult` shapes:
 *
 *     { envelope: { status, command?, ... }, exitCode: number }   ← JSON cmds
 *     { rawText: string, exitCode: number }                       ← non-JSON cmds
 *
 * The Layer 2 wrapper (`wrap()` in dispatch.mjs) routes both:
 *   - JSON shape   → output(envelope) + (exitCode !== 0 ? process.exit(exitCode))
 *   - rawText shape → process.stdout.write(rawText) + (exitCode !== 0 ? process.exit(exitCode))
 *
 * The transport metadata (exitCode) lives OUTSIDE the envelope — the envelope
 * shape is exactly the pre-refactor JSON contract callers parse from stdout.
 * There is intentionally NO `_exitCode` (or other transport field) on the
 * envelope; D-01 (d) was adopted specifically to keep the public JSON contract
 * clean.
 *
 * Helpers:
 *
 *   ok(envelope)               — JSON path, exit 0, envelope unchanged.
 *   err(msg, exitCode = 1, x)  — JSON path, exit `exitCode`, synthesizes a
 *                                 { status: 'error', error: msg, ...x } envelope.
 *   exitWith(envelope, code)   — JSON path, exit `code`, envelope unchanged.
 *                                 Used for the documented exit-2 semantics
 *                                 (pr-review busy/mismatch/exhausted, audit
 *                                 pending-artifacts --fail-on-dirty,
 *                                 state-artifacts secrets-found).
 *   rawText(text, code = 0)    — Non-JSON path, exit `code`, raw text on stdout.
 *                                 Used by template, effects list, consistency
 *                                 check --format human, precompact-hook.
 *
 * The CommandResult typedef is JSDoc-only (no TypeScript here — Layer 1 lives
 * in .mjs). Tests assert on `result.envelope`/`result.rawText` and
 * `result.exitCode` separately.
 */

/**
 * Sentinel base class for helper-level usage failures inside Layer 1 cmds.
 * Subclass per-cmd (`class FooUsageError extends UsageError { ... }`) so
 * each cmd's catch boundary can remain narrowly scoped via `instanceof`.
 * The dispatcher's catch block converts any UsageError into `err(e.message)`.
 */
export class UsageError extends Error {
	constructor(message) {
		super(message)
		this.name = 'UsageError'
	}
}

/**
 * @typedef {Object} CommandResult
 * @property {{ status: string, command?: string, [k: string]: unknown }} envelope
 * @property {number} exitCode
 *
 * @typedef {Object} RawCommandResult
 * @property {string} rawText
 * @property {number} exitCode
 */

/**
 * Success path — pass envelope through verbatim, exit 0.
 *
 * @param {{ status: string, command?: string, [k: string]: unknown }} envelope
 * @returns {CommandResult}
 */
export function ok(envelope) {
	return { envelope, exitCode: 0 }
}

/**
 * Generic error path — synthesize a standard error envelope at the requested
 * exit code. `extra` merges in additional envelope fields (e.g. command name).
 *
 * @param {string} msg
 * @param {number} [exitCode=1]
 * @param {Record<string, unknown>} [extra]
 * @returns {CommandResult}
 */
export function err(msg, exitCode = 1, extra = {}) {
	return {
		envelope: { status: 'error', error: msg, ...extra },
		exitCode,
	}
}

/**
 * Pre-built envelope + explicit exit code. Used when the command has its own
 * domain-shaped envelope (e.g. pr-review's busy/mismatch envelopes) and only
 * needs to set the exit code.
 *
 * @param {{ status: string, command?: string, [k: string]: unknown }} envelope
 * @param {number} exitCode
 * @returns {CommandResult}
 */
export function exitWith(envelope, exitCode) {
	return { envelope, exitCode }
}

/**
 * Raw-text output path — for cmds that emit non-JSON content (template dump,
 * effects list, consistency check --format human, precompact-hook). The wrap()
 * helper routes this through `process.stdout.write(text)` instead of
 * `output(envelope)`, then sets the exit code.
 *
 * @param {string} text
 * @param {number} [exitCode=0]
 * @returns {RawCommandResult}
 */
export function rawText(text, exitCode = 0) {
	return { rawText: text, exitCode }
}
