import React from "react";

/**
 * Shared anchored positioning for the portalled overlays (Popover, Menu, Tooltip).
 *
 * Overlays portal to the document and position in viewport coordinates, because
 * a container that scrolls on one axis clips both — a row menu, a cell tooltip or
 * a popover opened from inside a scrolling table or sidebar would otherwise be
 * cut off, and removing the container's overflow is not a fix.
 *
 * Two details that are easy to get wrong:
 * - The trigger, not its wrapper, is measured. A wrapper is `display: inline-flex`
 *   and static, so an absolutely-positioned trigger inside it (a chart bar, a
 *   pinned badge) does not sit where the wrapper does — measuring the wrapper puts
 *   every overlay in the same wrong place.
 * - Position is recomputed on scroll and resize rather than closing the overlay,
 *   flips to the other side when there is no room, and clamps to the viewport.
 */
export function useAnchoredPosition(open, anchorRef, panelRef, side = "bottom", align = "center") {
  const [pos, setPos] = React.useState({ top: -9999, left: -9999 });
  const place = React.useCallback(() => {
    const host = anchorRef.current;
    const panel = panelRef.current;
    if (!host || !panel) return;
    const anchor = host.firstElementChild || host;
    const a = anchor.getBoundingClientRect();
    const p = panel.getBoundingClientRect();
    const m = 8;
    const vw = window.innerWidth;
    const vh = window.innerHeight;
    let top;
    let left;
    if (side === "left" || side === "right") {
      left = side === "right" ? a.right + 6 : a.left - 6 - p.width;
      if (side === "right" && left + p.width > vw - m && a.left - 6 - p.width > m) left = a.left - 6 - p.width;
      if (side === "left" && left < m && a.right + 6 + p.width < vw - m) left = a.right + 6;
      top = a.top + (a.height - p.height) / 2;
    } else {
      const below = a.bottom + 4;
      const above = a.top - 4 - p.height;
      top = side === "top" ? above : below;
      if (side !== "top" && below + p.height > vh - m && above > m) top = above;
      if (side === "top" && above < m && below + p.height < vh - m) top = below;
      left = align === "end" ? a.right - p.width : align === "start" ? a.left : a.left + (a.width - p.width) / 2;
    }
    left = Math.min(Math.max(m, left), Math.max(m, vw - m - p.width));
    top = Math.min(Math.max(m, top), Math.max(m, vh - m - p.height));
    setPos({ top: top, left: left });
  }, [anchorRef, panelRef, side, align]);
  React.useLayoutEffect(() => { if (open) place(); }, [open, place]);
  React.useEffect(() => {
    if (!open) return undefined;
    const on = () => place();
    window.addEventListener("scroll", on, true);
    window.addEventListener("resize", on);
    return () => { window.removeEventListener("scroll", on, true); window.removeEventListener("resize", on); };
  }, [open, place]);
  return pos;
}

/* Portal to the document body. Falls back to rendering in place when ReactDOM is
   not on the page — the panel is `position: fixed` in viewport coordinates either
   way, so it still lands correctly unless an ancestor is transformed. */
export function portalToBody(node) {
  const dom = typeof ReactDOM !== "undefined" ? ReactDOM : typeof window !== "undefined" ? window.ReactDOM : null;
  return dom && dom.createPortal ? dom.createPortal(node, document.body) : node;
}

/* Closing on an outside click has to treat the portalled panel as inside. */
export function useDismiss(open, close, refs) {
  React.useEffect(() => {
    if (!open) return undefined;
    const away = (event) => {
      if (refs.some((ref) => ref.current && ref.current.contains(event.target))) return;
      close();
    };
    const onKey = (event) => { if (event.key === "Escape") close(); };
    document.addEventListener("mousedown", away);
    document.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("mousedown", away); document.removeEventListener("keydown", onKey); };
  });
}

export function Popover({ open, defaultOpen = false, onOpenChange, className = "", children }) {
  const [internal, setInternal] = React.useState(defaultOpen);
  const isOpen = open !== undefined ? open : internal;
  const anchorRef = React.useRef(null);
  const panelRef = React.useRef(null);
  const set = (v) => { if (open === undefined) setInternal(v); if (onOpenChange) onOpenChange(v); };
  useDismiss(isOpen, () => set(false), [anchorRef, panelRef]);
  const ctx = { open: isOpen, setOpen: set, anchorRef: anchorRef, panelRef: panelRef };
  return (
    <span ref={anchorRef} className={className} style={{ display: "inline-flex" }} data-slot="popover">
      {React.Children.map(children, (child) => (React.isValidElement(child) ? React.cloneElement(child, { __pop: ctx }) : child))}
    </span>
  );
}
export function PopoverTrigger({ __pop, children }) {
  return React.cloneElement(children, { onClick: () => __pop && __pop.setOpen(!__pop.open), "aria-expanded": __pop ? __pop.open : undefined });
}
export function PopoverPopup({ __pop, side = "bottom", align = "center", className = "", style, children, ...props }) {
  const ref = __pop ? __pop.panelRef : React.useRef(null);
  const anchorRef = __pop ? __pop.anchorRef : React.useRef(null);
  const isOpen = !!(__pop && __pop.open);
  const pos = useAnchoredPosition(isOpen, anchorRef, ref, side, align);
  if (!isOpen) return null;
  return portalToBody(
    <div ref={ref} className={["o-popup o-popover", className].filter(Boolean).join(" ")} data-slot="popover-popup" data-side={side}
      style={Object.assign({ position: "fixed", top: pos.top, left: pos.left, zIndex: 60, minWidth: "14rem" }, style)} {...props}>{children}</div>
  );
}
export function PopoverTitle({ className = "", children, ...props }) {
  return <div className={["o-popover__title", className].filter(Boolean).join(" ")} data-slot="popover-title" {...props}>{children}</div>;
}
export function PopoverDescription({ className = "", children, ...props }) {
  return <p className={["o-popover__description", className].filter(Boolean).join(" ")} data-slot="popover-description" {...props}>{children}</p>;
}
export function PopoverClose({ __pop, children }) {
  return React.cloneElement(children, { onClick: () => __pop && __pop.setOpen(false) });
}
