/**
 * errors.ts — Structured error types for consistent, actionable CLI diagnostics.
 *
 * Purpose: every error path in the CLI and headless orchestrator should be
 * able to emit context that helps users (and future debuggers) understand
 * *what* failed, *where*, and *what to try next* — without depending on
 * heavy error-handling libraries.
 *
 * Consumer: cli.ts, headless.ts, and any extension that surfaces user-facing
 *   failures.  The types are plain data so they serialize cleanly to stderr,
 *   JSON batch output, and trace spans.
 */

// ---------------------------------------------------------------------------
// Core structured error type
// ---------------------------------------------------------------------------

/**
 * A user-facing or machine-readable error record with rich context.
 *
 * Purpose: provide a single, serializable shape that every error path can
 * enrich incrementally (operation, file, guidance, retry hint) so that
 * consumers — stderr printers, JSON exporters, and trace emitters — receive
 * enough context to produce actionable output without guessing.
 *
 * Consumer: cli.ts catch blocks, headless.ts event handlers, trace span
 *   error events, and any extension that surfaces user-facing failures.
 */
export interface StructuredError {
	/** Human-readable description of what went wrong. */
	message: string;

	/** The high-level operation that was in progress when the error occurred,
	 *  e.g. "graph build", "session resume", "model validation". */
	operation?: string;

	/** The file path most relevant to the failure (the file being read,
	 *  written, or expected). */
	file?: string;

	/** The line number inside `file` if known (e.g. from a parser error). */
	line?: number;

	/** Actionable guidance for the user — what to check or try next. */
	guidance?: string;

	/** Whether retrying the same operation (with the same inputs) might
	 *  succeed, e.g. transient network failures. */
	retry?: boolean;

	/** The underlying cause, if this error wraps another.  Kept as `unknown`
	 *  so callers aren't forced to coerce to Error. */
	cause?: unknown;
}

// ---------------------------------------------------------------------------
// Convenience constructors
// ---------------------------------------------------------------------------

/**
 * Create a {@link StructuredError} from a message and optional context.
 *
 * Purpose: reduce boilerplate at catch sites where we want to enrich a raw
 * exception with operation/file context before logging or returning it.
 *
 * Consumer: cli.ts catch blocks, headless.ts event handlers.
 */
export function error(
	message: string,
	ctx?: Omit<StructuredError, "message">,
): StructuredError {
	return { message, ...ctx };
}

// ---------------------------------------------------------------------------
// Formatters
// ---------------------------------------------------------------------------

/**
 * Format a {@link StructuredError} as plain text suitable for stderr.
 *
 * Output shape (fields omitted when undefined):
 *   [sf] Error: <message>
 *          Operation: <operation>
 *          File:      <file>:<line>
 *          Guidance:  <guidance>
 *          Retryable: yes|no
 *
 * Purpose: give users a consistent, scannable error layout in the terminal
 * so they can spot the file, guidance, and retry hint without parsing JSON.
 *
 * Consumer: cli.ts before writing to process.stderr.
 */
export function formatStructuredError(
	err: StructuredError,
	prefix = "[sf]",
): string {
	const parts: string[] = [`${prefix} Error: ${err.message}`];

	if (err.operation) {
		parts.push(`       Operation: ${err.operation}`);
	}
	if (err.file) {
		const line = err.line !== undefined ? `:${err.line}` : "";
		parts.push(`       File:      ${err.file}${line}`);
	}
	if (err.guidance) {
		parts.push(`       Guidance:  ${err.guidance}`);
	}
	if (err.retry !== undefined) {
		parts.push(`       Retryable: ${err.retry ? "yes" : "no"}`);
	}

	return parts.join("\n") + "\n";
}

/**
 * Format a {@link StructuredError} as a JSON object.
 *
 * Purpose: headless --output-format json mode can embed structured errors
 * in the result payload instead of interleaving free-form text on stderr.
 *
 * Consumer: headless.ts when emitting batch JSON results.
 */
export function errorToJson(err: StructuredError): Record<string, unknown> {
	const out: Record<string, unknown> = { message: err.message };
	if (err.operation !== undefined) out.operation = err.operation;
	if (err.file !== undefined) out.file = err.file;
	if (err.line !== undefined) out.line = err.line;
	if (err.guidance !== undefined) out.guidance = err.guidance;
	if (err.retry !== undefined) out.retry = err.retry;
	if (err.cause !== undefined) {
		out.cause =
			err.cause instanceof Error
				? { message: err.cause.message, name: err.cause.name }
				: String(err.cause);
	}
	return out;
}

// ---------------------------------------------------------------------------
// Predicates
// ---------------------------------------------------------------------------

/**
 * Narrow an `unknown` value to a {@link StructuredError}.
 *
 * Purpose: safe type guards at catch boundaries where the thrown value may
 * be a plain Error, a StructuredError, or something else entirely.
 *
 * Consumer: headless.ts before deciding whether to call formatStructuredError
 *   or fall back to String(err).
 */
export function isStructuredError(val: unknown): val is StructuredError {
	return (
		typeof val === "object" &&
		val !== null &&
		"message" in val &&
		typeof (val as Record<string, unknown>).message === "string"
	);
}
