"use client";

import { useEffect, useEffectEvent, useLayoutEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import {
  COMPOSE_EXIT_ENDPOINT, composeDraftFingerprint, exitDraftForm, openExitDraftDatabase,
  storeExitDraft, submitExitDraft, type ExitDraft,
} from "@/lib/mail-compose-exit-client";

export function useComposeExitSave(mailbox: string, snapshot: () => FormData | null, suspended: () => boolean) {
  const router = useRouter();
  const [saving, setSaving] = useState(false);
  const state = useRef({ key: "", grant: "", baseline: "", lastFingerprint: "", dismissed: false, leaving: false, latest: null as FormData | null });
  const refreshMemory = useEffectEvent(() => { const form = snapshot(); if (form) state.current.latest = form; });
  useLayoutEffect(() => { refreshMemory(); });
  const buildDraft = (form: FormData | null) => {
    if (!form) return null;
    const fingerprint = composeDraftFingerprint(form);
    if (fingerprint === state.current.baseline) return null;
    if (state.current.lastFingerprint && state.current.lastFingerprint !== fingerprint) {
      // A failed response may already have committed. Never reuse that receipt
      // for newly edited content and silently accept an older version.
      const key = crypto.randomUUID();
      state.current.key = key;
      state.current.grant = "";
      void fetch(`${COMPOSE_EXIT_ENDPOINT}?key=${key}`).then(response => response.ok ? response.json() : null)
        .then(value => { if (state.current.key === key && value?.grant) state.current.grant = value.grant; }).catch(() => undefined);
    }
    state.current.lastFingerprint = fingerprint;
    const payload = new FormData();
    for (const [name, value] of form.entries()) payload.append(name, value);
    payload.set("exitGrant", state.current.grant);
    return { key: state.current.key, mailbox, entries: Array.from(payload.entries()), createdAt: Date.now() } satisfies ExitDraft;
  };
  const capture = useEffectEvent(() => {
    if (state.current.dismissed || suspended()) return null;
    return buildDraft(snapshot() ?? state.current.latest);
  });

  useEffect(() => {
    state.current.key = crypto.randomUUID();
    const initial = snapshot();
    state.current.baseline = initial ? composeDraftFingerprint(initial) : "";
    void openExitDraftDatabase().catch(() => undefined);
    const controller = new AbortController();
    const initialKey = state.current.key;
    void fetch(`${COMPOSE_EXIT_ENDPOINT}?key=${initialKey}`, { signal: controller.signal })
      .then(response => response.ok ? response.json() : null)
      .then(value => { if (state.current.key === initialKey && value?.grant) state.current.grant = value.grant; }).catch(() => undefined);

    const unload = () => {
      if (state.current.leaving) return;
      const draft = capture();
      if (!draft) return;
      void storeExitDraft(draft).catch(() => undefined);
      const form = exitDraftForm(draft);
      // Beacon/keepalive bodies share a ~64 KiB budget. Larger attachments are
      // recovered from IndexedDB on the next authenticated visit, not dropped.
      const size = draft.entries.reduce((total, [, value]) => total + (typeof value === "string" ? new Blob([value]).size : value.size) + 512, 1024);
      if (size < 60 * 1024) navigator.sendBeacon(COMPOSE_EXIT_ENDPOINT, form);
    };
    let abandonedKey = "";
    const abandon = () => {
      if (state.current.leaving) return;
      const draft = capture();
      if (!draft || abandonedKey === draft.key) return;
      abandonedKey = draft.key;
      void (async () => {
        await storeExitDraft(draft).catch(() => undefined);
        try {
          await submitExitDraft(draft);
          window.dispatchEvent(new CustomEvent("official-mail-draft-saved", { detail: { mailbox } }));
        } catch {
          // The page has already changed. Keep the recovery copy for a retry.
        }
      })();
    };
    const saveAndLeave = async (draft: ExitDraft, navigate: () => void) => {
      if (state.current.leaving) return;
      state.current.leaving = true;
      setSaving(true);
      await storeExitDraft(draft).catch(() => undefined);
      try {
        await submitExitDraft(draft);
        state.current.dismissed = true;
        sessionStorage.setItem("official-mail-exit-draft-notice", mailbox);
        navigate();
      } catch {
        state.current.leaving = false;
        setSaving(false);
        window.dispatchEvent(new CustomEvent("official-mail-draft-error"));
      }
    };
    const click = (event: MouseEvent) => {
      if (event.defaultPrevented || event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
      const anchor = event.target instanceof Element ? event.target.closest<HTMLAnchorElement>("a[href]") : null;
      if (!anchor || anchor.download || (anchor.target && anchor.target !== "_self")) return;
      const url = new URL(anchor.href, location.href);
      if (url.protocol !== "http:" && url.protocol !== "https:") return;
      if (url.pathname === location.pathname && url.search === location.search) return;
      const draft = capture();
      if (!draft) return;
      event.preventDefault();
      event.stopImmediatePropagation();
      void saveAndLeave(draft, () => {
        if (url.origin === location.origin) router.push(`${url.pathname}${url.search}${url.hash}`);
        else location.assign(url.href);
      });
    };
    const submit = (event: SubmitEvent) => {
      const target = event.target;
      if (!(target instanceof HTMLFormElement) || target.dataset.mailCompose === "true") return;
      const draft = capture();
      if (!draft) return;
      event.preventDefault();
      event.stopImmediatePropagation();
      const submitter = event.submitter as HTMLButtonElement | HTMLInputElement | null;
      void saveAndLeave(draft, () => target.requestSubmit(submitter));
    };
    window.addEventListener("beforeunload", unload);
    window.addEventListener("pagehide", unload);
    window.addEventListener("popstate", abandon);
    document.addEventListener("click", click, true);
    document.addEventListener("submit", submit, true);
    const input = () => queueMicrotask(() => refreshMemory());
    document.addEventListener("input", input, true);
    return () => {
      // Programmatic session redirects may not emit pagehide or a link click.
      // Keep an in-memory snapshot until this actual compose unmount.
      abandon();
      controller.abort();
      window.removeEventListener("beforeunload", unload);
      window.removeEventListener("pagehide", unload);
      window.removeEventListener("popstate", abandon);
      document.removeEventListener("click", click, true);
      document.removeEventListener("submit", submit, true);
      document.removeEventListener("input", input, true);
    };
    // Mount identifies this compose session; live fields are read by capture.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [mailbox, router]);

  const saveForSessionExit = async () => {
    if (state.current.dismissed) return;
    const draft = buildDraft(snapshot() ?? state.current.latest);
    if (!draft) return;
    await storeExitDraft(draft).catch(() => undefined);
    await submitExitDraft(draft).catch(() => undefined);
    state.current.dismissed = true;
  };
  return { saving, dismiss: () => { state.current.dismissed = true; }, saveForSessionExit };
}
