import * as React from 'react'
import { createPortal } from 'react-dom'

import { cn } from './utils'

/* ------------------------------------------------------------------ */
/*  Context                                                            */
/* ------------------------------------------------------------------ */

interface PopoverContextValue {
  open: boolean
  setOpen: React.Dispatch<React.SetStateAction<boolean>>
  triggerRef: React.RefObject<HTMLButtonElement | null>
}

const PopoverContext = React.createContext<PopoverContextValue | null>(null)

function usePopoverContext() {
  const ctx = React.useContext(PopoverContext)
  if (!ctx) throw new Error('Popover compound components must be used within <Popover>')
  return ctx
}

/* ------------------------------------------------------------------ */
/*  Popover (Root)                                                     */
/* ------------------------------------------------------------------ */

export interface PopoverProps {
  open?: boolean
  defaultOpen?: boolean
  onOpenChange?: (open: boolean) => void
  children: React.ReactNode
}

function Popover({
  open: controlledOpen,
  defaultOpen = false,
  onOpenChange,
  children,
}: PopoverProps) {
  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
  const isControlled = controlledOpen !== undefined
  const open = isControlled ? controlledOpen : uncontrolledOpen
  const triggerRef = React.useRef<HTMLButtonElement | null>(null)

  const setOpen = React.useCallback(
    (value: React.SetStateAction<boolean>) => {
      const next = typeof value === 'function' ? value(open) : value
      if (!isControlled) setUncontrolledOpen(next)
      onOpenChange?.(next)
    },
    [open, isControlled, onOpenChange]
  )

  return (
    <PopoverContext.Provider value={{ open, setOpen, triggerRef }}>
      {children}
    </PopoverContext.Provider>
  )
}

/* ------------------------------------------------------------------ */
/*  PopoverTrigger                                                     */
/* ------------------------------------------------------------------ */

function PopoverTrigger({ className, ...props }: React.ComponentProps<'button'>) {
  const { open, setOpen, triggerRef } = usePopoverContext()

  return (
    <button
      ref={triggerRef}
      data-slot="popover-trigger"
      type="button"
      aria-haspopup="dialog"
      aria-expanded={open}
      onClick={() => {
        setOpen(prev => !prev)
      }}
      className={className}
      {...props}
    />
  )
}

/* ------------------------------------------------------------------ */
/*  PopoverAnchor                                                      */
/* ------------------------------------------------------------------ */

function PopoverAnchor({ className, ...props }: React.ComponentProps<'div'>) {
  return <div data-slot="popover-anchor" className={className} {...props} />
}

/* ------------------------------------------------------------------ */
/*  PopoverContent                                                     */
/* ------------------------------------------------------------------ */

export interface PopoverContentProps extends React.ComponentProps<'div'> {
  align?: 'start' | 'center' | 'end'
  sideOffset?: number
  side?: 'top' | 'bottom' | 'left' | 'right'
}

function PopoverContent({
  className,
  align = 'center',
  sideOffset = 8,
  side = 'bottom',
  children,
  ...props
}: PopoverContentProps) {
  const { open, setOpen, triggerRef } = usePopoverContext()
  const contentRef = React.useRef<HTMLDivElement>(null)
  const [position, setPosition] = React.useState({ top: 0, left: 0 })

  // Position the popover relative to the trigger
  React.useEffect(() => {
    if (!open || !triggerRef.current) return

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

      const alignedLeft =
        align === 'start' ? rect.left : align === 'end' ? rect.right : rect.left + rect.width / 2

      const computed = (() => {
        switch (side) {
          case 'top':
            return { top: rect.top - sideOffset, left: alignedLeft }
          case 'left':
            return { top: rect.top + rect.height / 2, left: rect.left - sideOffset }
          case 'right':
            return { top: rect.top + rect.height / 2, left: rect.right + sideOffset }
          case 'bottom':
          default:
            return { top: rect.bottom + sideOffset, left: alignedLeft }
        }
      })()

      setPosition(computed)
    }

    updatePosition()
    window.addEventListener('scroll', updatePosition, true)
    window.addEventListener('resize', updatePosition)
    return () => {
      window.removeEventListener('scroll', updatePosition, true)
      window.removeEventListener('resize', updatePosition)
    }
  }, [open, side, align, sideOffset, triggerRef])

  // Close on outside click
  React.useEffect(() => {
    if (!open) return
    const handler = (e: MouseEvent) => {
      const target = e.target as Node
      if (
        contentRef.current &&
        !contentRef.current.contains(target) &&
        triggerRef.current &&
        !triggerRef.current.contains(target)
      ) {
        setOpen(false)
      }
    }
    const keyHandler = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        e.preventDefault()
        setOpen(false)
        triggerRef.current?.focus()
      }
    }
    // Delay to avoid closing from the same click that opened
    const timer = setTimeout(() => {
      document.addEventListener('mousedown', handler)
    }, 0)
    document.addEventListener('keydown', keyHandler)
    return () => {
      clearTimeout(timer)
      document.removeEventListener('mousedown', handler)
      document.removeEventListener('keydown', keyHandler)
    }
  }, [open, setOpen, triggerRef])

  if (!open) return null

  const translateX =
    side === 'top' || side === 'bottom'
      ? align === 'start'
        ? '0%'
        : align === 'end'
          ? '-100%'
          : '-50%'
      : side === 'left'
        ? '-100%'
        : '0%'

  const translateY = side === 'left' || side === 'right' ? '-50%' : side === 'top' ? '-100%' : '0%'

  return createPortal(
    <div
      ref={contentRef}
      data-slot="popover-content"
      role="dialog"
      className={cn(
        'z-50 w-72 rounded-xl border border-border-glass bg-glass p-4 shadow-lg backdrop-blur-[20px]',
        'text-foreground outline-none',
        'animate-in fade-in-0 zoom-in-95',
        className
      )}
      style={{
        position: 'fixed',
        top: position.top,
        left: position.left,
        transform: `translate(${translateX}, ${translateY})`,
      }}
      {...props}
    >
      {children}
    </div>,
    document.body
  )
}

/* ------------------------------------------------------------------ */
/*  PopoverClose                                                       */
/* ------------------------------------------------------------------ */

function PopoverClose({ className, onClick, ...props }: React.ComponentProps<'button'>) {
  const { setOpen } = usePopoverContext()

  return (
    <button
      data-slot="popover-close"
      type="button"
      onClick={e => {
        setOpen(false)
        onClick?.(e)
      }}
      className={className}
      {...props}
    />
  )
}

export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverClose }
