"use client";

import * as React from "react";
import { useRouter } from "next/navigation";
import { TriangleAlertIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";

import { Modal } from "./modal";

/* ------------------------------------- Dialog ------------------------------------- */

/**
 * "Leave without saving?" Stay is the safe default (it has focus, and Escape or a backdrop click also stays).
 * Discard leaves and loses the edits; Save and leave saves first and only leaves if the save succeeds.
 */
export function UnsavedChangesDialog({
  open,
  changes = [],
  saving = false,
  saveError,
  onStay,
  onDiscard,
  onSave,
}: {
  open: boolean;
  /** Human labels of what changed, e.g. ["Phone", "Credit limit"]. */
  changes?: string[];
  saving?: boolean;
  saveError?: string;
  onStay: () => void;
  onDiscard: () => void;
  /** Omit when the form cannot be saved from here (e.g. it has errors); the button is then hidden. */
  onSave?: () => void;
}) {
  return (
    <Modal
      open={open}
      onOpenChange={(o) => !o && !saving && onStay()}
      size="sm"
      icon={TriangleAlertIcon}
      title="Leave without saving?"
      description={
        changes.length
          ? `You changed ${changes.length === 1 ? changes[0] : `${changes.slice(0, -1).join(", ")} and ${changes[changes.length - 1]}`}. If you leave now, these changes are lost.`
          : "You have changes that are not saved. If you leave now, they are lost."
      }
      dismissible={!saving}
      footer={
        <>
          <Button variant="outline" onClick={onStay} disabled={saving} autoFocus>
            Stay
          </Button>
          <Button variant="destructive" onClick={onDiscard} disabled={saving}>
            Discard changes
          </Button>
          {onSave ? (
            <Button onClick={onSave} disabled={saving}>
              {saving ? <Spinner /> : null}
              {saving ? "Saving" : "Save and leave"}
            </Button>
          ) : null}
        </>
      }
    >
      {saveError ? (
        <p role="alert" className="rounded-lg border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text">
          {saveError}
        </p>
      ) : null}
    </Modal>
  );
}

/* -------------------------------------- Hook -------------------------------------- */

/**
 * Guards a form with unsaved edits.
 * - In-app links: clicks on same-origin <a href> (including Next <Link>) are caught while `dirty` and confirmed
 *   first. Mark an area with `data-unsaved-ignore` to let its links through.
 * - Reload, tab close, typed URLs: the browser's own "Leave site?" prompt (its wording cannot be customised).
 * - Anything else (Cancel, switching tabs, closing a drawer): wrap it in `guard(fn)`.
 * - Not covered: the browser Back and Forward buttons, which the App Router gives no hook for.
 * `save` should resolve true on success; false (or a throw) keeps the user on the page with the error shown.
 */
export function useUnsavedChangesGuard({
  dirty,
  changes,
  save,
  discard,
}: {
  dirty: boolean;
  changes?: string[];
  save?: () => Promise<boolean>;
  /** Resets the form to its saved values. Called on Discard before the action runs, so in-page actions (tabs) do not keep stale edits. */
  discard?: () => void;
}) {
  const router = useRouter();
  const [pending, setPending] = React.useState<(() => void) | null>(null);
  const [saving, setSaving] = React.useState(false);
  const [saveError, setSaveError] = React.useState<string>();

  React.useEffect(() => {
    if (!dirty) return;
    const onBeforeUnload = (e: BeforeUnloadEvent) => {
      e.preventDefault();
      e.returnValue = ""; // required by some browsers to show the prompt
    };
    window.addEventListener("beforeunload", onBeforeUnload);
    return () => window.removeEventListener("beforeunload", onBeforeUnload);
  }, [dirty]);

  React.useEffect(() => {
    if (!dirty) return;
    const onClick = (e: MouseEvent) => {
      if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
      const a = (e.target as Element | null)?.closest?.("a[href]") as HTMLAnchorElement | null;
      if (!a || a.target === "_blank" || a.hasAttribute("download") || a.closest("[data-unsaved-ignore]")) return;
      const url = new URL(a.href, window.location.href);
      if (url.origin !== window.location.origin) return; // external: the browser's prompt handles it
      if (url.pathname === window.location.pathname && url.search === window.location.search && url.hash) return; // same-page anchor
      // Capture phase: stop the click before Next's <Link> handler navigates.
      e.preventDefault();
      e.stopPropagation();
      setSaveError(undefined);
      setPending(() => () => router.push(url.pathname + url.search + url.hash));
    };
    document.addEventListener("click", onClick, true);
    return () => document.removeEventListener("click", onClick, true);
  }, [dirty, router]);

  const guard = React.useCallback(
    (fn: () => void) => {
      if (!dirty) return fn();
      setSaveError(undefined);
      setPending(() => fn);
    },
    [dirty],
  );

  const proceed = () => {
    const fn = pending;
    setPending(null);
    fn?.();
  };

  const dialog = (
    <UnsavedChangesDialog
      open={!!pending}
      changes={changes}
      saving={saving}
      saveError={saveError}
      onStay={() => setPending(null)}
      onDiscard={() => {
        discard?.();
        proceed();
      }}
      onSave={
        save
          ? async () => {
              setSaving(true);
              setSaveError(undefined);
              let ok = false;
              try {
                ok = await save();
              } catch {
                ok = false;
              }
              setSaving(false);
              if (ok) proceed();
              else setSaveError("The changes could not be saved. Stay to fix them, or discard them.");
            }
          : undefined
      }
    />
  );

  return { guard, dialog };
}
