import { structuralShare } from "./structural-share.js";
const IDLE_STATE = { stage: "idle" };
/**
 * Framework-agnostic store that holds the active execution snapshot
 * with structural sharing. Implements the contract required by
 * React's `useSyncExternalStore`.
 *
 * - `ingestSnapshot` applies structural sharing so unchanged nested
 *   entities keep their previous reference.
 * - Listeners are notified only when state actually changes.
 * - `getExecution` and `getStreamState` return stable references
 *   suitable as `useSyncExternalStore` snapshot selectors.
 */
export class ConversationStore {
    _execution = null;
    _streamState = IDLE_STATE;
    _connectTimedOut = false;
    _isSlow = false;
    _listeners = new Set();
    // -- Ingestion -----------------------------------------------------------
    /**
     * Ingest a new execution snapshot. Applies structural sharing
     * against the previous snapshot and notifies listeners only if
     * the resulting reference changed.
     */
    ingestSnapshot(snapshot) {
        const shared = structuralShare(this._execution, snapshot);
        if (shared === this._execution)
            return;
        this._execution = shared;
        this._notify();
    }
    /**
     * Transition the stream lifecycle state. Notifies listeners only
     * when the stage or executionId actually changes.
     */
    setStreamState(state) {
        if (streamStateEqual(this._streamState, state))
            return;
        this._streamState = state;
        this._notify();
    }
    /**
     * Set the hard connect-timeout signal — the stream opened but no first
     * snapshot arrived within the watchdog window even after a silent retry.
     *
     * Orthogonal to {@link setStreamState}: the stream may still be live, so
     * this is **not** a lifecycle stage and deliberately does not touch the
     * `error` stage (that is auto-reconnect's domain). Booleans are stable by
     * value, so no snapshot caching is needed; listeners fire only on change.
     */
    setConnectTimedOut(value) {
        if (this._connectTimedOut === value)
            return;
        this._connectTimedOut = value;
        this._notify();
    }
    /**
     * Set the soft slow-stall hint — the stream is non-terminal but has gone
     * silent past the watchdog window. Purely informational ("still working,
     * taking longer than usual"); cleared by the next snapshot. Never aborts.
     */
    setSlow(value) {
        if (this._isSlow === value)
            return;
        this._isSlow = value;
        this._notify();
    }
    /**
     * Reset to initial state. Used when the session identity changes
     * or the hook unmounts.
     */
    reset() {
        const wasClean = this._execution === null &&
            this._streamState.stage === "idle" &&
            !this._connectTimedOut &&
            !this._isSlow;
        this._execution = null;
        this._streamState = IDLE_STATE;
        this._connectTimedOut = false;
        this._isSlow = false;
        if (!wasClean)
            this._notify();
    }
    // -- useSyncExternalStore contract ---------------------------------------
    /**
     * Subscribe a listener that is called whenever any store state
     * changes. Returns an unsubscribe function.
     */
    subscribe = (listener) => {
        this._listeners.add(listener);
        return () => {
            this._listeners.delete(listener);
        };
    };
    /** Stable snapshot selector for the current execution. */
    getExecution = () => {
        return this._execution;
    };
    /** Stable snapshot selector for the stream lifecycle state. */
    getStreamState = () => {
        return this._streamState;
    };
    /** Stable snapshot selector for the hard connect-timeout signal. */
    getConnectTimedOut = () => {
        return this._connectTimedOut;
    };
    /** Stable snapshot selector for the soft slow-stall hint. */
    getSlow = () => {
        return this._isSlow;
    };
    // -- Internal ------------------------------------------------------------
    _notify() {
        for (const listener of this._listeners) {
            listener();
        }
    }
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function streamStateEqual(a, b) {
    if (a.stage !== b.stage)
        return false;
    if (a.stage === "idle")
        return true;
    if (a.stage === "error" &&
        b.stage === "error" &&
        a.executionId === b.executionId &&
        a.error === b.error)
        return true;
    // Each retry bumps `attempt`, so two reconnecting states are only equal
    // when the attempt matches — every attempt must re-notify subscribers.
    if (a.stage === "reconnecting" &&
        b.stage === "reconnecting" &&
        a.executionId === b.executionId &&
        a.attempt === b.attempt)
        return true;
    if ("executionId" in a && "executionId" in b)
        return a.executionId === b.executionId;
    return false;
}
//# sourceMappingURL=conversation-store.js.map