import { isTerminalPhase } from "../execution/execution-phases";
import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
const IDLE = { stage: "idle" };
// ---------------------------------------------------------------------------
// StreamController
// ---------------------------------------------------------------------------
/**
 * Framework-agnostic finite state machine that manages the lifecycle
 * of a single execution stream subscription.
 *
 * Responsibilities:
 * - Track FSM state transitions (idle → connecting → streaming → complete/error)
 * - Buffer incoming snapshots and coalesce via `requestAnimationFrame`
 * - Flush terminal snapshots immediately (no rAF delay)
 * - Provide abort/reconnect semantics
 *
 * This class has no React dependency. It communicates outward through
 * a {@link StreamControllerSink} and a `scheduleFlush` function
 * (typically `requestAnimationFrame`).
 */
export class StreamController {
    _state = IDLE;
    _bufferedSnapshot = null;
    _rafId = null;
    _sink;
    _scheduleFlush;
    _cancelFlush;
    constructor(sink, scheduleFlush = typeof requestAnimationFrame !== "undefined"
        ? requestAnimationFrame
        : (cb) => setTimeout(cb, 16), cancelFlush = typeof cancelAnimationFrame !== "undefined"
        ? cancelAnimationFrame
        : clearTimeout) {
        this._sink = sink;
        this._scheduleFlush = scheduleFlush;
        this._cancelFlush = cancelFlush;
    }
    /** Current FSM state (read-only). */
    get state() {
        return this._state;
    }
    /**
     * Transition to `connecting` for the given execution ID.
     * If already active for a different ID, resets first.
     */
    start(executionId) {
        this._cancelPendingFlush();
        this._bufferedSnapshot = null;
        this._transition({ stage: "connecting", executionId });
    }
    /**
     * Handle an incoming snapshot from the gRPC stream.
     * Non-terminal snapshots are buffered for rAF coalescing.
     * Terminal snapshots flush immediately.
     */
    handleSnapshot(snapshot) {
        const executionId = this._activeExecutionId();
        if (!executionId)
            return;
        const phase = snapshot.status?.phase ?? ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
        const terminal = isTerminalPhase(phase);
        if (terminal) {
            this._cancelPendingFlush();
            this._bufferedSnapshot = null;
            this._sink.ingestSnapshot(snapshot);
            this._transition({ stage: "complete", executionId });
        }
        else {
            if (this._state.stage === "connecting") {
                this._transition({ stage: "streaming", executionId });
            }
            this._bufferedSnapshot = snapshot;
            this._scheduleFlushOnce();
        }
    }
    /**
     * Handle stream completion (iterator exhausted without error).
     * If we still have a buffered snapshot, flush it first.
     */
    handleStreamEnd() {
        const executionId = this._activeExecutionId();
        if (!executionId)
            return;
        this._flushBuffer();
        if (this._state.stage !== "complete") {
            this._transition({ stage: "complete", executionId });
        }
    }
    /**
     * Handle a stream error. Flushes any buffered snapshot first
     * so the UI shows the last known good state alongside the error.
     */
    handleError(error) {
        const executionId = this._activeExecutionId();
        if (!executionId)
            return;
        this._cancelPendingFlush();
        this._flushBuffer();
        this._transition({ stage: "error", executionId, error });
    }
    /** Reset to idle. Cancels any pending flush. */
    reset() {
        this._cancelPendingFlush();
        this._bufferedSnapshot = null;
        if (this._state.stage !== "idle") {
            this._transition(IDLE);
        }
    }
    /** Whether the controller has a buffered snapshot awaiting flush. */
    get hasPendingFlush() {
        return this._rafId !== null;
    }
    // -------------------------------------------------------------------------
    // Private
    // -------------------------------------------------------------------------
    _activeExecutionId() {
        if (this._state.stage === "idle")
            return null;
        return this._state.executionId;
    }
    _transition(next) {
        this._state = next;
        this._sink.setStreamState(next);
    }
    _scheduleFlushOnce() {
        if (this._rafId !== null)
            return;
        this._rafId = this._scheduleFlush(() => {
            this._rafId = null;
            this._flushBuffer();
        });
    }
    _flushBuffer() {
        if (this._bufferedSnapshot) {
            const snap = this._bufferedSnapshot;
            this._bufferedSnapshot = null;
            this._sink.ingestSnapshot(snap);
        }
    }
    _cancelPendingFlush() {
        if (this._rafId !== null) {
            this._cancelFlush(this._rafId);
            this._rafId = null;
        }
    }
}
//# sourceMappingURL=stream-controller.js.map