"use client";

import { useEffect, useRef } from "react";
import { createPortal } from "react-dom";

interface Props {
  open: boolean;
  /** Called when user clicks the backdrop or presses Escape. */
  onClose: () => void;
  children: React.ReactNode;
  /**
   * ARIA label fallback when no labelled element exists inside.
   * Optional; if you have a heading inside the modal with id, set
   * `aria-labelledby` on a wrapper instead.
   */
  ariaLabel?: string;
}

/**
 * Portal-based modal.
 *
 * Why a portal
 * ------------
 * `position: fixed` becomes positioned relative to a transformed
 * ancestor's containing block, not the viewport, when ANY ancestor
 * has a `transform`, `perspective`, or `filter` applied. Our
 * `animate-in` utility sets `transform: translateY(0)` on a lot of
 * dashboard/tenant-detail containers (because of the fade-up
 * animation, which uses `animation-fill-mode: both` to keep the
 * transform on after the animation finishes). That broke modals
 * rendered as in-place children — they centred to the panel they
 * lived in, not to the page.
 *
 * Rendering at `document.body` via `createPortal` escapes every
 * containing-block ancestor and gives us true viewport coordinates.
 *
 * UX details
 * ----------
 * - Backdrop click triggers `onClose`. (Bubbling check: only fires
 *   when the click target IS the backdrop, not the panel inside.)
 * - Escape key triggers `onClose`. Standard modal expectation.
 * - `body` overflow is locked while open so background content
 *   doesn't scroll behind the modal.
 * - Renders nothing on first paint server-side, then mounts on
 *   client. `useEffect` gating ensures `document.body` is available;
 *   without it Next.js SSR would throw on `document` reference.
 */
export function Modal({ open, onClose, children, ariaLabel }: Props) {
  const closeRef = useRef(onClose);
  closeRef.current = onClose;

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

    // Lock background scroll. Restore on unmount/close.
    const previousOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";

    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") closeRef.current();
    };
    window.addEventListener("keydown", onKey);

    return () => {
      document.body.style.overflow = previousOverflow;
      window.removeEventListener("keydown", onKey);
    };
  }, [open]);

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

  return createPortal(
    <div
      role="dialog"
      aria-modal="true"
      aria-label={ariaLabel}
      className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm"
      onClick={(e) => {
        if (e.target === e.currentTarget) onClose();
      }}
    >
      <div className="bg-surface-1 border border-border rounded-xl p-6 max-w-md w-full max-h-[90vh] overflow-y-auto">
        {children}
      </div>
    </div>,
    document.body
  );
}
