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

export type UnsavedNavigationAction = () => void;

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

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

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

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

export function UnsavedChangesProvider({ children }: {children: ReactNode;}) {
  const navigate = useNavigate();
  const [registrations, setRegistrations] = useState<Array<{id: number;registration: UnsavedChangesRegistration;}>>([]);
  const [pendingAction, setPendingAction] = useState<UnsavedNavigationAction | null>(null);
  const [saving, setSaving] = useState(false);
  const [saveError, setSaveError] = useState("");
  const registrationRef = useRef<UnsavedChangesRegistration | null>(null);
  const nextRegistrationId = useRef(1);
  const registration = registrations.length ? registrations[registrations.length - 1].registration : null;

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

  const hasUnsavedChanges = Boolean(registration);

  const registerUnsavedChanges = useCallback((next: UnsavedChangesRegistration | null) => {
    if (!next) return () => undefined;
    const id = nextRegistrationId.current;
    nextRegistrationId.current += 1;
    setRegistrations((current) => [...current, { id, registration: next }]);
    return () => {
      setRegistrations((current) => current.filter((item) => item.id !== id));
    };
  }, []);

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

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

  useEffect(() => {
    function onBeforeUnload(event: BeforeUnloadEvent) {
      const active = registrationRef.current;
      if (!active) return;
      const message = active.message || "i18n:govoplan-core.this_page_has_unsaved_changes_save_them_before_l.419a9d8b";
      event.preventDefault();
      event.returnValue = message;
      return message;
    }

    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("i18n:govoplan-core.the_changes_could_not_be_saved_please_review_the.4615a3c6");
        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 &&
      <Dialog
        open
        role="alertdialog"
        title={registration.title ?? "i18n:govoplan-core.unsaved_changes.29267269"}
        className="unsaved-changes-dialog"
        footerClassName="unsaved-changes-actions"
        closeOnBackdrop={!saving}
        closeDisabled={saving}
        onClose={() => setPendingAction(null)}
        footer={
        <>
              <Button onClick={() => setPendingAction(null)} disabled={saving}>i18n:govoplan-core.cancel.77dfd213</Button>
              <Button onClick={handleDiscardAndLeave} disabled={saving}>i18n:govoplan-core.discard.36fff63c</Button>
              <Button variant="primary" onClick={() => void handleSaveAndLeave()} disabled={saving}>{saving ? "i18n:govoplan-core.saving.ae7e8875" : "i18n:govoplan-core.save_and_leave.0507824a"}</Button>
            </>
        }>
        
          <p>{registration.message ?? "i18n:govoplan-core.this_page_has_unsaved_changes_save_them_before_l.419a9d8b"}</p>
          {saveError && <DismissibleAlert tone="danger" resetKey={saveError}>{saveError}</DismissibleAlert>}
        </Dialog>
      }
    </UnsavedChangesContext.Provider>);

}

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

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

export function useRegisterUnsavedChanges(registration: UnsavedChangesRegistration | null) {
  const { registerUnsavedChanges } = useUnsavedChanges();

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

export function useUnsavedDraftGuard({
  dirty,
  title = "i18n:govoplan-core.unsaved_changes.29267269",
  message = "i18n:govoplan-core.this_page_has_unsaved_changes_save_them_before_l.419a9d8b",
  onSave,
  onDiscard,
  enabled = true
}: UnsavedDraftGuardOptions) {
  const saveRef = useRef(onSave);
  const discardRef = useRef(onDiscard);

  useEffect(() => {
    saveRef.current = onSave;
    discardRef.current = onDiscard;
  }, [onDiscard, onSave]);

  const registration = useMemo<UnsavedChangesRegistration | null>(() => enabled && dirty ? {
    title,
    message,
    onSave: () => saveRef.current(),
    onDiscard: () => discardRef.current?.()
  } : null, [dirty, enabled, message, title]);

  useRegisterUnsavedChanges(registration);
}

export function useGuardedNavigate(): NavigateFunction {
  const navigate = useNavigate();
  const { requestNavigation } = useUnsavedChanges();

  return useCallback(((to: To | number, options?: NavigateOptions) => {
    requestNavigation(() => {
      if (typeof to === "number") {
        navigate(to);
      } else {
        navigate(to, options);
      }
    });
  }) as NavigateFunction, [navigate, requestNavigation]);
}
