import React, { createContext, useContext, useEffect, useReducer, useRef, type Dispatch } from 'react';
import type { AppState, AppAction } from './state';
import { initialState } from './state';
import { appReducer } from './reducer';
import { CHAT_STATE_CHANNEL, createChatStateSyncMessage, parseChatStateSyncMessage } from './chatStateSync';
import type { ChatStateSyncMessage } from './chatStateSync';

const AppStateContext = createContext<AppState>(initialState);
const AppDispatchContext = createContext<Dispatch<AppAction>>(() => { /* noop */ });

export function AppStateProvider({ children, testInitialState }: { children: React.ReactNode; testInitialState?: Partial<AppState> }) {
  const [state, dispatch] = useReducer(appReducer, testInitialState ? { ...initialState, ...testInitialState } : initialState);
  const stateRef = useRef(state);
  const channelRef = useRef<BroadcastChannel | null>(null);
  const applyingRemoteState = useRef(false);
  const pendingFlushRaf = useRef<number | null>(null);

  useEffect(() => {
    stateRef.current = state;
  }, [state]);

  useEffect(() => {
    if (testInitialState || !channelRef.current) return;
    if (applyingRemoteState.current) {
      applyingRemoteState.current = false;
      return;
    }
    if (pendingFlushRaf.current !== null) return;
    // The cleanup of the channel-lifecycle effect (below) deliberately does
    // not cancel the pending rAF on every state change — that would defeat
    // coalescing. Cancel-and-final-flush only happens on full unmount.
    pendingFlushRaf.current = requestAnimationFrame(() => {
      const channel = channelRef.current;
      if (!channel) {
        pendingFlushRaf.current = null;
        return;
      }
      channel.postMessage(createChatStateSyncMessage({
        messagesByMind: stateRef.current.messagesByMind,
        streamingByMind: stateRef.current.streamingByMind,
        conversationViewByMind: stateRef.current.conversationViewByMind,
      }));
      pendingFlushRaf.current = null;
    });
  }, [state.conversationViewByMind, state.messagesByMind, state.streamingByMind, testInitialState]);

  useEffect(() => {
    if (testInitialState || typeof BroadcastChannel === 'undefined') return;

    const channel = new BroadcastChannel(CHAT_STATE_CHANNEL);
    channelRef.current = channel;
    channel.onmessage = (event) => {
      const message = parseChatStateSyncMessage(event.data);
      if (!message) return;

      if (message.type === 'request-state') {
        channel.postMessage(createChatStateSyncMessage({
          messagesByMind: stateRef.current.messagesByMind,
          streamingByMind: stateRef.current.streamingByMind,
          conversationViewByMind: stateRef.current.conversationViewByMind,
        }));
        return;
      }

      applyingRemoteState.current = true;
      dispatch({ type: 'HYDRATE_CHAT_STATE', payload: message.payload });
    };

    channel.postMessage({ type: 'request-state' } satisfies ChatStateSyncMessage);

    return () => {
      if (pendingFlushRaf.current !== null) {
        cancelAnimationFrame(pendingFlushRaf.current);
        pendingFlushRaf.current = null;
        // Final synchronous flush so peer windows do not lose the last
        // delta when the source window unmounts mid-frame.
        channel.postMessage(createChatStateSyncMessage({
          messagesByMind: stateRef.current.messagesByMind,
          streamingByMind: stateRef.current.streamingByMind,
          conversationViewByMind: stateRef.current.conversationViewByMind,
        }));
      }
      channelRef.current = null;
      channel.close();
    };
  }, [testInitialState]);

  return (
    <AppStateContext.Provider value={state}>
      <AppDispatchContext.Provider value={dispatch}>
        {children}
      </AppDispatchContext.Provider>
    </AppStateContext.Provider>
  );
}

export function useAppState() {
  return useContext(AppStateContext);
}

export function useAppDispatch() {
  return useContext(AppDispatchContext);
}
