"use client";
import { startTransition, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
import { useStigmer } from "../hooks";
import { toError } from "../internal/toError";
import { useStreamRate } from "../internal/dev";
import { StreamController, } from "../internal/stream-controller";
import { ConversationStore } from "../internal/store";
import { isTerminalPhase } from "./execution-phases";
/**
 * Behavior hook that subscribes to real-time {@link AgentExecution}
 * updates via `stigmer.agentExecution.subscribe()`.
 *
 * Manages the full subscription lifecycle through a finite state
 * machine: connection establishment, rAF-coalesced snapshot streaming,
 * terminal-phase detection, error handling, and manual reconnection.
 *
 * **Performance characteristics:**
 * - Non-terminal snapshots are coalesced via `requestAnimationFrame`
 *   so React commits at most once per display frame (~60Hz)
 * - Terminal snapshots (complete/failed/cancelled) flush immediately
 * - Store updates are wrapped in `startTransition` so thread renders
 *   don't block urgent interactions (e.g. composer typing)
 *
 * Pass `null` as `executionId` to skip subscribing (stable no-op).
 * When `executionId` changes, the previous subscription is aborted
 * and a fresh one begins.
 *
 * @example
 * ```tsx
 * function LiveExecution({ id }: { id: string }) {
 *   const { execution, isStreaming, error, reconnect } =
 *     useExecutionStream(id);
 *
 *   if (error) return <p>{error.message} <button onClick={reconnect}>Retry</button></p>;
 *   if (!execution) return <p>Connecting…</p>;
 *
 *   return (
 *     <div>
 *       {execution.status?.messages.map((m, i) => (
 *         <p key={i}>{m.content}</p>
 *       ))}
 *       {isStreaming && <span>Streaming…</span>}
 *     </div>
 *   );
 * }
 * ```
 */
export function useExecutionStream(executionId, options) {
    const stigmer = useStigmer();
    // -- Store setup ----------------------------------------------------------
    // Use the externally provided store, or create a private one for
    // standalone usage. The ref ensures the internal store is stable
    // across re-renders.
    const internalStoreRef = useRef(null);
    if (!options?.store && !internalStoreRef.current) {
        internalStoreRef.current = new ConversationStore();
    }
    const store = options?.store ?? internalStoreRef.current;
    // -- Controller setup -----------------------------------------------------
    const controllerRef = useRef(null);
    if (!controllerRef.current) {
        const sink = {
            ingestSnapshot(snapshot) {
                startTransition(() => {
                    store.ingestSnapshot(snapshot);
                });
            },
            setStreamState(state) {
                startTransition(() => {
                    store.setStreamState(state);
                });
            },
        };
        controllerRef.current = new StreamController(sink);
    }
    const controller = controllerRef.current;
    // -- Reconnect ------------------------------------------------------------
    const [connectKey, setConnectKey] = useState(0);
    const reconnect = useCallback(() => {
        setConnectKey((k) => k + 1);
    }, []);
    // -- Stream rate instrumentation ------------------------------------------
    const streamRate = useStreamRate();
    const streamRateRef = useRef(streamRate);
    streamRateRef.current = streamRate;
    // -- Subscription effect --------------------------------------------------
    // Note: controller, store, and streamRate are ref-backed stable objects —
    // they MUST NOT appear in the deps array. Including them would cause
    // infinite re-renders because useStreamRate returns a new object per render.
    useEffect(() => {
        if (!executionId) {
            controller.reset();
            store.reset();
            return;
        }
        const abortController = new AbortController();
        controller.start(executionId);
        (async () => {
            try {
                for await (const snapshot of stigmer.agentExecution.subscribe(executionId, abortController.signal)) {
                    if (abortController.signal.aborted)
                        return;
                    controller.handleSnapshot(snapshot);
                    streamRateRef.current.tick(snapshot.status?.messages?.length ?? 0);
                    const phase = snapshot.status?.phase ??
                        ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
                    if (isTerminalPhase(phase))
                        break;
                }
                if (!abortController.signal.aborted) {
                    controller.handleStreamEnd();
                    streamRateRef.current.summary();
                }
            }
            catch (err) {
                if (abortController.signal.aborted)
                    return;
                controller.handleError(toError(err));
            }
        })();
        return () => {
            abortController.abort();
            controller.reset();
            store.reset();
        };
    }, [executionId, stigmer, connectKey]);
    // -- Read from store via useSyncExternalStore ------------------------------
    const execution = useSyncExternalStore(store.subscribe, store.getExecution);
    const streamState = useSyncExternalStore(store.subscribe, store.getStreamState);
    // -- Derive public return values ------------------------------------------
    const phase = useMemo(() => execution?.status?.phase ?? ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED, [execution]);
    const isStreaming = streamState.stage === "streaming";
    const isConnecting = streamState.stage === "connecting";
    const error = streamState.stage === "error" ? streamState.error : null;
    return { execution, phase, isStreaming, isConnecting, error, reconnect };
}
//# sourceMappingURL=useExecutionStream.js.map