import { cross } from "@simplybusiness/icons";
import classNames from "classnames/dedupe";
import type { ReactElement, ReactNode, RefAttributes } from "react";
import { cloneElement, useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { useWindowEvent } from "@simplybusiness/mobius-hooks";
import type { DOMProps } from "../../types";
import { Button } from "../Button";
import { Icon } from "../Icon";
import { Arrow } from "./Arrow";
import { useAutoUpdate } from "./useAutoUpdate";
import { useFloatingPosition } from "./useFloatingPosition";
import { useOutsidePress } from "./useOutsidePress";
import "./Popover.css";

export type PopoverElementType = HTMLDivElement;

export interface PopoverProps
  extends DOMProps, RefAttributes<PopoverElementType> {
  children?: ReactNode;
  trigger: ReactElement;
  /** Callback that fires each time the accordion is opened */
  onOpen?: () => void;
  /** Callback that fires each time the accordion is closed */
  onClose?: () => void;
  /** Custom class name for setting specific CSS */
  className?: string;
}

const OFFSET_FROM_CONTENT_DEFAULT = 10;
const ARROW_WIDTH = 20;

export const Popover = (props: PopoverProps) => {
  const { trigger, children, onOpen, onClose, className } = props;
  const referenceRef = useRef<HTMLElement | null>(null);
  const floatingRef = useRef<HTMLDivElement | null>(null);
  const arrowRef = useRef<SVGSVGElement | null>(null);
  const [isOpen, setIsOpen] = useState(false);
  const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);

  const isInsideDialog =
    portalTarget !== null && portalTarget.tagName === "DIALOG";

  const { initialFloatingStyles, initialArrowStyles, update } =
    useFloatingPosition({
      referenceRef,
      floatingRef,
      arrowRef,
      isOpen,
      offsetPx: OFFSET_FROM_CONTENT_DEFAULT,
      arrowWidth: ARROW_WIDTH,
      useFixedStrategy: isInsideDialog,
    });

  useAutoUpdate({
    referenceRef,
    floatingRef,
    onUpdate: update,
    enabled: isOpen,
  });

  useOutsidePress({
    referenceRef,
    floatingRef,
    enabled: isOpen,
    onOutsidePress: () => {
      onClose?.();
      setIsOpen(false);
    },
  });

  const containerClasses = classNames(
    "mobius",
    "mobius-popover__container",
    className,
  );

  // Native listener to prevent clicks inside the popover from activating
  // interactive ancestors. Must be native because React's onClick fires
  // too late via delegation.
  useEffect(() => {
    const el = floatingRef.current;
    if (!el) return;

    const preventLabelActivation = (e: Event) => {
      const target = e.target as HTMLElement;
      // Allow default behavior for interactive elements (links, inputs, etc.)
      // so they remain functional inside the popover.
      if (!target.closest("a[href], input, select, textarea")) {
        e.preventDefault();
      }
    };
    el.addEventListener("click", preventLabelActivation);
    return () => el.removeEventListener("click", preventLabelActivation);
  }, [isOpen]);

  const toggleVisibility = () => {
    if (isOpen) {
      setIsOpen(false);
      onClose?.();
      return;
    }

    // Portal into the nearest open <dialog> ancestor so the popover renders
    // in the same top-layer as the dialog. document.body falls outside that
    // top-layer and paints beneath the modal backdrop.
    const dialog = referenceRef.current?.closest("dialog");
    setPortalTarget(dialog ?? document.body);
    setIsOpen(true);
    onOpen?.();
  };

  const setReferenceRef = useCallback((node: HTMLElement | null) => {
    referenceRef.current = node;
  }, []);

  const triggerComponent = cloneElement(trigger, {
    ref: setReferenceRef,
    className: classNames(
      (trigger.props as { className?: string }).className,
      "mobius-popover__toggle",
    ),
    onClick: toggleVisibility,
  } as Record<string, unknown>);

  useWindowEvent("keydown", e => {
    if (e.key === "Escape" && isOpen) {
      setIsOpen(false);
      onClose?.();
      e.preventDefault();
      e.stopPropagation();
    }
  });

  const floatingElement = isOpen ? (
    <div
      className={containerClasses}
      ref={floatingRef}
      style={initialFloatingStyles}
    >
      <div className="mobius-popover">
        <header className="mobius-popover__header">
          <Button
            type="button"
            className="mobius-popover__close-button"
            onClick={toggleVisibility}
            aria-label="Close"
            variant="ghost"
          >
            <Icon
              icon={cross}
              size="md"
              className="mobius-popover__close-icon"
            />
          </Button>
        </header>
        <div className="mobius-popover__body">{children}</div>
      </div>
      <Arrow
        ref={arrowRef}
        style={initialArrowStyles}
        className="mobius-popover__arrow-icon"
        width={ARROW_WIDTH}
      />
    </div>
  ) : null;

  return (
    <>
      {triggerComponent}
      {floatingElement && portalTarget
        ? createPortal(floatingElement, portalTarget)
        : null}
    </>
  );
};
