'use client';
import { useCallback, useEffect, useRef, useState } from 'react';
import { createLogger } from '../lib/logger.js';
const log = createLogger('[SSE]');
const SW_PATH = '/agent-events-sw.js';
const MAX_EVENTS = 500;
const PRUNE_KEEP = 250;
/**
 * Hook that receives real-time agent notification events via a Service Worker.
 *
 * The SW maintains a single SSE connection to /api/agent-events and
 * broadcasts events to all open tabs. Falls back to direct EventSource
 * when Service Workers are unavailable.
 */
export function useAgentEvents(options) {
    const [events, setEvents] = useState([]);
    const [lastEvent, setLastEvent] = useState(null);
    const [agentMessages, setAgentMessages] = useState([]);
    const [lastAgentMessage, setLastAgentMessage] = useState(null);
    const [agentQuestions, setAgentQuestions] = useState([]);
    const [lastAgentQuestion, setLastAgentQuestion] = useState(null);
    const [supervisorDecisions, setSupervisorDecisions] = useState([]);
    const [lastSupervisorDecision, setLastSupervisorDecision] = useState(null);
    const [connectionStatus, setConnectionStatus] = useState('disconnected');
    const runId = options?.runId;
    const swRef = useRef(null);
    const onMessage = useCallback((event) => {
        const msg = event.data;
        if (!msg || typeof msg !== 'object')
            return;
        if (msg.type === 'notification') {
            const parsed = msg.data;
            log.debug('event received:', parsed.eventType, parsed);
            setEvents((prev) => {
                const next = [...prev, parsed];
                return next.length > MAX_EVENTS ? next.slice(-PRUNE_KEEP) : next;
            });
            setLastEvent(parsed);
        }
        else if (msg.type === 'agent_message') {
            const parsed = msg.data;
            setAgentMessages((prev) => {
                const next = [...prev, parsed];
                return next.length > MAX_EVENTS ? next.slice(-PRUNE_KEEP) : next;
            });
            setLastAgentMessage(parsed);
        }
        else if (msg.type === 'agent_question') {
            const parsed = msg.data;
            setAgentQuestions((prev) => {
                const next = [...prev, parsed];
                return next.length > MAX_EVENTS ? next.slice(-PRUNE_KEEP) : next;
            });
            setLastAgentQuestion(parsed);
        }
        else if (msg.type === 'supervisor_decision') {
            const parsed = msg.data;
            setSupervisorDecisions((prev) => {
                const next = [...prev, parsed];
                return next.length > MAX_EVENTS ? next.slice(-PRUNE_KEEP) : next;
            });
            setLastSupervisorDecision(parsed);
        }
        else if (msg.type === 'status') {
            setConnectionStatus(msg.status);
        }
    }, []);
    useEffect(() => {
        if (typeof window === 'undefined')
            return;
        // ?sse=direct bypasses the Service Worker for debugging
        const bypassSW = typeof location !== 'undefined' && new URLSearchParams(location.search).has('sse');
        // Fallback: direct EventSource when SW not supported or bypassed
        if (!navigator.serviceWorker || bypassSW) {
            return connectDirectEventSource(runId, {
                setEvents,
                setLastEvent,
                setAgentMessages,
                setLastAgentMessage,
                setAgentQuestions,
                setLastAgentQuestion,
                setSupervisorDecisions,
                setLastSupervisorDecision,
                setConnectionStatus,
            });
        }
        let cancelled = false;
        const fallbackCleanupRef = { current: undefined };
        function subscribeToWorker(worker) {
            if (cancelled)
                return;
            swRef.current = worker;
            worker.postMessage({ type: 'subscribe', runId });
            setConnectionStatus('connecting');
        }
        // Listen for messages from whatever SW controls this page
        navigator.serviceWorker.addEventListener('message', onMessage);
        // Re-subscribe when SW controller changes (e.g., after SW update via skipWaiting)
        function handleControllerChange() {
            if (cancelled)
                return;
            const newController = navigator.serviceWorker.controller;
            if (newController) {
                swRef.current = newController;
                newController.postMessage({ type: 'subscribe', runId });
                setConnectionStatus('connecting');
            }
        }
        navigator.serviceWorker.addEventListener('controllerchange', handleControllerChange);
        navigator.serviceWorker
            .register(SW_PATH, { scope: '/' })
            .then((registration) => {
            if (cancelled)
                return;
            // Use the active worker, or wait for it to activate
            const sw = registration.active ?? registration.installing ?? registration.waiting;
            if (!sw) {
                // No worker at all — fall back to direct EventSource
                fallbackCleanupRef.current = connectDirectEventSource(runId, {
                    setEvents,
                    setLastEvent,
                    setAgentMessages,
                    setLastAgentMessage,
                    setAgentQuestions,
                    setLastAgentQuestion,
                    setSupervisorDecisions,
                    setLastSupervisorDecision,
                    setConnectionStatus,
                });
                return;
            }
            if (sw.state === 'activated') {
                subscribeToWorker(sw);
            }
            else {
                const onStateChange = () => {
                    if (sw.state === 'activated') {
                        sw.removeEventListener('statechange', onStateChange);
                        subscribeToWorker(sw);
                    }
                };
                sw.addEventListener('statechange', onStateChange);
            }
        })
            .catch(() => {
            // SW registration failed — fall back to direct EventSource
            if (!cancelled) {
                fallbackCleanupRef.current = connectDirectEventSource(runId, {
                    setEvents,
                    setLastEvent,
                    setAgentMessages,
                    setLastAgentMessage,
                    setAgentQuestions,
                    setLastAgentQuestion,
                    setSupervisorDecisions,
                    setLastSupervisorDecision,
                    setConnectionStatus,
                });
            }
        });
        return () => {
            cancelled = true;
            navigator.serviceWorker.removeEventListener('message', onMessage);
            navigator.serviceWorker.removeEventListener('controllerchange', handleControllerChange);
            swRef.current?.postMessage({ type: 'unsubscribe' });
            swRef.current = null;
            fallbackCleanupRef.current?.();
        };
    }, [runId, onMessage]);
    return {
        events,
        lastEvent,
        agentMessages,
        lastAgentMessage,
        agentQuestions,
        lastAgentQuestion,
        supervisorDecisions,
        lastSupervisorDecision,
        connectionStatus,
    };
}
// --- EventSource fallback (identical to the previous implementation) ---
const BASE_BACKOFF_MS = 1000;
const MAX_BACKOFF_MS = 30_000;
const STABLE_CONNECTION_MS = 5_000;
function connectDirectEventSource(runId, setters) {
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    if (typeof EventSource === 'undefined')
        return () => { };
    let es = null;
    let stopped = false;
    let backoff = BASE_BACKOFF_MS;
    let reconnectTimer = null;
    let stableTimer = null;
    function connect() {
        if (stopped)
            return;
        const url = runId
            ? `/api/agent-events?runId=${encodeURIComponent(runId)}`
            : '/api/agent-events';
        es = new EventSource(url);
        setters.setConnectionStatus('connecting');
        es.onopen = () => {
            setters.setConnectionStatus('connected');
            stableTimer = setTimeout(() => {
                stableTimer = null;
                backoff = BASE_BACKOFF_MS;
            }, STABLE_CONNECTION_MS);
        };
        es.onerror = () => {
            es?.close();
            es = null;
            setters.setConnectionStatus('disconnected');
            if (stableTimer !== null) {
                clearTimeout(stableTimer);
                stableTimer = null;
            }
            const delay = backoff;
            backoff = Math.min(delay * 2, MAX_BACKOFF_MS);
            reconnectTimer = setTimeout(() => {
                reconnectTimer = null;
                connect();
            }, delay);
        };
        es.addEventListener('notification', ((event) => {
            const parsed = JSON.parse(event.data);
            setters.setEvents((prev) => {
                const next = [...prev, parsed];
                return next.length > MAX_EVENTS ? next.slice(-PRUNE_KEEP) : next;
            });
            setters.setLastEvent(parsed);
        }));
        es.addEventListener('agent_message', ((event) => {
            const parsed = JSON.parse(event.data);
            setters.setAgentMessages((prev) => {
                const next = [...prev, parsed];
                return next.length > MAX_EVENTS ? next.slice(-PRUNE_KEEP) : next;
            });
            setters.setLastAgentMessage(parsed);
        }));
        es.addEventListener('agent_question', ((event) => {
            const parsed = JSON.parse(event.data);
            setters.setAgentQuestions((prev) => {
                const next = [...prev, parsed];
                return next.length > MAX_EVENTS ? next.slice(-PRUNE_KEEP) : next;
            });
            setters.setLastAgentQuestion(parsed);
        }));
        es.addEventListener('supervisor_decision', ((event) => {
            const parsed = JSON.parse(event.data);
            setters.setSupervisorDecisions((prev) => {
                const next = [...prev, parsed];
                return next.length > MAX_EVENTS ? next.slice(-PRUNE_KEEP) : next;
            });
            setters.setLastSupervisorDecision(parsed);
        }));
    }
    connect();
    return () => {
        stopped = true;
        if (reconnectTimer !== null) {
            clearTimeout(reconnectTimer);
            reconnectTimer = null;
        }
        if (stableTimer !== null) {
            clearTimeout(stableTimer);
            stableTimer = null;
        }
        if (es) {
            es.close();
            es = null;
        }
    };
}
