import clsx from "clsx";
import { useEffect, useRef, type HTMLAttributes, type MouseEvent } from "react";

import styles from "./Dialog.module.css";

/**
 * A generic dialog component to display content in a dialog (non-blocking) or
 * a modal (blocking) way. This has built-in functionality like backdrop
 * clicking and escape key handling. It also accepts a trigger element which
 * will open the dialog when clicked. This is to allow focus to be returned to
 * the trigger when the element is closed (for accessibility).
 */

export interface DialogProps extends HTMLAttributes<HTMLDialogElement> {
  open: boolean;
  onClose: (e?: MouseEvent) => void;
  modal?: boolean;
  onBackdropClick?: () => void;
}

export const Dialog: React.FC<DialogProps> = ({
  open,
  onClose,
  modal,
  children,
  className,
  onBackdropClick,
  ...props
}) => {
  const dialogRef = useRef<HTMLDialogElement>(null);
  const triggerRef = useRef<Element | null>(null);

  const handleClose = () => {
    onClose();
    dialogRef.current?.close();
    // Returns focus to trigger after close for accessibility
    (triggerRef.current as HTMLButtonElement)?.focus();
  };

  const handleClickBackdrop = (e?: MouseEvent) => {
    if (!e) return;

    // Keyboard-triggered clicks (e.g. pressing Enter on a button) report
    // clientX/clientY as 0, which falls outside the dialog rect and would
    // incorrectly be treated as a backdrop click.
    // https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail
    if (e.detail === 0) return;

    const rect = dialogRef.current?.getBoundingClientRect();
    const isClickInDialog =
      rect &&
      rect.top <= e.clientY &&
      e.clientY <= rect.top + rect.height &&
      rect.left <= e.clientX &&
      e.clientX <= rect.left + rect.width;

    if (!isClickInDialog) {
      // Stop the click from bubbling to a parent dialog's backdrop handler,
      // so only the innermost dialog closes when its backdrop is clicked.
      e.stopPropagation();
      onBackdropClick?.();
      handleClose();
    }
  };

  useEffect(
    () => {
      // Get the element that was clicked to open the dialog
      triggerRef.current = document.activeElement;

      if (modal && open) dialogRef.current?.showModal();
      if (open) dialogRef.current?.focus();

      if (!open) {
        handleClose();
      }
    },
    //eslint-disable-next-line react-hooks/exhaustive-deps
    [dialogRef.current, open, modal],
  );

  useEffect(
    () => {
      if (modal) {
        // The native cancel event is only fired for modal dialogs opened via
        // showModal(). The browser adds its own Escape key listener and ensures
        // the cancel event only fires on the topmost modal, so stacked dialogs
        // close one at a time.
        const dialog = dialogRef.current;
        const handleCancel = (e: Event) => {
          e.preventDefault();
          handleClose();
        };
        dialog?.addEventListener("cancel", handleCancel);
        return () => dialog?.removeEventListener("cancel", handleCancel);
      } else {
        // Non-modal dialogs don't receive the cancel event, so we fall back to
        // a document-level keydown listener.
        const handleEsc = (e: KeyboardEvent) => {
          if (e.key === "Escape" && open) handleClose();
        };
        document.addEventListener("keydown", handleEsc);
        return () => document.removeEventListener("keydown", handleEsc);
      }
    },
    //eslint-disable-next-line react-hooks/exhaustive-deps
    [modal, open],
  );

  return (
    <dialog
      className={clsx([styles.dialog, className])}
      ref={dialogRef}
      onClick={modal ? handleClickBackdrop : undefined}
      open={modal ? undefined : open}
      aria-modal={modal}
      {...props}
    >
      {children}
    </dialog>
  );
};
