import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
import { useNavigate } from "react-router-dom";
import Button from "../../../components/Button";
import DismissibleAlert from "../../../components/DismissibleAlert";

type NavigationAction = () => void;

type UnsavedChangesRegistration = {
  title?: string;
  message?: string;
  onSave: () => boolean | Promise<boolean>;
  onDiscard?: () => void;
};

type UnsavedChangesContextValue = {
  hasUnsavedChanges: boolean;
  registerUnsavedChanges: (registration: UnsavedChangesRegistration | null) => () => void;
  requestNavigation: (action: NavigationAction) => void;
};

const UnsavedChangesContext = createContext<UnsavedChangesContextValue | null>(null);

export function CampaignUnsavedChangesProvider({ children }: { children: ReactNode }) {
  const navigate = useNavigate();
  const [registration, setRegistration] = useState<UnsavedChangesRegistration | null>(null);
  const [pendingAction, setPendingAction] = useState<NavigationAction | null>(null);
  const [saving, setSaving] = useState(false);
  const [saveError, setSaveError] = useState("");
  const registrationRef = useRef<UnsavedChangesRegistration | null>(null);

  useEffect(() => {
    registrationRef.current = registration;
  }, [registration]);

  const hasUnsavedChanges = Boolean(registration);

  const registerUnsavedChanges = useCallback((next: UnsavedChangesRegistration | null) => {
    setRegistration(next);
    return () => {
      setRegistration((current) => current === next ? null : current);
    };
  }, []);

  const proceed = useCallback((action: NavigationAction) => {
    setPendingAction(null);
    setSaveError("");
    action();
  }, []);

  const requestNavigation = useCallback((action: NavigationAction) => {
    const active = registrationRef.current;
    if (!active) {
      action();
      return;
    }
    setSaveError("");
    setPendingAction(() => action);
  }, []);

  useEffect(() => {
    function onBeforeUnload(event: BeforeUnloadEvent) {
      if (!registrationRef.current) return;
      event.preventDefault();
      event.returnValue = "";
    }

    window.addEventListener("beforeunload", onBeforeUnload);
    return () => window.removeEventListener("beforeunload", onBeforeUnload);
  }, []);

  useEffect(() => {
    function onDocumentClick(event: MouseEvent) {
      if (!registrationRef.current) return;
      if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) return;

      const target = event.target as Element | null;
      const anchor = target?.closest?.("a[href]") as HTMLAnchorElement | null;
      if (!anchor) return;
      if (anchor.target && anchor.target !== "_self") return;
      if (anchor.hasAttribute("download")) return;
      if (anchor.getAttribute("href")?.startsWith("#")) return;

      const destination = new URL(anchor.href, window.location.href);
      const current = new URL(window.location.href);
      if (destination.href === current.href) return;

      event.preventDefault();
      event.stopPropagation();

      requestNavigation(() => {
        if (destination.origin === current.origin) {
          navigate(`${destination.pathname}${destination.search}${destination.hash}`);
        } else {
          window.location.assign(destination.href);
        }
      });
    }

    document.addEventListener("click", onDocumentClick, true);
    return () => document.removeEventListener("click", onDocumentClick, true);
  }, [navigate, requestNavigation]);

  async function handleSaveAndLeave() {
    const action = pendingAction;
    const active = registrationRef.current;
    if (!action || !active) return;

    setSaving(true);
    setSaveError("");
    try {
      const ok = await active.onSave();
      if (!ok) {
        setSaveError("The changes could not be saved. Please review the page message and try again.");
        return;
      }
      proceed(action);
    } catch (err) {
      setSaveError(err instanceof Error ? err.message : String(err));
    } finally {
      setSaving(false);
    }
  }

  function handleDiscardAndLeave() {
    const action = pendingAction;
    const active = registrationRef.current;
    if (!action) return;
    active?.onDiscard?.();
    proceed(action);
  }

  const value = useMemo<UnsavedChangesContextValue>(() => ({
    hasUnsavedChanges,
    registerUnsavedChanges,
    requestNavigation
  }), [hasUnsavedChanges, registerUnsavedChanges, requestNavigation]);

  return (
    <UnsavedChangesContext.Provider value={value}>
      {children}
      {pendingAction && registration && (
        <div className="overlay-backdrop" role="dialog" aria-modal="true">
          <div className="modal-panel unsaved-changes-dialog">
            <header className="modal-header">
              <h2>{registration.title ?? "Unsaved campaign changes"}</h2>
              <button className="modal-close" onClick={() => setPendingAction(null)} disabled={saving}>×</button>
            </header>
            <div className="modal-body">
              <p>{registration.message ?? "This campaign page has unsaved changes. Save them before leaving, or discard the changes and continue."}</p>
              {saveError && <DismissibleAlert tone="danger" resetKey={saveError}>{saveError}</DismissibleAlert>}
            </div>
            <footer className="modal-footer unsaved-changes-actions">
              <Button onClick={() => setPendingAction(null)} disabled={saving}>Cancel</Button>
              <Button onClick={handleDiscardAndLeave} disabled={saving}>Discard</Button>
              <Button variant="primary" onClick={handleSaveAndLeave} disabled={saving}>{saving ? "Saving…" : "Save and leave"}</Button>
            </footer>
          </div>
        </div>
      )}
    </UnsavedChangesContext.Provider>
  );
}

const fallbackUnsavedChangesContext: UnsavedChangesContextValue = {
  hasUnsavedChanges: false,
  registerUnsavedChanges: () => () => undefined,
  requestNavigation: (action) => action()
};

export function useCampaignUnsavedChanges() {
  return useContext(UnsavedChangesContext) ?? fallbackUnsavedChangesContext;
}

export function useRegisterCampaignUnsavedChanges(registration: UnsavedChangesRegistration | null) {
  const { registerUnsavedChanges } = useCampaignUnsavedChanges();

  useEffect(() => {
    return registerUnsavedChanges(registration);
  }, [registerUnsavedChanges, registration]);
}
