import { computePoolPlan } from "./reconcile.js";
const DEFAULT_INTERVAL_MS = 30_000;
/**
 * In-process worker that keeps `cfg.size` warm envs AVAILABLE. Runs inside the
 * Switchboard reactor (a subgraph starts it in onSetup), so it creates envs via
 * the reactor client directly — no wallet, no separate service.
 */
export class PoolKeeper {
    d;
    intervalMs;
    timer = null;
    constructor(d, intervalMs = DEFAULT_INTERVAL_MS) {
        this.d = d;
        this.intervalMs = intervalMs;
    }
    start() {
        if (this.timer)
            return;
        const tick = () => {
            this.reconcileOnce().catch((err) => this.d.logger.warn(`[studio-pool] keeper tick failed: ${String(err)}`));
        };
        tick();
        this.timer = setInterval(tick, this.intervalMs);
    }
    stop() {
        if (this.timer)
            clearInterval(this.timer);
        this.timer = null;
    }
    async reconcileOnce() {
        if (!this.d.cfg.enabled)
            return;
        // 1. Promote any warm env the observability reconciler drove to READY.
        await this.d.db.promoteReadyToAvailable();
        // 2. Plan from current rows.
        const rows = await this.d.db.listPoolRows();
        const plan = computePoolPlan(rows, {
            size: this.d.cfg.size,
            version: this.d.cfg.version,
        });
        // 3. Clear zombies: pool rows whose env is dead (terminated/recycled/failed)
        //    so they stop counting and can't be claimed.
        if (plan.toClear.length > 0) {
            try {
                await this.d.db.clearPoolState(plan.toClear);
                this.d.logger.info(`[studio-pool] cleared ${plan.toClear.length} dead pool row(s)`);
            }
            catch (err) {
                this.d.logger.warn(`[studio-pool] clear failed: ${String(err)}`);
            }
        }
        // 4. Recycle stale-version unclaimed envs by DELETING them — full teardown
        //    (read-model + gitops + namespace/cert), not a TERMINATING husk that
        //    keeps a pod + TLS cert alive. Deleting also removes the pool row, so
        //    the next tick sees the deficit and replaces them.
        for (const id of plan.toRecycle) {
            try {
                await this.d.deleteEnv(id);
                this.d.logger.info(`[studio-pool] recycled (deleted) stale env ${id}`);
            }
            catch (err) {
                this.d.logger.warn(`[studio-pool] recycle ${id} failed: ${String(err)}`);
            }
        }
        // 5. Create the deficit; seed each new doc as WARMING. If the doc was
        //    created but seeding fails, DELETE the orphan so it can't leak a
        //    namespace/pod/cert the pool no longer tracks.
        for (let i = 0; i < plan.toCreate; i++) {
            let created;
            try {
                created = await this.d.createEnv();
            }
            catch (err) {
                this.d.logger.warn(`[studio-pool] create failed: ${String(err)}`);
                continue;
            }
            try {
                await this.d.db.seedWarming({
                    id: created.documentId,
                    subdomain: created.subdomain,
                    tenantId: created.tenantId,
                    pinnedVersion: this.d.cfg.version,
                });
                this.d.logger.info(`[studio-pool] warming ${created.documentId} (${created.tenantId})`);
            }
            catch (err) {
                this.d.logger.warn(`[studio-pool] seed failed for ${created.documentId}; deleting orphan: ${String(err)}`);
                await this.d.deleteEnv(created.documentId).catch(() => { });
            }
        }
    }
}
