"use strict";
/**
 * GoalTicker — periodic bridge that turns the active /goal into queued
 * missions, gated by governance.
 *
 * Phase 2 (Goal→Mission Bridge) of the autonomous-agent milestone.
 *
 * On each tick: if the active goal changed since it was last compiled
 * (DreamGoalStore.getActiveGoal().needsCompile), compile it into mission
 * drafts, run each draft through the governance gate, enqueue the allowed
 * ones, and mark the goal compiled (so re-ticking without a goal change is a
 * no-op — idempotent).
 *
 * Governance gate (3-way):
 *   - allowed                       → enqueue
 *   - !allowed && requiresConsent   → HOLD: emit a consent event, do NOT
 *                                     enqueue. Interactive approval (Telegram)
 *                                     is a later refinement; for now the held
 *                                     draft is surfaced, not silently dropped.
 *   - !allowed && !requiresConsent  → SKIP: hard block (e.g. prompt
 *                                     injection); recorded, never enqueued.
 *
 * Decoupled from the scheduler via the injected `enqueue` callback so it is
 * unit-testable without the orchestration stack.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.GoalTicker = void 0;
const crypto_1 = require("crypto");
const DreamGoalStore_1 = require("./DreamGoalStore");
const GoalCompiler_1 = require("./GoalCompiler");
const GovernanceLayer_1 = require("./GovernanceLayer");
class GoalTicker {
    store;
    compiler;
    governance;
    enqueueFn;
    onConsentRequired;
    onBlocked;
    intervalMs;
    timer = null;
    ticking = false;
    constructor(opts) {
        this.store = opts.store || new DreamGoalStore_1.DreamGoalStore();
        this.compiler = opts.compiler || new GoalCompiler_1.GoalCompiler();
        this.governance = opts.governance || new GovernanceLayer_1.GovernanceLayer();
        this.enqueueFn = opts.enqueue;
        this.onConsentRequired = opts.onConsentRequired;
        this.onBlocked = opts.onBlocked;
        this.intervalMs = opts.intervalMs ?? 300_000;
    }
    /** Start the periodic tick. Performs an immediate tick. Idempotent. */
    start() {
        if (this.timer)
            return;
        void this.tick();
        this.timer = setInterval(() => { void this.tick(); }, this.intervalMs);
        if (typeof this.timer.unref === 'function')
            this.timer.unref();
    }
    stop() {
        if (this.timer) {
            clearInterval(this.timer);
            this.timer = null;
        }
    }
    /**
     * One compile→gate→enqueue pass. Safe to call manually (tests). Re-entrancy
     * guarded so overlapping ticks don't double-compile.
     */
    async tick() {
        if (this.ticking)
            return { compiled: 0, enqueued: 0, held: 0, blocked: 0, source: 'noop' };
        this.ticking = true;
        try {
            const active = this.store.getActiveGoal();
            if (!active.needsCompile || !active.goal) {
                return { compiled: 0, enqueued: 0, held: 0, blocked: 0, source: 'noop' };
            }
            const result = await this.compiler.compile(active.goal, active.dream);
            if (!result.ok) {
                return { compiled: 0, enqueued: 0, held: 0, blocked: 0, source: 'error' };
            }
            let enqueued = 0;
            let held = 0;
            let blocked = 0;
            for (const draft of result.drafts) {
                const decision = this.governance.evaluate(draft.goal);
                if (decision.allowed) {
                    this.enqueueFn({
                        id: this.missionId(active.goal, draft),
                        goal: draft.goal,
                        source: 'workflow',
                        context: {
                            origin: 'goal-compiler',
                            parentGoal: active.goal,
                            acceptanceCriteria: draft.acceptanceCriteria,
                            maxSteps: draft.maxSteps,
                            rationale: draft.rationale,
                        },
                    });
                    enqueued += 1;
                }
                else if (decision.requiresConsent) {
                    held += 1;
                    try {
                        this.onConsentRequired?.(draft, decision);
                    }
                    catch { /* listener guard */ }
                }
                else {
                    blocked += 1;
                    try {
                        this.onBlocked?.(draft, decision);
                    }
                    catch { /* listener guard */ }
                }
            }
            // Mark compiled regardless of enqueue outcome — re-compiling the same
            // goal would produce the same gated result. The user changes /goal to
            // re-trigger. Held drafts are surfaced via the consent callback.
            this.store.markCompiled(active.goal);
            return { compiled: result.drafts.length, enqueued, held, blocked, source: result.source };
        }
        finally {
            this.ticking = false;
        }
    }
    /** Stable mission id: goal-scoped so re-enqueue of the same draft collides intentionally. */
    missionId(parentGoal, draft) {
        const h = (0, crypto_1.createHash)('sha256').update(`${parentGoal}::${draft.goal}`).digest('hex').slice(0, 12);
        return `goal-${h}`;
    }
}
exports.GoalTicker = GoalTicker;
