import { isEligibleForSleep } from "./policy.js";
export function loadKeeperConfig(env = process.env) {
    const int = (name, fallback) => {
        const v = env[name];
        if (!v)
            return fallback;
        const n = Number.parseInt(v, 10);
        return Number.isNaN(n) || n <= 0 ? fallback : n;
    };
    return {
        enabled: (env.HOUSEKEEPING_DETECTOR_ENABLED ?? "false").toLowerCase() === "true",
        // Default-safe: even when enabled, only logs until explicitly turned off.
        dryRun: (env.HOUSEKEEPING_DRY_RUN ?? "true").toLowerCase() !== "false",
        idleThresholdSeconds: int("HOUSEKEEPING_IDLE_THRESHOLD_SECONDS", 24 * 60 * 60),
        scanIntervalMs: int("HOUSEKEEPING_SCAN_INTERVAL_MS", 15 * 60 * 1000),
        baseDomain: env.STUDIO_BASE_DOMAIN ?? "vetra.io",
        allowlist: (env.HOUSEKEEPING_ALLOWLIST ?? "")
            .split(",")
            .map((s) => s.trim())
            .filter(Boolean),
    };
}
/** Apex studio host for a subdomain (CLINT studios are served at the apex). */
export function studioHost(subdomain, baseDomain) {
    return `${subdomain}.${baseDomain}`;
}
export class HousekeepingKeeper {
    deps;
    timer = null;
    running = false;
    constructor(deps) {
        this.deps = deps;
    }
    /** One detection pass. Returns the hosts slept (or that would be, in dry-run). */
    async reconcileOnce() {
        const { listStudios, loki, sleepEnv, config } = this.deps;
        const log = this.deps.logger ?? console;
        const studios = await listStudios();
        const eligible = studios.filter((s) => isEligibleForSleep(s, { allowlist: config.allowlist }));
        log.info(`[housekeeping-keeper] ${studios.length} READY, ${eligible.length} eligible (dryRun=${config.dryRun})`);
        const slept = [];
        for (const s of eligible) {
            if (!s.subdomain)
                continue;
            const host = studioHost(s.subdomain, config.baseDomain);
            const activity = await loki.classifyHostActivity(host, config.idleThresholdSeconds);
            // Sleep ONLY on a positive idle signal (logs exist, none proper). ACTIVE
            // and UNKNOWN (no logs / query failed) are both left running — never sleep
            // something we can't prove is idle.
            if (activity !== "IDLE")
                continue;
            if (config.dryRun) {
                log.info(`[housekeeping-keeper] would sleep ${host} (idle ≥ ${config.idleThresholdSeconds}s)`);
                slept.push(host);
                continue;
            }
            try {
                await sleepEnv(s.envId);
                log.info(`[housekeeping-keeper] slept ${host}`);
                slept.push(host);
            }
            catch (err) {
                log.warn(`[housekeeping-keeper] sleep failed for ${host}: ${err instanceof Error ? err.message : String(err)}`);
            }
        }
        return slept;
    }
    start() {
        if (this.timer)
            return;
        const log = this.deps.logger ?? console;
        const tick = () => {
            if (this.running)
                return;
            this.running = true;
            this.reconcileOnce()
                .catch((err) => log.warn(`[housekeeping-keeper] pass failed: ${err instanceof Error ? err.message : String(err)}`))
                .finally(() => {
                this.running = false;
            });
        };
        tick();
        this.timer = setInterval(tick, this.deps.config.scanIntervalMs);
    }
    stop() {
        if (this.timer)
            clearInterval(this.timer);
        this.timer = null;
    }
}
