import {
  useEffect,
  useId,
  useLayoutEffect,
  useRef,
  useState,
  type ButtonHTMLAttributes,
  type CSSProperties,
  type MouseEvent,
  type ReactNode,
} from 'react';
import { createPortal } from 'react-dom';

export type DetailsPopoverAlign = 'start' | 'center' | 'end';

export interface DetailsPopoverProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'title'> {
  details: string | string[];
  children: ReactNode;
  popoverLabel?: string;
  align?: DetailsPopoverAlign;
  maxWidth?: number;
}

interface DetailsPopoverPosition {
  top: number;
  left: number;
  width: number;
  arrowLeft: number;
  placement: 'top' | 'bottom';
}

const VIEWPORT_MARGIN = 12;

export function DetailsPopover({
  details,
  children,
  className,
  popoverLabel = 'Детали',
  align = 'start',
  maxWidth = 360,
  disabled,
  onClick,
  ...props
}: DetailsPopoverProps) {
  const detailsId = useId();
  const triggerRef = useRef<HTMLButtonElement>(null);
  const popoverRef = useRef<HTMLDivElement>(null);
  const [open, setOpen] = useState(false);
  const [position, setPosition] = useState<DetailsPopoverPosition>({
    top: 0,
    left: 0,
    width: Math.min(maxWidth, 360),
    arrowLeft: 24,
    placement: 'bottom',
  });
  const detailLines = Array.isArray(details)
    ? details.filter(Boolean)
    : [details].filter(Boolean);
  const classes = ['ui-details-popover-trigger', className ?? ''].filter(Boolean).join(' ');

  useEffect(() => {
    if (disabled && open) setOpen(false);
  }, [disabled, open]);

  useLayoutEffect(() => {
    if (!open) return;

    const updatePosition = () => {
      const trigger = triggerRef.current;
      if (!trigger) return;

      const rect = trigger.getBoundingClientRect();
      const width = Math.min(maxWidth, Math.max(220, window.innerWidth - VIEWPORT_MARGIN * 2));
      let left = rect.left;
      if (align === 'center') left = rect.left + rect.width / 2 - width / 2;
      if (align === 'end') left = rect.right - width;

      left = Math.max(VIEWPORT_MARGIN, Math.min(left, window.innerWidth - width - VIEWPORT_MARGIN));

      const popoverHeight = popoverRef.current?.offsetHeight ?? 0;
      let top = rect.bottom + 8;
      let placement: DetailsPopoverPosition['placement'] = 'bottom';

      if (
        popoverHeight
        && top + popoverHeight > window.innerHeight - VIEWPORT_MARGIN
        && rect.top > popoverHeight + VIEWPORT_MARGIN + 8
      ) {
        top = rect.top - popoverHeight - 8;
        placement = 'top';
      } else if (popoverHeight) {
        top = Math.min(top, window.innerHeight - popoverHeight - VIEWPORT_MARGIN);
      }

      const arrowLeft = Math.max(
        16,
        Math.min(rect.left + rect.width / 2 - left, width - 16),
      );

      setPosition({
        top: Math.max(VIEWPORT_MARGIN, top),
        left,
        width,
        arrowLeft,
        placement,
      });
    };

    updatePosition();
    const frame = window.requestAnimationFrame(updatePosition);
    window.addEventListener('resize', updatePosition);
    window.addEventListener('scroll', updatePosition, true);

    return () => {
      window.cancelAnimationFrame(frame);
      window.removeEventListener('resize', updatePosition);
      window.removeEventListener('scroll', updatePosition, true);
    };
  }, [align, maxWidth, open]);

  useEffect(() => {
    if (!open) return;

    const closeOnOutsidePointer = (event: PointerEvent) => {
      const target = event.target as Node;
      if (triggerRef.current?.contains(target)) return;
      if (popoverRef.current?.contains(target)) return;
      setOpen(false);
    };

    const closeOnEscape = (event: KeyboardEvent) => {
      if (event.key !== 'Escape') return;
      setOpen(false);
      triggerRef.current?.focus();
    };

    document.addEventListener('pointerdown', closeOnOutsidePointer);
    document.addEventListener('keydown', closeOnEscape);

    return () => {
      document.removeEventListener('pointerdown', closeOnOutsidePointer);
      document.removeEventListener('keydown', closeOnEscape);
    };
  }, [open]);

  const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
    onClick?.(event);
    if (!event.defaultPrevented) setOpen((current) => !current);
  };

  const popoverStyle = {
    top: position.top,
    left: position.left,
    width: position.width,
    '--details-popover-arrow-left': `${position.arrowLeft}px`,
  } as CSSProperties;

  return (
    <>
      <button
        {...props}
        ref={triggerRef}
        type={props.type ?? 'button'}
        className={classes}
        aria-controls={open ? detailsId : undefined}
        aria-expanded={open}
        aria-haspopup="dialog"
        disabled={disabled}
        onClick={handleClick}
      >
        {children}
      </button>
      {open && detailLines.length && typeof document !== 'undefined'
        ? createPortal(
            <div
              ref={popoverRef}
              id={detailsId}
              className="ui-details-popover"
              data-placement={position.placement}
              role="dialog"
              aria-label={popoverLabel}
              style={popoverStyle}
            >
              <div className="ui-details-popover-head">
                <strong>{popoverLabel}</strong>
                <button
                  type="button"
                  className="ui-details-popover-close"
                  aria-label="Закрыть"
                  onClick={() => {
                    setOpen(false);
                    triggerRef.current?.focus();
                  }}
                >
                  ×
                </button>
              </div>
              <div className="ui-details-popover-body">
                {detailLines.map((line, index) => (
                  <p key={`${line}-${index}`}>{line}</p>
                ))}
              </div>
            </div>,
            document.body,
          )
        : null}
    </>
  );
}
