import { useEffect, useRef, useState } from "react";
import type { ApiSettings } from "../types";
import { apiFetch } from "./client";

export type SharedNotificationSummary = {
  total: number;
  unread: number;
  pending: number;
  failed: number;
  show_unread_badge?: boolean;
};

export type SharedNotificationSummarySnapshot = {
  summary: SharedNotificationSummary | null;
  loading: boolean;
  error: unknown;
};

type Listener = (snapshot: SharedNotificationSummarySnapshot) => void;

type SummaryStore = {
  settings: ApiSettings;
  listeners: Map<Listener, number>;
  snapshot: SharedNotificationSummarySnapshot;
  inFlight: Promise<void> | null;
  lastFetchedAt: number;
  timerId: number | null;
  focusListener: () => void;
  changeListener: () => void;
};

const stores = new Map<string, SummaryStore>();
const EMPTY_SNAPSHOT: SharedNotificationSummarySnapshot = {
  summary: null,
  loading: false,
  error: null
};

function storeKey(settings: ApiSettings, scopeKey: string): string {
  return [
    settings.apiBaseUrl,
    settings.apiKey,
    settings.accessToken,
    scopeKey
  ].join("\u0000");
}

function notify(store: SummaryStore): void {
  for (const listener of store.listeners.keys()) {
    listener(store.snapshot);
  }
}

function minimumInterval(store: SummaryStore): number {
  return Math.max(
    5_000,
    Math.min(...Array.from(store.listeners.values()))
  );
}

function refresh(store: SummaryStore, force = false): Promise<void> {
  if (store.inFlight) return store.inFlight;
  if (
    !force &&
    store.lastFetchedAt > 0 &&
    Date.now() - store.lastFetchedAt < Math.min(minimumInterval(store), 5_000)
  ) {
    return Promise.resolve();
  }
  if (store.snapshot.summary === null) {
    store.snapshot = { ...store.snapshot, loading: true, error: null };
    notify(store);
  }
  const request = apiFetch<SharedNotificationSummary>(
    store.settings,
    "/api/v1/notifications/summary"
  )
    .then((summary) => {
      store.lastFetchedAt = Date.now();
      store.snapshot = { summary, loading: false, error: null };
      notify(store);
    })
    .catch((error) => {
      store.snapshot = {
        ...store.snapshot,
        loading: false,
        error
      };
      notify(store);
    })
    .finally(() => {
      if (store.inFlight === request) store.inFlight = null;
    });
  store.inFlight = request;
  return request;
}

function schedule(store: SummaryStore): void {
  if (store.timerId !== null) window.clearInterval(store.timerId);
  if (store.listeners.size === 0) {
    store.timerId = null;
    return;
  }
  store.timerId = window.setInterval(
    () => void refresh(store, true),
    minimumInterval(store)
  );
}

function createStore(settings: ApiSettings): SummaryStore {
  const store = {} as SummaryStore;
  store.settings = settings;
  store.listeners = new Map();
  store.snapshot = { ...EMPTY_SNAPSHOT };
  store.inFlight = null;
  store.lastFetchedAt = 0;
  store.timerId = null;
  store.focusListener = () => void refresh(store);
  store.changeListener = () => void refresh(store, true);
  return store;
}

function subscribe(
  settings: ApiSettings,
  scopeKey: string,
  intervalMs: number,
  listener: Listener
): { store: SummaryStore; unsubscribe: () => void } {
  const key = storeKey(settings, scopeKey);
  let store = stores.get(key);
  if (!store) {
    store = createStore(settings);
    stores.set(key, store);
  }
  const firstListener = store.listeners.size === 0;
  store.listeners.set(listener, Math.max(5_000, intervalMs));
  if (firstListener) {
    window.addEventListener("focus", store.focusListener);
    window.addEventListener(
      "govoplan:notifications-changed",
      store.changeListener
    );
  }
  listener(store.snapshot);
  schedule(store);
  void refresh(store);
  return {
    store,
    unsubscribe: () => {
      store?.listeners.delete(listener);
      if (store?.listeners.size === 0) {
        schedule(store);
        window.removeEventListener("focus", store.focusListener);
        window.removeEventListener(
          "govoplan:notifications-changed",
          store.changeListener
        );
        stores.delete(key);
      } else if (store) {
        schedule(store);
      }
    }
  };
}

export function useSharedNotificationSummary(
  settings: ApiSettings,
  options: {
    enabled: boolean;
    scopeKey: string;
    intervalMs?: number;
    refreshKey?: string | number;
  }
): SharedNotificationSummarySnapshot {
  const [snapshot, setSnapshot] =
    useState<SharedNotificationSummarySnapshot>(EMPTY_SNAPSHOT);
  const activeStore = useRef<SummaryStore | null>(null);
  const previousRefreshKey = useRef(options.refreshKey);

  useEffect(() => {
    if (!options.enabled) {
      activeStore.current = null;
      setSnapshot(EMPTY_SNAPSHOT);
      return;
    }
    const subscription = subscribe(
      settings,
      options.scopeKey,
      options.intervalMs ?? 60_000,
      setSnapshot
    );
    activeStore.current = subscription.store;
    return () => {
      activeStore.current = null;
      subscription.unsubscribe();
    };
  }, [
    options.enabled,
    options.intervalMs,
    options.scopeKey,
    settings.accessToken,
    settings.apiBaseUrl,
    settings.apiKey
  ]);

  useEffect(() => {
    if (previousRefreshKey.current === options.refreshKey) return;
    previousRefreshKey.current = options.refreshKey;
    if (activeStore.current) void refresh(activeStore.current, true);
  }, [options.refreshKey]);

  return snapshot;
}
