/**
 * build-if-stale.ts — Phase B2 of olam-world-bundle-freshness.
 *
 * Compares the max mtime across source files in
 * `packages/intelligence/src/agent-stream/` against the min mtime
 * across compiled files in `packages/intelligence/dist/agent-stream/`.
 * When source is newer (or dist is absent), triggers an incremental
 * `npm run build --workspace=@olam/intelligence` build.
 *
 * Call site: `olam restart` (B2) and `olam create` (B3).
 *
 * Design decisions (from olam-world-bundle-freshness design doc):
 *   - No file-watch daemon — the bind-mount IS the watch.
 *   - Build failure is fatal: do NOT restart/create with a stale bundle.
 *   - Skip when source-mode is false (install-mode CLI has no source tree).
 */
import * as fs from 'node:fs';
import * as path from 'node:path';
import { spawnSync } from 'node:child_process';
import ora from 'ora';
import { isDevMode } from '../install-root.js';
import { resolveBundleSource, BundleSourceNotFoundError } from './bundle-source.js';
/** File extensions that count as TypeScript source. */
const TS_EXTS = new Set(['.ts', '.js', '.mjs', '.json']);
/** Maximum mtime in ms across all files in a directory tree. */
function maxMtimeMs(dir) {
    if (!fs.existsSync(dir))
        return 0;
    let max = 0;
    const scan = (d) => {
        let entries;
        try {
            entries = fs.readdirSync(d, { withFileTypes: true });
        }
        catch {
            return;
        }
        for (const entry of entries) {
            const full = path.join(d, entry.name);
            if (entry.isDirectory()) {
                // Skip __tests__ and node_modules — they don't affect the build output.
                if (entry.name === '__tests__' || entry.name === 'node_modules')
                    continue;
                scan(full);
            }
            else if (entry.isFile() && TS_EXTS.has(path.extname(entry.name))) {
                try {
                    const mt = fs.statSync(full).mtimeMs;
                    if (mt > max)
                        max = mt;
                }
                catch {
                    // ignore
                }
            }
        }
    };
    scan(dir);
    return max;
}
/** Minimum mtime in ms across all .js files in a directory (non-recursive). */
function minDistMtimeMs(dir) {
    if (!fs.existsSync(dir))
        return 0;
    let min = Infinity;
    let found = false;
    let entries;
    try {
        entries = fs.readdirSync(dir, { withFileTypes: true });
    }
    catch {
        return 0;
    }
    for (const entry of entries) {
        if (entry.isFile() && entry.name.endsWith('.js')) {
            try {
                const mt = fs.statSync(path.join(dir, entry.name)).mtimeMs;
                if (mt < min)
                    min = mt;
                found = true;
            }
            catch {
                // ignore
            }
        }
    }
    return found ? min : 0;
}
/**
 * Check whether the agent-stream dist is stale relative to source; if so,
 * trigger `npm run build --workspace=@olam/intelligence` with an ora spinner.
 *
 * @param repoRoot - Absolute path to the repo root (must contain `packages/`).
 * @param env - Process env for isDevMode check. Defaults to `process.env`.
 */
export function buildIfStale(repoRoot, env = process.env) {
    // C3 (olam-world-bundle-freshness): branch on bundle source mode.
    // install-mode → emit the C3 acceptance log line and return immediately.
    // source-mode → run the B2 stale-check + rebuild logic below.
    try {
        const bundleSource = resolveBundleSource(env);
        if (bundleSource.mode === 'install') {
            process.stderr.write('bundle source: install-mode; skipping rebuild\n');
            return { ok: true, built: false, message: 'bundle source: install-mode; skipping rebuild' };
        }
        // source-mode: fall through to the stale-check below.
    }
    catch (err) {
        if (err instanceof BundleSourceNotFoundError) {
            // Neither path exists: gracefully skip (caller will handle the fallback).
            return { ok: true, built: false, message: 'bundle source unresolved; skipping build check' };
        }
        throw err;
    }
    // Legacy guard (backward compat for callers that pass a bare env without
    // OLAM_DEV set). If resolveBundleSource didn't throw and returned source-mode,
    // isDevMode must also be true, but we keep this guard for safety.
    if (!isDevMode(env)) {
        return { ok: true, built: false, message: 'install-mode: skipping build check' };
    }
    const srcDir = path.join(repoRoot, 'packages', 'intelligence', 'src', 'agent-stream');
    const distDir = path.join(repoRoot, 'packages', 'intelligence', 'dist', 'agent-stream');
    const srcMtime = maxMtimeMs(srcDir);
    const distMtime = minDistMtimeMs(distDir);
    if (distMtime > 0 && srcMtime <= distMtime) {
        return {
            ok: true,
            built: false,
            message: 'bundle: current; skipping rebuild',
        };
    }
    const reason = distMtime === 0
        ? 'dist is absent'
        : `source newer by ${Math.round((srcMtime - distMtime) / 1000)}s`;
    const spinner = ora(`Rebuilding agent-stream bundle… (${reason})`).start();
    const result = spawnSync('npm', ['run', 'build', '--workspace=@olam/intelligence'], {
        cwd: repoRoot,
        stdio: ['ignore', 'pipe', 'pipe'],
        encoding: 'utf-8',
    });
    if (result.status !== 0) {
        spinner.fail('Agent-stream bundle build failed');
        const detail = (result.stderr ?? result.stdout ?? '').trim();
        if (detail) {
            process.stderr.write(detail + '\n');
        }
        return {
            ok: false,
            built: false,
            message: `build failed (exit ${result.status ?? 'null'})`,
        };
    }
    spinner.succeed('Agent-stream bundle rebuilt');
    return { ok: true, built: true, message: 'bundle rebuilt successfully' };
}
//# sourceMappingURL=build-if-stale.js.map