#!/usr/bin/env node
import process from 'node:process';
import { EXIT_CODES, HELP_TEXT, CliError, parseArgs, usageError } from "./cli/infrastructure.js";
import { stableStringify, compactStringify } from "./presenters/json.js";
import { COMMANDS } from "./cli/commands/registry.js";
// Output format must be captured from raw process.argv BEFORE parseArgs() runs.
// If parseArgs() itself throws (e.g., unknown flag, malformed input), the `options`
// object will not exist, so the error handler cannot read `options.json`. By
// pre-scanning argv, the error handler can still emit structured output.
const hasJsonFlag = process.argv.includes('--json');
const hasNdjsonFlag = process.argv.includes('--ndjson');
/** Runtime guard: does this value carry a `payload` field? */
function hasPayload(value) {
    return typeof value === 'object' && value !== null && 'payload' in value;
}
/** Runtime guard: does this value carry an async `close` function? */
function hasCloseFn(value) {
    if (typeof value !== 'object' || value === null) {
        return false;
    }
    const rec = value;
    return typeof rec['close'] === 'function';
}
/** Normalizes any handler return shape into { payload, exitCode }. */
function normalizeResult(result) {
    if (hasPayload(result)) {
        return {
            payload: result.payload,
            exitCode: typeof result.exitCode === 'number' ? result.exitCode : EXIT_CODES.OK,
        };
    }
    return { payload: result, exitCode: EXIT_CODES.OK };
}
/** Short-circuit the various early-exit conditions (help, removed
 *  flags, mutual exclusion, empty command). Returns true iff the
 *  caller should stop. */
function handleEarlyExits(parsed) {
    const { options, command } = parsed;
    if (options.help) {
        process.stdout.write(HELP_TEXT);
        process.exitCode = EXIT_CODES.OK;
        return true;
    }
    if (options.view !== null && options.view !== '') {
        throw usageError('--view has been removed. Use warp-ttd for visualization.');
    }
    if (options.json && options.ndjson) {
        throw usageError('--json and --ndjson are mutually exclusive');
    }
    if (command === undefined || command === '') {
        process.stderr.write(HELP_TEXT);
        process.exitCode = EXIT_CODES.USAGE;
        return true;
    }
    return false;
}
/** Registers SIGINT/SIGTERM handlers that shut down a long-running
 *  command gracefully. */
function installShutdownHandlers(close) {
    let closing = false;
    const shutdown = async () => {
        if (closing) {
            return;
        }
        closing = true;
        await close();
        process.exit(EXIT_CODES.OK);
    };
    process.on('SIGINT', () => { shutdown().catch(() => process.exit(1)); });
    process.on('SIGTERM', () => { shutdown().catch(() => process.exit(1)); });
}
/** Writes the payload (when present) in the requested stringify
 *  format. */
function emitPayload(payload, ndjson) {
    if (payload === undefined) {
        return;
    }
    const stringify = ndjson ? compactStringify : stableStringify;
    process.stdout.write(`${stringify(payload)}\n`);
}
/**
 * CLI entry point. Parses arguments, dispatches to the appropriate command handler,
 * and emits the result to stdout (JSON or human-readable).
 */
async function main() {
    const parsed = parseArgs(process.argv.slice(2));
    if (handleEarlyExits(parsed)) {
        return;
    }
    const { options, command, commandArgs } = parsed;
    // handleEarlyExits already returned for empty/undefined commands.
    // Re-narrow here for the compiler since early-exit propagation
    // doesn't survive the function boundary.
    if (command === undefined || command === '') {
        return;
    }
    const handler = COMMANDS.get(command);
    if (!handler) {
        throw usageError(`Unknown command: ${command}`);
    }
    const result = await handler({ args: commandArgs, options });
    const normalized = normalizeResult(result);
    emitPayload(normalized.payload, options.ndjson);
    // Long-running commands may return a `close` function.
    // Wait for SIGINT/SIGTERM instead of exiting immediately.
    if (hasCloseFn(result)) {
        installShutdownHandlers(result.close);
        return; // Keep the process alive
    }
    // Use process.exit() to avoid waiting for fire-and-forget I/O (e.g. seek cache writes).
    process.exit(normalized.exitCode);
}
main().catch((error) => {
    const exitCode = error instanceof CliError ? error.exitCode : EXIT_CODES.INTERNAL;
    const code = error instanceof CliError ? error.code : 'E_INTERNAL';
    const message = error instanceof Error ? error.message : 'Unknown error';
    const payload = { error: { code, message } };
    if (error instanceof Error && error.cause !== undefined) {
        payload.error.cause = error.cause instanceof Error ? error.cause.message : error.cause;
    }
    if (hasJsonFlag || hasNdjsonFlag) {
        const stringify = hasNdjsonFlag ? compactStringify : stableStringify;
        process.stdout.write(`${stringify(payload)}\n`);
    }
    else {
        process.stderr.write(`Error: ${payload.error.message}\n`);
    }
    process.exit(exitCode);
});
