import { useState, useEffect, useRef, useCallback, type ReactNode } from 'react'
import { createPortal } from 'react-dom'
import styles from './Popover.module.css'

export interface PopoverProps {
  trigger: ReactNode
  content: ReactNode
  position?: 'top' | 'bottom' | 'left' | 'right'
  align?: 'start' | 'center' | 'end'
  className?: string
}

interface ContentStyle {
  top: number
  left: number
}

function getContentStyle(
  triggerRect: DOMRect,
  contentEl: HTMLDivElement | null,
  position: 'top' | 'bottom' | 'left' | 'right',
  align: 'start' | 'center' | 'end',
): ContentStyle {
  const ARROW_SIZE = 8
  const GAP = ARROW_SIZE + 4

  const contentWidth = contentEl?.offsetWidth ?? 200
  const contentHeight = contentEl?.offsetHeight ?? 100

  let top = 0
  let left = 0

  // Main axis
  if (position === 'bottom') {
    top = triggerRect.bottom + window.scrollY + GAP
  } else if (position === 'top') {
    top = triggerRect.top + window.scrollY - contentHeight - GAP
  } else if (position === 'left') {
    left = triggerRect.left + window.scrollX - contentWidth - GAP
  } else if (position === 'right') {
    left = triggerRect.right + window.scrollX + GAP
  }

  // Cross axis alignment
  if (position === 'top' || position === 'bottom') {
    if (align === 'start') {
      left = triggerRect.left + window.scrollX
    } else if (align === 'center') {
      left = triggerRect.left + window.scrollX + triggerRect.width / 2 - contentWidth / 2
    } else {
      left = triggerRect.right + window.scrollX - contentWidth
    }
  } else {
    if (align === 'start') {
      top = triggerRect.top + window.scrollY
    } else if (align === 'center') {
      top = triggerRect.top + window.scrollY + triggerRect.height / 2 - contentHeight / 2
    } else {
      top = triggerRect.bottom + window.scrollY - contentHeight
    }
  }

  return { top, left }
}

export function Popover({
  trigger,
  content,
  position = 'bottom',
  align = 'center',
  className,
}: PopoverProps) {
  const [open, setOpen] = useState(false)
  const [style, setStyle] = useState<ContentStyle>({ top: 0, left: 0 })
  const triggerRef = useRef<HTMLDivElement>(null)
  const contentRef = useRef<HTMLDivElement>(null)

  const recalculate = useCallback(() => {
    if (!triggerRef.current) return
    const rect = triggerRef.current.getBoundingClientRect()
    setStyle(getContentStyle(rect, contentRef.current, position, align))
  }, [position, align])

  // Recalculate after content renders
  useEffect(() => {
    if (open) {
      // Allow the DOM to paint once, then measure
      const id = requestAnimationFrame(() => {
        recalculate()
      })
      return () => cancelAnimationFrame(id)
    }
  }, [open, recalculate])

  // Close on outside click
  useEffect(() => {
    if (!open) return
    function handleMouseDown(e: MouseEvent) {
      if (
        triggerRef.current &&
        !triggerRef.current.contains(e.target as Node) &&
        contentRef.current &&
        !contentRef.current.contains(e.target as Node)
      ) {
        setOpen(false)
      }
    }
    document.addEventListener('mousedown', handleMouseDown)
    return () => document.removeEventListener('mousedown', handleMouseDown)
  }, [open])

  // Close on Esc
  useEffect(() => {
    if (!open) return
    function handleKey(e: KeyboardEvent) {
      if (e.key === 'Escape') setOpen(false)
    }
    document.addEventListener('keydown', handleKey)
    return () => document.removeEventListener('keydown', handleKey)
  }, [open])

  return (
    <div ref={triggerRef} className={`${styles.wrapper} ${className ?? ''}`}>
      <div
        className={styles.trigger}
        onClick={() => setOpen((prev) => !prev)}
      >
        {trigger}
      </div>
      {open &&
        createPortal(
          <div
            ref={contentRef}
            className={`${styles.content} ${styles[`position-${position}`]} ${styles[`align-${align}`]}`}
            style={{ top: style.top, left: style.left }}
            role="dialog"
            data-testid="popover-content"
          >
            <div className={`${styles.arrow} ${styles[`arrow-${position}`]}`} aria-hidden="true" />
            {content}
          </div>,
          document.body,
        )}
    </div>
  )
}
