import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { loadSessions, loadUsage, sessionEventsUrl } from "./notification-api.js";
import { cleanupNotificationState, dismissSessionNotificationState, mergeNotificationState, readPersistedNotifications, readPersistedSessionSnapshot, writePersistedNotifications, writePersistedSessionSnapshot } from "./notification-persistence.js";
import { collectNotificationPoll, detectSessionNotifications, mergeSessionSnapshot, mergeSessionUpdate } from "./notification-state.js";
import { readNotificationPreferences } from "./notification-settings.js";
import { deliverSystemNotifications } from "./system-notification.js";
import { request } from "../../app/api.js";
import { createReconnectingEventSource } from "../../app/event-stream.js";

export function useNotifications({ accounts, currentSession, recoveryVersion = 0 }) {
  const [notificationState, setNotificationState] = useState(() => readPersistedNotifications());
  const [sessions, setSessions] = useState([]);
  const [openError, setOpenError] = useState(null);
  const [refreshErrors, setRefreshErrors] = useState([]);
  const notificationStateRef = useRef(notificationState);
  const deliveredNotificationsRef = useRef(notificationState.notifications);
  const previousSessions = useRef(readPersistedSessionSnapshot());
  const previousUsage = useRef(new Map());
  const currentSessionId = useRef(currentSession?.id);
  const accountsRef = useRef(accounts);
  const refreshRef = useRef(() => Promise.resolve());
  const accountKey = useMemo(() => accounts.map((account) => account.id).sort().join("|"), [accounts]);

  useEffect(() => { currentSessionId.current = currentSession?.id; }, [currentSession]);
  useEffect(() => { accountsRef.current = accounts; }, [accounts]);
  notificationStateRef.current = notificationState;
  useEffect(() => { writePersistedNotifications(undefined, notificationState, { sessions }); }, [notificationState, sessions]);
  useEffect(() => {
    const previous = deliveredNotificationsRef.current;
    deliveredNotificationsRef.current = notificationState.notifications;
    void deliverSystemNotifications(previous, notificationState.notifications, readNotificationPreferences());
  }, [notificationState.notifications]);
  useEffect(() => {
    if (!currentSession?.id) return;
    setNotificationState((current) => {
      const next = dismissSessionNotificationState(current, currentSession?.id, { types: ["completed"] });
      notificationStateRef.current = next;
      return next;
    });
  }, [currentSession?.id, notificationState.notifications]);

  const appendNotifications = useCallback((items) => {
    if (items.length === 0) return;
    setNotificationState((current) => {
      const next = mergeNotificationState(current, items);
      notificationStateRef.current = next;
      return next;
    });
  }, []);

  const commitSessions = useCallback((nextSessions, { detect = true } = {}) => {
    const previous = previousSessions.current;
    if (detect && previous) appendNotifications(detectSessionNotifications(previous, nextSessions, currentSessionId.current, previousUsage.current));
    previousSessions.current = nextSessions;
    writePersistedSessionSnapshot(undefined, nextSessions);
    setSessions(nextSessions);
    setNotificationState((current) => cleanupNotificationState(current, { sessions: nextSessions }));
  }, [appendNotifications]);

  useEffect(() => {
    let active = true;

    async function load() {
      const latestAccounts = accountsRef.current;
      const results = await Promise.allSettled([loadSessions(), ...latestAccounts.map((account) => loadUsage(account.id))]);
      if (!active) return;
      const poll = collectNotificationPoll({
        accounts: latestAccounts,
        results,
        previousSessions: previousSessions.current,
        previousUsage: previousUsage.current,
        currentNotifications: notificationStateRef.current.notifications,
        currentSessionId: currentSessionId.current
      });
      if (poll.sessions) {
        commitSessions(mergeSessionSnapshot(previousSessions.current ?? [], poll.sessions), { detect: false });
      }
      previousUsage.current = poll.previousUsage;
      setRefreshErrors(poll.errors);
      if (active) appendNotifications(poll.notifications);
    }

    refreshRef.current = load;
    load();
    const timer = setInterval(load, 60000);
    return () => { active = false; clearInterval(timer); };
  }, [accountKey, appendNotifications, commitSessions]);

  useEffect(() => {
    const connection = createReconnectingEventSource({
      url: sessionEventsUrl,
      onSource(source) {
        source.addEventListener("sessions.snapshot", (event) => {
          const data = JSON.parse(event.data);
          commitSessions(mergeSessionSnapshot(previousSessions.current ?? [], data.sessions ?? []));
        });
        source.addEventListener("session.updated", (event) => {
          const updatedSession = JSON.parse(event.data);
          commitSessions(mergeSessionUpdate(previousSessions.current ?? [], updatedSession));
        });
      },
      onError: () => { void request("/auth/session").catch(() => {}); }
    });
    return () => connection.stop();
  }, [commitSessions, recoveryVersion]);

  return {
    notifications: notificationState.notifications,
    sessions,
    openError,
    refreshErrors,
    reload: () => refreshRef.current(),
    failOpen: () => setOpenError("Could not open notification. Try again."),
    clearOpenError: () => setOpenError(null),
    dismiss: (id) => setNotificationState((current) => ({
      notifications: current.notifications.filter((item) => item.id !== id),
      dismissedIds: current.dismissedIds.includes(id) ? current.dismissedIds : [id, ...current.dismissedIds]
    })),
    dismissSession: (sessionId) => setNotificationState((current) => dismissSessionNotificationState(current, sessionId)),
    clear: () => {
      setOpenError(null);
      setRefreshErrors([]);
      setNotificationState((current) => ({
        notifications: [],
        dismissedIds: [...new Set([...current.notifications.map((item) => item.id), ...current.dismissedIds])]
      }));
    }
  };
}
