/**
 * olam diagnose — G1/G2/G3.
 *
 * G1: builds ~/.olam/diagnostics/<ts>.zip with secrets stripped.
 * G2: --show-telemetry prints the future telemetry payload; --no-telemetry suppresses it.
 * G3: --upload prints stub message (CF Worker not yet provisioned).
 *
 * Relationship to `olam doctor` (Phase A3 of olam-operator-onboarding-parity):
 *
 * `olam doctor` is the pass/fail host-health gate (single source of truth at
 * `packages/cli/src/lib/health-probes.ts` — the Seam). `olam diagnose` is the
 * support-bundle builder; it does NOT today consume the probes because the
 * existing bundle contents (version / os-info / log-tail / status.json /
 * audit-auth-callers) have no probe overlap with the 5 probes shipped in
 * Phase A1 (probeDockerDaemon / probeImagePresence / probeHostCpHealth /
 * probeAuthVault / probeKgStorage).
 *
 * Future additions: when a probe-shaped check is added to the bundle
 * (e.g. capturing the doctor verdict alongside log-tail for support
 * triage), import from `../lib/health-probes.js` rather than re-implementing
 * the underlying docker/fetch/fs probes inline. The cap thresholds for
 * KG storage live at `../lib/kg-caps.ts` (post-A1).
 */
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { execFileSync, execSync } from 'node:child_process';
import pc from 'picocolors';
import { stripSecrets } from '@olam/core/src/diagnose/secret-stripper.js';
const DIAGNOSTICS_DIR = path.join(os.homedir(), '.olam', 'diagnostics');
const LOG_DIR = path.join(os.homedir(), '.olam', 'log');
const CACHE_DIR = path.join(os.homedir(), '.olam', 'cache');
const LOG_TAIL_LINES = 200;
function safeExec(cmd) {
    try {
        return execSync(cmd, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
    }
    catch {
        return '';
    }
}
function defaultZip(zipPath, files) {
    // execFileSync avoids shell word-splitting on paths containing spaces
    execFileSync('zip', ['-j', zipPath, ...files]);
}
export async function buildDiagnosticsZip(_exec = (cmd) => safeExec(cmd), _outDir = DIAGNOSTICS_DIR, _logDir = LOG_DIR, _zip = defaultZip) {
    const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 23);
    const zipPath = path.join(_outDir, `olam-diag-${ts}.zip`);
    const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'olam-diag-'));
    try {
        fs.mkdirSync(_outDir, { recursive: true });
        const entries = [];
        // version.txt
        const version = process.env['OLAM_CLI_VERSION'] ?? 'unknown';
        const nodeVersion = process.version;
        const platform = `${process.platform}/${process.arch}`;
        const versionContent = `olam: ${version}\nnode: ${nodeVersion}\nplatform: ${platform}\n`;
        _writeEntry(tmpDir, 'version.txt', stripSecrets(versionContent), entries);
        // os-info.txt — homedir omitted (contains OS username, PII)
        const osContent = [
            `os: ${os.type()} ${os.release()}`,
            `arch: ${os.arch()}`,
            `uptime: ${os.uptime()}s`,
        ].join('\n') + '\n';
        _writeEntry(tmpDir, 'os-info.txt', stripSecrets(osContent), entries);
        // deps.json (from B5 cache)
        const depsFile = path.join(CACHE_DIR, 'deps.json');
        if (fs.existsSync(depsFile)) {
            const deps = fs.readFileSync(depsFile, 'utf-8');
            _writeEntry(tmpDir, 'deps.json', stripSecrets(deps), entries);
        }
        // log-tail.txt — last LOG_TAIL_LINES lines of latest host log
        const latestLog = _latestLog(_logDir);
        if (latestLog) {
            const lines = fs.readFileSync(latestLog, 'utf-8').split('\n');
            const tail = lines.slice(-LOG_TAIL_LINES).join('\n');
            _writeEntry(tmpDir, 'log-tail.txt', stripSecrets(tail), entries);
        }
        // status.json
        const statusOut = _exec('olam status --json');
        if (statusOut) {
            _writeEntry(tmpDir, 'status.json', stripSecrets(statusOut), entries);
        }
        // audit-auth-callers.txt
        const authAudit = _exec('npm run audit:auth-callers --if-present 2>&1');
        if (authAudit) {
            _writeEntry(tmpDir, 'audit-auth-callers.txt', stripSecrets(authAudit), entries);
        }
        // Bundle into zip
        const fileArgs = entries.map((e) => path.join(tmpDir, e));
        try {
            _zip(zipPath, fileArgs);
        }
        catch (err) {
            throw new Error(`zip command produced no output file. zip stderr: ${err.message}`);
        }
        if (!fs.existsSync(zipPath)) {
            throw new Error('zip command produced no output file.');
        }
        return { zipPath, entries };
    }
    finally {
        try {
            fs.rmSync(tmpDir, { recursive: true, force: true });
        }
        catch { /* best-effort */ }
    }
}
function _writeEntry(dir, name, content, entries) {
    fs.writeFileSync(path.join(dir, name), content, { mode: 0o644 });
    entries.push(name);
}
function _latestLog(logDir) {
    if (!fs.existsSync(logDir))
        return null;
    const files = fs.readdirSync(logDir)
        .filter((f) => f.startsWith('host-'))
        .sort()
        .reverse();
    return files.length > 0 ? path.join(logDir, files[0]) : null;
}
export async function buildTelemetryPayload() {
    // Phase F's machine-config (`@olam/core/src/config/machine-schema.js`) was
    // not cherry-picked alongside this Phase G code, so the `channel` field is
    // hard-coded to "stable". When Phase F lands, restore the dynamic read.
    const channel = 'stable';
    const manifestFile = path.join(CACHE_DIR, 'manifest.json');
    let manifestAgeHours = null;
    if (fs.existsSync(manifestFile)) {
        const mtime = fs.statSync(manifestFile).mtime.getTime();
        manifestAgeHours = Math.round((Date.now() - mtime) / 3_600_000);
    }
    return {
        version: process.env['OLAM_CLI_VERSION'] ?? 'unknown',
        os: process.platform,
        arch: process.arch,
        channel,
        manifest_age_hours: manifestAgeHours,
        // No userid, no project, no path — privacy per design doc
    };
}
export function registerDiagnose(program) {
    program
        .command('diagnose')
        .description('Bundle diagnostics into a zip file for sharing with maintainers')
        .option('--show-telemetry', 'Print the telemetry payload that would be sent (no endpoint yet)')
        .option('--no-telemetry', 'Suppress telemetry payload (future opt-out)')
        .option('--upload', 'Share zip with maintainers (endpoint not yet provisioned)')
        .option('--quiet', 'Suppress progress output')
        .action(async (opts) => {
        if (opts.showTelemetry) {
            const payload = await buildTelemetryPayload();
            console.log(JSON.stringify(payload, null, 2));
            return;
        }
        if (!opts.quiet) {
            console.log(pc.cyan('Building diagnostics zip…'));
        }
        try {
            const { zipPath, entries } = await buildDiagnosticsZip();
            if (!opts.quiet) {
                console.log(pc.green(`Done: ${zipPath}`));
                console.log(pc.dim(`Contents: ${entries.join(', ')}`));
            }
            if (opts.upload) {
                console.log(pc.yellow('Telemetry endpoint not yet provisioned. Share the zip manually:\n' +
                    `  File: ${zipPath}\n` +
                    '  See docs/runbooks/share-diagnostics.md for instructions.'));
            }
        }
        catch (err) {
            console.error(pc.red(`Diagnose failed: ${err.message}`));
            process.exitCode = 1;
        }
    });
}
//# sourceMappingURL=diagnose.js.map