import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from "react";
import { useAuth } from "@/contexts/AuthContext";
import {
  applyDismiss,
  applyEnqueue,
  shouldEnqueueNote,
  type PopupPayload,
} from "@/lib/incoming-note-queue";

export type {
  PopupPayload,
  IncomingNotePayload,
  IncomingReplyPayload,
} from "@/lib/incoming-note-queue";

interface IncomingNotePopupContextValue {
  current: PopupPayload | null;
  queueLength: number;
  /**
   * Adds the payload to the FIFO popup queue. Returns true if the
   * payload was actually queued, false if it was suppressed (sender's
   * own note/reply, duplicate id, or malformed). Callers use the
   * boolean to gate side-effects like the chime so we don't beep for
   * our own outgoing actions or for replayed socket events.
   */
  enqueue: (
    payload: PopupPayload,
    currentUserId: number | null,
  ) => boolean;
  dismiss: (target: number | { replyId: number }) => void;
  /**
   * patch the checklist items of any queued popup payload
   * for `noteId` so an open popup re-renders against the freshest
   * shared state when another collaborator toggles an item.
   */
  updateChecklistItems: (
    noteId: number,
    items: { id: string; text: string; done: boolean }[],
  ) => void;
  clear: () => void;
}

const IncomingNotePopupContext =
  createContext<IncomingNotePopupContextValue | null>(null);

export function IncomingNotePopupProvider({ children }: { children: ReactNode }) {
  const [queue, setQueue] = useState<PopupPayload[]>([]);
  const { user } = useAuth();

  // Drop any queued popups whenever the active user identity changes
  // (logout, account switch, or even user→user transition) so the next
  // session never inherits the previous user's pending notes.
  const userId = user?.id ?? null;
  useEffect(() => {
    setQueue([]);
  }, [userId]);

  // Track the latest queue in a ref so `enqueue` can decide acceptance
  // synchronously without depending on React's state-updater timing.
  const queueRef = useRef<PopupPayload[]>(queue);
  useEffect(() => {
    queueRef.current = queue;
  }, [queue]);

  const enqueue = useCallback(
    (payload: PopupPayload, currentUserId: number | null): boolean => {
      const accepted = shouldEnqueueNote(
        queueRef.current,
        payload,
        currentUserId,
      );
      if (accepted) {
        setQueue((prev) => applyEnqueue(prev, payload, currentUserId));
      }
      return accepted;
    },
    [],
  );

  const dismiss = useCallback((target: number | { replyId: number }) => {
    setQueue((prev) => applyDismiss(prev, target));
  }, []);

  const updateChecklistItems = useCallback(
    (
      noteId: number,
      items: { id: string; text: string; done: boolean }[],
    ) => {
      setQueue((prev) =>
        prev.map((p) => {
          // Only the "incoming note" payload variant carries an `items`
          // field; reply popups don't have a checklist surface. Note
          // payloads have `kind === undefined | "note"`, replies have
          // `kind === "reply"`.
          if (p.kind === "reply" || p.noteId !== noteId) return p;
          return { ...p, items };
        }),
      );
    },
    [],
  );

  const clear = useCallback(() => setQueue([]), []);

  const value = useMemo<IncomingNotePopupContextValue>(
    () => ({
      current: queue[0] ?? null,
      queueLength: queue.length,
      enqueue,
      dismiss,
      updateChecklistItems,
      clear,
    }),
    [queue, enqueue, dismiss, updateChecklistItems, clear],
  );

  return (
    <IncomingNotePopupContext.Provider value={value}>
      {children}
    </IncomingNotePopupContext.Provider>
  );
}

export function useIncomingNotePopup(): IncomingNotePopupContextValue {
  const ctx = useContext(IncomingNotePopupContext);
  if (!ctx) {
    throw new Error(
      "useIncomingNotePopup must be used within IncomingNotePopupProvider",
    );
  }
  return ctx;
}
