import { twJoin } from "tailwind-merge";
import React, {
  cloneElement,
  isValidElement,
  type KeyboardEvent,
  type MouseEvent,
  type ReactElement,
  type ReactNode,
  useState,
} from "react";
import {
  Dialog as MUIDialog,
  type DialogProps as MUIDialogProps,
} from "@mui/material";

import { DialogTitle } from "./DialogTitle";
import { DialogContent } from "./DialogContent";
import { DialogActions, DialogActionsProps } from "./DialogActions";
import { ScrollDetector } from "./ScrollDetector";
import { useAutomaticSizing } from "./useAutomaticSizing";

export interface DialogProps
  extends Omit<MUIDialogProps, "open" | "className" | "classes"> {
  title?: string;
  /**
   * Whether the "long dialog" styles should be enforced.
   *
   * Long dialogs have a border in the actions and padding animation on scroll.
   */
  long?: boolean;
  isOpen: boolean;
  width?: number;
  onClose: (
    e: MouseEvent | KeyboardEvent,
    reason: "backdropClick" | "escapeKeyDown" | undefined
  ) => void;
  ESCAPE_className?: string;
  ESCAPE_contentClassName?: string;
}

/**
 * Validates if the child is an instance of the DialogActions component.
 *
 * It uses the component type to validate but this validation fails on webpack.
 * As an escape hatch we added a specific dialog actions attribute.
 *
 * So if your DialogActions are not detected, please use the syntax below:
 * ```tsx
 * <DialogActions data-docoli-dialog-actions>
 *   {children}
 * </DialogActions>
 * ```
 */
function childIsDialogActions(
  child: ReactNode
): child is ReactElement<DialogActionsProps> {
  return (
    isValidElement(child) &&
    (child.type === DialogActions ||
      "data-docoli-dialog-actions" in child.props)
  );
}

export const Dialog = ({
  title,
  long: longProp = undefined,
  isOpen,
  onClose,
  children,
  width = 480,
  ESCAPE_className,
  ESCAPE_contentClassName,
  ...restProps
}: DialogProps) => {
  const [contentNode, setContentNode] = useState<HTMLElement | null>(null);
  const [contentInnerNode, setContentInnerNode] =
    useState<HTMLDivElement | null>(null);

  const [actionsCollapsed, setActionsCollapsed] = useState(false);
  const { keepActionsCollapsed, reduceScreenMargin, useLongStyles } =
    useAutomaticSizing(longProp, contentInnerNode);

  const childrenArray = React.Children.toArray(children);
  const lastChild = childrenArray.pop();

  return (
    <MUIDialog
      classes={{
        root: ESCAPE_className,
        scrollPaper: twJoin(
          "box-border flex-col",
          // ↓ This element flexibly offsets the dialog from the center slightly to the top.
          "after:content-[''] after:w-0 after:max-h-[60px] after:flex-1",
          reduceScreenMargin ? "py-32" : "py-[56px]"
        ),
      }}
      slotProps={{ backdrop: { className: "bg-N200A" } }}
      open={isOpen}
      onClose={onClose}
      PaperProps={{
        classes: {
          root: "overflow-hidden max-h-[unset] shadow-S500 bg-N0 rounded-12 m-0",
        },
        style: { width },
      }}
      {...restProps}
    >
      {title && <DialogTitle onClose={onClose}>{title}</DialogTitle>}
      <DialogContent
        ref={setContentNode}
        innerRef={setContentInnerNode}
        long={useLongStyles}
        ESCAPE_className={ESCAPE_contentClassName}
        scrollDetector={
          useLongStyles ? (
            <ScrollDetector
              container={contentNode}
              onScroll={setActionsCollapsed}
            />
          ) : undefined
        }
      >
        {childrenArray}
        {!childIsDialogActions(lastChild) && lastChild}
      </DialogContent>
      {childIsDialogActions(lastChild) &&
        cloneElement(lastChild, {
          collapsed: keepActionsCollapsed || actionsCollapsed,
          long: useLongStyles,
          ...lastChild.props,
        })}
    </MUIDialog>
  );
};
