/**
 * olam status [world] — Show status.
 *
 * Without a world argument: shows machine-level status (CLI version,
 * Host CP state, worlds count, update availability). Exit 0 when host
 * CP is running; exit 1 when stopped.
 *
 * With a world ID: shows per-world details (unchanged from pre-D2 behaviour).
 *
 * Lazy imports for host-cp.js / context.js so tests can inject without
 * transitive @olam/adapters resolution.
 */
import * as fs from 'node:fs';
import * as http from 'node:http';
import * as os from 'node:os';
import * as path from 'node:path';
import { printError, printInfo, printHeader, formatAge } from '../output.js';
// CLI_VERSION is set by index.ts reading package.json; use env var directly
// so status.ts doesn't import @olam/core/world/version-pin.ts (transitive dep).
const CLI_VERSION = process.env['OLAM_CLI_VERSION'] ?? '0.0.0';
const HOST_CP_PORT = 19000;
const STATE_ENUM = [
    'running', 'starting', 'stopped', 'crashed', 'unknown',
];
/**
 * Parse and validate a server response payload into WorldRuntimeStatus.
 * Returns null on any shape mismatch so the caller falls back gracefully.
 * Exported for unit testing.
 */
export function parseRuntimeStatus(raw) {
    if (!raw || typeof raw !== 'object')
        return null;
    const obj = raw;
    const state = obj.state;
    if (typeof state !== 'string')
        return null;
    if (!STATE_ENUM.includes(state))
        return null;
    const ready_replicas = typeof obj.ready_replicas === 'number' ? obj.ready_replicas : 0;
    const restarts = typeof obj.restarts === 'number' ? obj.restarts : 0;
    const last_event_type = typeof obj.last_event_type === 'string'
        ? obj.last_event_type
        : null;
    const last_event_age_seconds = typeof obj.last_event_age_seconds === 'number'
        ? obj.last_event_age_seconds
        : null;
    return {
        state: state,
        ready_replicas,
        restarts,
        last_event_type,
        last_event_age_seconds,
    };
}
/**
 * Default fetcher — hits host-cp's /v1/worlds/:id/status via the loopback
 * port with a 2-second timeout. Returns null on any error so the calling
 * `olam status` command renders the registry view without the runtime
 * block when host-cp is down or the engine fails (preserves the legacy
 * behaviour for operators with a stopped host-cp).
 */
