import React, { useRef, useState, useEffect, useLayoutEffect, useCallback, ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { usePortalContainer } from './portalContainer';

export type Placement =
  | 'top' | 'top-start' | 'top-end'
  | 'bottom' | 'bottom-start' | 'bottom-end'
  | 'left' | 'left-start' | 'left-end'
  | 'right' | 'right-start' | 'right-end';

interface PositionedPortalProps {
  open: boolean;
  anchor: React.RefObject<HTMLElement>;
  children: ReactNode;
  placement?: Placement;
  offset?: number;
  onClickOutside?: () => void;
  onEscapeKey?: () => void;
  matchWidth?: boolean;
  zIndex?: number;
}

interface Position {
  top: number;
  left: number;
  width?: number;
}

const calculatePosition = (
  anchorRect: DOMRect,
  contentSize: { width: number; height: number },
  placement: Placement,
  offset: number,
  matchWidth: boolean
): Position => {
  // anchorRect from getBoundingClientRect() is viewport-relative,
  // which is exactly what we need for position: fixed.
  const vpWidth = window.innerWidth;
  const vpHeight = window.innerHeight;

  let position: Position = { top: 0, left: 0 };

  // Calculate initial position based on placement (viewport-relative for fixed positioning)
  switch (placement) {
    case 'top':
      position = {
        top: anchorRect.top - contentSize.height - offset,
        left: anchorRect.left + anchorRect.width / 2 - contentSize.width / 2,
      };
      break;
    case 'top-start':
      position = {
        top: anchorRect.top - contentSize.height - offset,
        left: anchorRect.left,
      };
      break;
    case 'top-end':
      position = {
        top: anchorRect.top - contentSize.height - offset,
        left: anchorRect.right - contentSize.width,
      };
      break;
    case 'bottom':
      position = {
        top: anchorRect.bottom + offset,
        left: anchorRect.left + anchorRect.width / 2 - contentSize.width / 2,
      };
      break;
    case 'bottom-start':
      position = {
        top: anchorRect.bottom + offset,
        left: anchorRect.left,
      };
      break;
    case 'bottom-end':
      position = {
        top: anchorRect.bottom + offset,
        left: anchorRect.right - contentSize.width,
      };
      break;
    case 'left':
      position = {
        top: anchorRect.top + anchorRect.height / 2 - contentSize.height / 2,
        left: anchorRect.left - contentSize.width - offset,
      };
      break;
    case 'left-start':
      position = {
        top: anchorRect.top,
        left: anchorRect.left - contentSize.width - offset,
      };
      break;
    case 'left-end':
      position = {
        top: anchorRect.bottom - contentSize.height,
        left: anchorRect.left - contentSize.width - offset,
      };
      break;
    case 'right':
      position = {
        top: anchorRect.top + anchorRect.height / 2 - contentSize.height / 2,
        left: anchorRect.right + offset,
      };
      break;
    case 'right-start':
      position = {
        top: anchorRect.top,
        left: anchorRect.right + offset,
      };
      break;
    case 'right-end':
      position = {
        top: anchorRect.bottom - contentSize.height,
        left: anchorRect.right + offset,
      };
      break;
  }

  // Match anchor width if requested
  if (matchWidth) {
    position.width = anchorRect.width;
  }

  const padding = 8;

  // Flip placement if it overflows (must run BEFORE clamping)
  const isAbove = placement.startsWith('top');
  const isBelow = placement.startsWith('bottom');
  const isLeft = placement.startsWith('left');
  const isRight = placement.startsWith('right');

  if (isBelow && position.top + contentSize.height > vpHeight - padding) {
    const aboveTop = anchorRect.top - contentSize.height - offset;
    if (aboveTop >= padding) {
      position.top = aboveTop;
    }
  } else if (isAbove && position.top < padding) {
    const belowTop = anchorRect.bottom + offset;
    if (belowTop + contentSize.height <= vpHeight - padding) {
      position.top = belowTop;
    }
  }

  if (isRight && position.left + contentSize.width > vpWidth - padding) {
    const leftPos = anchorRect.left - contentSize.width - offset;
    if (leftPos >= padding) {
      position.left = leftPos;
    }
  } else if (isLeft && position.left < padding) {
    const rightPos = anchorRect.right + offset;
    if (rightPos + contentSize.width <= vpWidth - padding) {
      position.left = rightPos;
    }
  }

  // Clamp to viewport bounds as final safety net
  position.left = Math.max(padding, Math.min(position.left, vpWidth - contentSize.width - padding));
  position.top = Math.max(padding, Math.min(position.top, vpHeight - contentSize.height - padding));

  return position;
};

export const PositionedPortal: React.FC<PositionedPortalProps> = ({
  open,
  anchor,
  children,
  placement = 'bottom-start',
  offset = 4,
  onClickOutside,
  onEscapeKey,
  matchWidth = false,
  zIndex = 1000,
}) => {
  const contentRef = useRef<HTMLDivElement>(null);
  const [position, setPosition] = useState<Position>({ top: 0, left: 0 });
  const [isPositioned, setIsPositioned] = useState(false);

  // Acquire the shared theme-aware portal container on mount, release on
  // unmount.  The container mirrors the active Unistyles theme class and
  // sets color-scheme so that portal content inherits the correct theme.
  const portalContainer = usePortalContainer();

  // Calculate position
  const updatePosition = useCallback(() => {
    if (!contentRef.current || !anchor.current) return;

    const anchorRect = anchor.current.getBoundingClientRect();
    // Use scrollWidth/scrollHeight for intrinsic content size —
    // getBoundingClientRect() can be wrong when the element is at position 0,0
    // because the container div has no explicit width and may stretch.
    const contentWidth = contentRef.current.scrollWidth;
    const contentHeight = contentRef.current.scrollHeight;

    const newPosition = calculatePosition(
      anchorRect,
      { width: contentWidth, height: contentHeight },
      placement,
      offset,
      matchWidth
    );

    setPosition(newPosition);
    setIsPositioned(true);
  }, [anchor, placement, offset, matchWidth]);

  // Position after DOM is ready
  useLayoutEffect(() => {
    if (open) {
      const rafId = requestAnimationFrame(() => {
        updatePosition();
      });
      return () => cancelAnimationFrame(rafId);
    } else {
      setIsPositioned(false);
    }
  }, [open, updatePosition]);

  // Update position on scroll/resize
  useEffect(() => {
    if (!open) return;

    updatePosition();

    const handleUpdate = () => updatePosition();
    window.addEventListener('resize', handleUpdate);
    window.addEventListener('scroll', handleUpdate, true);

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

  // Handle escape key
  useEffect(() => {
    if (!open || !onEscapeKey) return;

    const handleEscape = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        onEscapeKey();
      }
    };

    document.addEventListener('keydown', handleEscape);
    return () => document.removeEventListener('keydown', handleEscape);
  }, [open, onEscapeKey]);

  // Handle click outside
  useEffect(() => {
    if (!open || !onClickOutside) return;

    const handleClickOutside = (event: MouseEvent) => {
      const target = event.target as Node;

      if (
        contentRef.current && !contentRef.current.contains(target) &&
        anchor.current && !anchor.current.contains(target)
      ) {
        onClickOutside();
      }
    };

    document.addEventListener('mousedown', handleClickOutside, true);
    return () => document.removeEventListener('mousedown', handleClickOutside, true);
  }, [open, onClickOutside, anchor]);

  if (!open) return null;

  const content = (
    <div
      ref={contentRef}
      style={{
        position: 'fixed',
        top: position.top,
        left: position.left,
        ...(position.width != null && { width: position.width }),
        zIndex,
        opacity: isPositioned ? 1 : 0,
        pointerEvents: isPositioned ? 'auto' : 'none',
      }}
    >
      {children}
    </div>
  );

  return createPortal(content, portalContainer);
};
