import fs from "node:fs";
import path from "node:path";
function compositeKey(goalId, gateId) {
    return `${goalId}::${gateId}`;
}
export class GateStore {
    storeDir;
    storeFile;
    gates = new Map();
    /** Optional callback invoked when gate summary truth changes (for bumping goal generation). */
    onStatusChange;
    constructor(stateDir) {
        this.storeDir = stateDir;
        this.storeFile = path.join(stateDir, "gates.json");
        this.load();
    }
    load() {
        try {
            if (fs.existsSync(this.storeFile)) {
                const data = JSON.parse(fs.readFileSync(this.storeFile, "utf-8"));
                if (Array.isArray(data)) {
                    for (const g of data) {
                        if (g.gateId && g.goalId) {
                            this.gates.set(compositeKey(g.goalId, g.gateId), g);
                        }
                    }
                }
            }
        }
        catch (err) {
            console.error("[gate-store] Failed to load persisted gates:", err);
        }
    }
    save() {
        try {
            if (!fs.existsSync(this.storeDir)) {
                fs.mkdirSync(this.storeDir, { recursive: true });
            }
            const data = Array.from(this.gates.values());
            fs.writeFileSync(this.storeFile, JSON.stringify(data, null, 2), "utf-8");
        }
        catch (err) {
            console.error("[gate-store] Failed to save gates:", err);
        }
    }
    /** Initialize pending gate states for a new goal. */
    initGatesForGoal(goalId, gateIds) {
        const now = Date.now();
        for (const gateId of gateIds) {
            const key = compositeKey(goalId, gateId);
            if (!this.gates.has(key)) {
                this.gates.set(key, {
                    gateId,
                    goalId,
                    status: "pending",
                    signals: [],
                    updatedAt: now,
                });
            }
        }
        this.save();
    }
    getGate(goalId, gateId) {
        return this.gates.get(compositeKey(goalId, gateId));
    }
    getGatesForGoal(goalId) {
        const result = [];
        for (const g of this.gates.values()) {
            if (g.goalId === goalId)
                result.push(g);
        }
        return result;
    }
    /** Append a signal to a gate's history. */
    recordSignal(signal) {
        const key = compositeKey(signal.goalId, signal.gateId);
        const gate = this.gates.get(key);
        if (!gate)
            return;
        gate.signals.push(signal);
        gate.updatedAt = Date.now();
        this.save();
        this.onStatusChange?.(signal.goalId, signal.gateId);
    }
    updateGateStatus(goalId, gateId, status) {
        const key = compositeKey(goalId, gateId);
        const gate = this.gates.get(key);
        if (!gate)
            return;
        gate.status = status;
        gate.updatedAt = Date.now();
        this.save();
        this.onStatusChange?.(goalId, gateId);
    }
    updateGateContent(goalId, gateId, content, version) {
        const key = compositeKey(goalId, gateId);
        const gate = this.gates.get(key);
        if (!gate)
            return;
        gate.currentContent = content;
        gate.currentContentVersion = version;
        gate.updatedAt = Date.now();
        this.save();
    }
    updateGateMetadata(goalId, gateId, metadata) {
        const key = compositeKey(goalId, gateId);
        const gate = this.gates.get(key);
        if (!gate)
            return;
        gate.currentMetadata = metadata;
        gate.updatedAt = Date.now();
        this.save();
    }
    /** Update a signal's verification results by signal ID. */
    updateSignalVerification(signalId, verification) {
        for (const gate of this.gates.values()) {
            const signal = gate.signals.find(s => s.id === signalId);
            if (signal) {
                if (signal.verification.status !== "running")
                    return; // already finalized
                signal.verification = verification;
                gate.updatedAt = Date.now();
                this.save();
                return;
            }
        }
    }
    getDependentGateIds(gateId, workflow, includeRequested) {
        const gateIds = new Set(workflow.gates.map(g => g.id));
        if (!gateIds.has(gateId)) {
            throw new Error(`Unknown gate: ${gateId}`);
        }
        const adjacency = new Map();
        for (const gate of workflow.gates) {
            for (const depId of gate.dependsOn) {
                const list = adjacency.get(depId) ?? [];
                list.push(gate.id);
                adjacency.set(depId, list);
            }
        }
        const result = [];
        const visited = new Set();
        const queue = [gateId];
        visited.add(gateId);
        while (queue.length > 0) {
            const current = queue.shift();
            if (includeRequested || current !== gateId)
                result.push(current);
            for (const depId of adjacency.get(current) ?? []) {
                if (visited.has(depId))
                    continue;
                visited.add(depId);
                queue.push(depId);
            }
        }
        return result;
    }
    /**
     * Reset a selected gate and every transitive dependent to pending.
     * Preserves signal history, current content, content version, and metadata.
     */
    resetGateAndDependents(goalId, gateId, workflow) {
        const affectedGateIds = this.getDependentGateIds(gateId, workflow, true);
        const changedGateIds = [];
        const unchangedGateIds = [];
        const previousStatuses = {};
        const now = Date.now();
        for (const affectedGateId of affectedGateIds) {
            const key = compositeKey(goalId, affectedGateId);
            const gate = this.gates.get(key);
            const previousStatus = gate?.status ?? "pending";
            previousStatuses[affectedGateId] = previousStatus;
            if (gate) {
                gate.verificationCacheInvalidatedAt = now;
                gate.updatedAt = now;
            }
            if (gate && gate.status !== "pending") {
                gate.status = "pending";
                changedGateIds.push(affectedGateId);
            }
            else {
                unchangedGateIds.push(affectedGateId);
            }
        }
        if (affectedGateIds.length > 0) {
            this.save();
        }
        for (const changedGateId of changedGateIds) {
            this.onStatusChange?.(goalId, changedGateId);
        }
        return {
            requestedGateId: gateId,
            affectedGateIds,
            changedGateIds,
            unchangedGateIds,
            previousStatuses,
        };
    }
    /**
     * Reset downstream gates to pending when an upstream gate is re-signaled.
     * Uses the workflow definition to find transitive dependents.
     */
    cascadeReset(goalId, gateId, workflow) {
        const dependents = this.getDependentGateIds(gateId, workflow, false);
        const changedGateIds = [];
        const now = Date.now();
        for (const depId of dependents) {
            const key = compositeKey(goalId, depId);
            const gate = this.gates.get(key);
            if (gate && gate.status !== "pending") {
                gate.status = "pending";
                gate.updatedAt = now;
                changedGateIds.push(depId);
            }
        }
        if (changedGateIds.length > 0)
            this.save();
    }
    /** Remove all gates for a goal (cleanup on goal deletion). */
    removeGoalGates(goalId) {
        const keysToRemove = [];
        for (const [key, gate] of this.gates) {
            if (gate.goalId === goalId)
                keysToRemove.push(key);
        }
        for (const key of keysToRemove) {
            this.gates.delete(key);
        }
        if (keysToRemove.length > 0)
            this.save();
    }
}
//# sourceMappingURL=gate-store.js.map