"use client";

import cx from "classnames";
import React, { useEffect, useMemo } from "react";
import ConditionalWrapper from "../../utils/ConditionalWrapper";
import { useStatic } from "../../utils/hooks";
import { Button } from "../Button";
import { Buttons } from "../Buttons";
import ModalStatic from "./Modal.static";
import { ModalBody } from "./ModalBody";
import { ModalCloseButton } from "./ModalCloseButton";
import { ModalTitle } from "./ModalTitle";

const CLASS_ROOT = "modal";

export const sizes = ["small", "large"] as const;

export type ModalSize = (typeof sizes)[number];

interface ModalProps extends React.HTMLAttributes<HTMLDivElement> {
  /** Action buttons in footer */
  actions?: React.ReactNode[];
  /** Full height on XS */
  fullHeight?: boolean;
  /** Sticky footer is visible when modal content is too long */
  hasStickyFooter?: boolean;
  /** Html id attribute. */
  id: string;
  /** isActive controls if modal is opened or closed */
  isActive?: boolean;
  /** Callback fired when modal is closed */
  onHide?: () => void;
  /** Custom header renderer. Receives id as function param. Returned element(s) must contain a header with close button. */
  renderHeader?: (id: string) => React.ReactNode;
  /** Custom body renderer. Receives title as function param. Returned element(s) must contain a title. */
  renderBody?: () => React.ReactNode;
  /** Custom footer renderer. Receives actions as function param. Returned element(s) must contain the actions. */
  renderFooter?: () => React.ReactNode;
  /** Custom main title renderer. Receives id as function param. Returned element must be a heading and use this id for accessibility purposes */
  renderTitle?: (id: string) => React.ReactNode;
  /** Modal dialog size */
  size?: ModalSize;
  /** Main title */
  title?: string;
  /** Child elements */
  children?: React.ReactNode;
  /** Additional CSS classes */
  className?: string;
  /** Disables header paddings */
  disableHeaderSpacing?: boolean;
  /** Disables footer paddings */
  disableFooterSpacing?: boolean;
}

const defaultProps = {
  actions: [
    <Button key="1" data-a11y-dialog-hide>
      Zatvoriť
    </Button>,
  ],
};

const Modal: React.FC<ModalProps> = ({
  actions = defaultProps.actions,
  className,
  children,
  hasStickyFooter,
  id,
  isActive,
  onHide,
  size,
  title,
  renderHeader,
  renderBody,
  renderTitle,
  renderFooter,
  fullHeight,
  disableHeaderSpacing,
  disableFooterSpacing,
  ...other
}) => {
  // In React controlled mode (using isActive), we skip data-modal
  // so vanilla JS won't initialize and move modal to #root-modals
  // onHide alone works in uncontrolled mode - it's just a callback
  const isReactControlled = isActive !== undefined;

  const modalConfig = useMemo(
    () => (isReactControlled ? { disablePortal: true } : {}),
    [isReactControlled],
  );
  const [modalRef, instance] = useStatic(ModalStatic, modalConfig);

  useEffect(() => {
    if (!instance.current || !onHide) return;

    const handleHide = () => {
      onHide();
    };

    instance.current.instance.on("hide", handleHide);

    return () => {
      instance.current?.instance.off("hide", handleHide);
    };
  }, [instance, onHide]);

  useEffect(() => {
    if (!instance.current) return;

    if (isActive) {
      instance.current.show();
    } else if (isActive === false) {
      instance.current.hide();
    }
  }, [instance, isActive]);

  const dialogClasses = cx(
    `${CLASS_ROOT}__dialog`,
    {
      [`${CLASS_ROOT}__dialog--${size}`]: size,
      [`${CLASS_ROOT}__dialog--full-height`]: fullHeight,
    },
    className,
  );

  const titleId = `${id}-title`;

  const dialogTitle = renderTitle ? (
    renderTitle(id)
  ) : (
    <ModalTitle id={titleId} size={size} data-a11y-dialog-initial-focus>
      {title}
    </ModalTitle>
  );

  return (
    <div
      id={id}
      {...(!isReactControlled && { "data-modal": true })}
      className={CLASS_ROOT}
      ref={modalRef}
      role="dialog"
      aria-hidden="true"
      aria-modal="true"
      aria-labelledby={titleId}
    >
      <div className="modal__overlay" tabIndex={-1} data-a11y-dialog-hide />
      <div className={dialogClasses} role="document" {...other}>
        <div
          className={cx(`${CLASS_ROOT}__header`, {
            [`${CLASS_ROOT}__header--no-spacing`]: disableHeaderSpacing,
          })}
        >
          {renderHeader ? (
            renderHeader(id)
          ) : (
            <>
              <ModalCloseButton />
              {dialogTitle}
            </>
          )}
        </div>
        {renderBody ? (
          renderBody()
        ) : (
          <ModalBody withTopPadding={!!renderHeader}>{children}</ModalBody>
        )}
        <div
          className={cx(`${CLASS_ROOT}__footer`, {
            [`${CLASS_ROOT}__footer--sticky`]: hasStickyFooter,
            [`${CLASS_ROOT}__footer--no-spacing`]: disableFooterSpacing,
          })}
        >
          {renderFooter ? (
            renderFooter()
          ) : (
            <ConditionalWrapper
              condition={actions.length > 1}
              wrapper={<Buttons />}
            >
              {actions.map((action) => action)}
            </ConditionalWrapper>
          )}
        </div>
      </div>
    </div>
  );
};

Modal.displayName = "Modal";

export { Modal };
