"use client";

import {
  type CSSProperties,
  type MouseEvent,
  type ReactNode,
  useEffect,
} from "react";
import { createPortal } from "react-dom";

let bodyScrollLockDepth = 0;
let previousBodyOverflow = "";

export interface ModalPortalProps {
  open: boolean;
  onClose: () => void;
  children: ReactNode;
  scrimClassName: string;
  panelClassName: string;
  scrimOpacity: number;
  panelStyle?: CSSProperties;
  persistent?: boolean;
  role?: "dialog" | "alertdialog";
  ariaLabel?: string;
  ariaLabelledBy?: string;
  ariaDescribedBy?: string;
}

export function ModalPortal({
  open,
  onClose,
  children,
  scrimClassName,
  panelClassName,
  scrimOpacity,
  panelStyle,
  persistent = false,
  role = "dialog",
  ariaLabel,
  ariaLabelledBy,
  ariaDescribedBy,
}: ModalPortalProps) {
  useEffect(() => {
    if (!open || typeof document === "undefined") return;

    const onKey = (event: KeyboardEvent) => {
      if (event.key === "Escape" && !persistent) onClose();
    };

    document.addEventListener("keydown", onKey);
    lockBodyScroll();

    return () => {
      document.removeEventListener("keydown", onKey);
      unlockBodyScroll();
    };
  }, [open, onClose, persistent]);

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

  const handleScrimClick = (event: MouseEvent<HTMLDivElement>) => {
    if (!persistent && event.target === event.currentTarget) onClose();
  };

  return createPortal(
    <div
      className={scrimClassName}
      style={{ background: `rgba(0, 0, 0, ${scrimOpacity})` }}
      onClick={handleScrimClick}
    >
      <div
        role={role}
        aria-modal="true"
        aria-label={ariaLabel}
        aria-labelledby={ariaLabelledBy}
        aria-describedby={ariaDescribedBy}
        className={panelClassName}
        style={panelStyle}
        onClick={(event) => event.stopPropagation()}
      >
        {children}
      </div>
    </div>,
    document.body,
  );
}

function lockBodyScroll(): void {
  if (typeof document === "undefined") return;

  if (bodyScrollLockDepth === 0) {
    previousBodyOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
  }
  bodyScrollLockDepth += 1;
}

function unlockBodyScroll(): void {
  if (typeof document === "undefined") return;

  bodyScrollLockDepth = Math.max(0, bodyScrollLockDepth - 1);
  if (bodyScrollLockDepth === 0) {
    document.body.style.overflow = previousBodyOverflow;
    previousBodyOverflow = "";
  }
}