export const fetchWorldRuntimeStatus = (worldId, token) => new Promise((resolve) => {
    const opts = {
        host: '127.0.0.1',
        port: HOST_CP_PORT,
        path: `/v1/worlds/${encodeURIComponent(worldId)}/status`,
        method: 'GET',
        headers: { Authorization: `Bearer ${token}` },
        timeout: 2_000,
    };
    const req = http.request(opts, (res) => {
        if (res.statusCode !== 200) {
            res.resume();
            return resolve(null);
        }
        let body = '';
        res.setEncoding('utf-8');
        res.on('data', (c) => { body += c; });
        res.on('end', () => {
            try {
                const parsed = parseRuntimeStatus(JSON.parse(body));
                resolve(parsed);
            }
            catch {
                resolve(null);
            }
        });
        res.on('error', () => resolve(null));
    });
    req.on('error', () => resolve(null));
    req.on('timeout', () => { req.destroy(); resolve(null); });
    req.end();
});
export async function getMachineStatus(_probe, _loadCtx, _readToken) {
    // Lazy-load the Docker-heavy modules only when not injected (prod path).
    const probe = _probe ?? (async () => {
        const { probeHostCp } = await import('./host-cp.js');
        return probeHostCp();
    });
    let readToken;
    if (_readToken) {
        readToken = _readToken;
    }
    else {
        const { readToken: rt } = await import('./host-cp.js');
        readToken = rt;
    }
    const loadCtx = _loadCtx ?? (async () => {
        const { loadContext } = await import('../context.js');
        return loadContext();
    });
    const probeResult = await probe();
    const tokenPresent = readToken() !== null;
    const hostCpRunning = probeResult !== null;
    let worldCount = 0;
    try {
        const { ctx } = await loadCtx();
        if (ctx) {
            worldCount = ctx.worldManager.listWorlds().length;
        }
    }
    catch { /* not configured — zero worlds */ }
    const manifestPath = path.join(os.homedir(), '.olam', 'cache', 'manifest.json');
    let updateAvailable = null;
    let lastUpdateCheck = null;
    if (fs.existsSync(manifestPath)) {
        const mtime = fs.statSync(manifestPath).mtime;
        lastUpdateCheck = mtime.toISOString();
        try {
            const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
            const latest = manifest['version'];
            updateAvailable = latest !== undefined && latest !== CLI_VERSION;
        }
        catch {
            updateAvailable = null;
        }
    }
    return {
        version: CLI_VERSION,
        port: HOST_CP_PORT,
        host_cp: hostCpRunning ? 'running' : 'stopped',
        token_present: tokenPresent,
        worlds: worldCount,
        update_available: updateAvailable,
        last_update_check: lastUpdateCheck,
    };
}
export function registerStatus(program) {
    program
        .command('status')
        .description('Show status (machine status when no world given; world details with a world ID)')
        .argument('[world]', 'World ID — omit to show machine status')
        .option('--json', 'Output as JSON')
        .option('--pretty', 'Render world status as a human-readable table (engine-aware view)')
        .action(async (worldId, opts) => {
        // ── Machine status (no world arg) ────────────────────────────
        if (!worldId) {
            const ms = await getMachineStatus();
            if (opts.json) {
                process.stdout.write(JSON.stringify(ms, null, 2) + '\n');
                process.exitCode = ms.host_cp === 'running' ? 0 : 1;
                return;
            }
            printHeader('Olam Status');
            printInfo('CLI version', `@pleri/olam-cli@${ms.version}`);
            printInfo('Host CP', ms.host_cp === 'running' ? `running on port ${ms.port}` : 'stopped');
            printInfo('Worlds', String(ms.worlds));
            printInfo('Token', ms.token_present ? 'present' : 'absent');
            if (ms.last_update_check) {
                printInfo('Last update check', ms.last_update_check);
                printInfo('Update available', ms.update_available === true ? 'yes' : ms.update_available === false ? 'no' : 'unknown');
            }
            else {
                printInfo('Update check', 'never (run `olam update --check`)');
            }
            process.exitCode = ms.host_cp === 'running' ? 0 : 1;
            return;
        }
        // ── World status (world ID given) — unchanged behaviour ───────
        const { loadContext } = await import('../context.js');
        const { ctx, error } = await loadContext();
        if (!ctx) {
            printError(error?.message ?? 'Olam is not configured. Run `olam init` first.');
            process.exitCode = 1;
            return;
        }
        const world = ctx.worldManager.getWorld(worldId);
        if (!world) {
            printError(`World "${worldId}" not found. Run \`olam list\` to see worlds.`);
            process.exitCode = 1;
            return;
        }
        const cost = ctx.costTracker.getWorldCost(worldId);
        const dashboardUrl = `http://localhost:${19080 + world.portOffset}`;
        // Fetch the engine-aware runtime status. host-cp may be down or the
        // engine may not yet have a record of this world — both produce
        // `null`, and we omit the runtime block from the output instead of
        // failing. Preserves legacy behavior when offline.
        const { readToken } = await import('./host-cp.js');
        const token = readToken();
        const runtime = token ? await fetchWorldRuntimeStatus(worldId, token) : null;
        if (opts.json) {
            // Additive merge: existing world envelope stays as-is, runtime
            // adds the 5 canonical fields under `runtime` for clear scope
            // separation. Keeps existing --json consumers green (registry
            // fields unchanged) while exposing the new engine-aware shape.
            const payload = { ...world, dashboardUrl, cost };
            if (runtime)
                payload.runtime = runtime;
            console.log(JSON.stringify(payload, null, 2));
            return;
        }
        printHeader(`${world.name} (${world.id})`);
        printInfo('Status', world.status);
        printInfo('Repos', world.repos.join(', '));
        printInfo('Branch', world.branch);
        printInfo('Provider', world.computeProvider);
        printInfo('Port Offset', String(world.portOffset));
        printInfo('Workspace', world.workspacePath);
        printInfo('Dashboard', dashboardUrl);
        if (world.appPortUrls && world.appPortUrls.length > 0) {
            world.appPortUrls.forEach((ap, i) => {
                printInfo(i === 0 ? 'Apps' : '', `${ap.repoName}  ${ap.url}`);
            });
        }
        printInfo('Cost', `$${cost.toFixed(4)}`);
        printInfo('Thoughts', String(world.thoughtCount));
        printInfo('Created', world.createdAt);
        printInfo('Uptime', formatAge(world.createdAt));
        printInfo('Updated', world.updatedAt);
        if (runtime) {
            // Engine-aware runtime block. Decision 17 canonical shape; no
            // engine name in stdout (per S2 — operator UX is invariant).
            printInfo('Runtime state', runtime.state);
            printInfo('Ready replicas', String(runtime.ready_replicas));
            printInfo('Restarts', String(runtime.restarts));
            if (runtime.last_event_type) {
                const ageSuffix = runtime.last_event_age_seconds != null
                    ? `  (${runtime.last_event_age_seconds}s ago)`
                    : '';
                printInfo('Last event', `${runtime.last_event_type}${ageSuffix}`);
            }
        }
    });
}
//# sourceMappingURL=status.js.map