"use client";

import { X } from "lucide-react";
import { useCallback, useEffect, useRef, type ReactNode } from "react";
import { createPortal } from "react-dom";

import { cn } from "@/lib/utils";

/**
 * Accessible modal: focus trap, Escape to close, scroll lock, portal to
 * <body>, and click-outside dismissal.
 */
export function Modal({
  open,
  onClose,
  title,
  description,
  ariaLabel,
  children,
  size = "md",
  className,
  bodyClassName,
}: {
  open: boolean;
  onClose: () => void;
  title?: ReactNode;
  description?: ReactNode;
  /**
   * Accessible name for dialogs that supply no `title`, and so render no header
   * of their own. Falls back to the title when it is plain text.
   */
  ariaLabel?: string;
  children: ReactNode;
  size?: "sm" | "md" | "lg";
  className?: string;
  /**
   * Extra classes for the scroll container around `children`.
   *
   * The default scrolls the whole body, which is right for a short form. A
   * two-pane dialog — the TV download modal, with a fixed season rail beside a
   * long episode list — needs the opposite: the container must not scroll, so
   * each pane owns its own overflow. Passing `overflow-hidden p-0` here does that
   * without changing the primitive's behaviour for anyone else.
   */
  bodyClassName?: string;
}) {
  const panelRef = useRef<HTMLDivElement>(null);
  const previouslyFocused = useRef<HTMLElement | null>(null);

  const handleKeyDown = useCallback(
    (event: KeyboardEvent) => {
      if (event.key === "Escape") {
        event.stopPropagation();
        onClose();
        return;
      }
      if (event.key !== "Tab") return;

      const focusables = panelRef.current?.querySelectorAll<HTMLElement>(
        'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])',
      );
      if (!focusables?.length) return;

      const first = focusables[0]!;
      const last = focusables[focusables.length - 1]!;
      if (event.shiftKey && document.activeElement === first) {
        event.preventDefault();
        last.focus();
      } else if (!event.shiftKey && document.activeElement === last) {
        event.preventDefault();
        first.focus();
      }
    },
    [onClose],
  );

  useEffect(() => {
    if (!open) return;

    previouslyFocused.current = document.activeElement as HTMLElement | null;
    const { overflow, paddingRight } = document.body.style;
    const scrollbar = window.innerWidth - document.documentElement.clientWidth;
    document.body.style.overflow = "hidden";
    if (scrollbar > 0) document.body.style.paddingRight = `${scrollbar}px`;
    document.addEventListener("keydown", handleKeyDown);

    const t = window.setTimeout(() => {
      panelRef.current
        ?.querySelector<HTMLElement>('[data-autofocus], button, a[href], input')
        ?.focus();
    }, 40);

    return () => {
      document.removeEventListener("keydown", handleKeyDown);
      document.body.style.overflow = overflow;
      document.body.style.paddingRight = paddingRight;
      window.clearTimeout(t);
      previouslyFocused.current?.focus?.();
    };
  }, [handleKeyDown, open]);

  if (!open || typeof document === "undefined") return null;

  return createPortal(
    <div className="fixed inset-0 z-[1000] flex items-end justify-center sm:items-center">
      <button
        type="button"
        aria-label="Close dialog"
        onClick={onClose}
        className="absolute inset-0 cursor-default bg-black/72 backdrop-blur-sm animate-fade-in"
      />
      <div
        ref={panelRef}
        role="dialog"
        aria-modal="true"
        aria-label={typeof title === "string" ? title : (ariaLabel ?? "Dialog")}
        className={cn(
          "relative z-10 w-full animate-pop overflow-hidden rounded-t-xl border border-line bg-surface shadow-[0_40px_120px_-20px_rgba(0,0,0,0.9)] sm:rounded-xl",
          size === "sm" && "sm:max-w-sm",
          size === "md" && "sm:max-w-lg",
          size === "lg" && "sm:max-w-2xl",
          className,
        )}
      >
        {title ? (
          <header className="flex items-start justify-between gap-4 border-b border-line px-5 py-4">
            <div>
              <h2 className="text-lg font-bold text-ink">{title}</h2>
              {description ? (
                <p className="mt-0.5 text-[13px] text-ink-muted">{description}</p>
              ) : null}
            </div>
            <button
              type="button"
              onClick={onClose}
              aria-label="Close"
              className="-mr-1 -mt-1 grid h-9 w-9 place-items-center rounded-full text-ink-muted transition hover:bg-white/8 hover:text-ink"
            >
              <X className="h-5 w-5" />
            </button>
          </header>
        ) : null}
        <div className={cn("max-h-[70vh] overflow-y-auto px-5 py-4", bodyClassName)}>
          {children}
        </div>
      </div>
    </div>,
    document.body,
  );
}
