import * as React from 'react'

import { cn } from './utils'

/* ------------------------------------------------------------------ */
/* Types                                                               */
/* ------------------------------------------------------------------ */

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

interface ContextMenuTriggerProps extends React.ComponentProps<'div'> {
  asChild?: boolean
}

interface ContextMenuContentProps extends React.ComponentProps<'div'> {
  /** Offset in px from the pointer position */
  alignOffset?: number
}

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

interface ContextMenuContext {
  open: boolean
  position: { x: number; y: number }
  setOpen: (open: boolean) => void
  setPosition: (pos: { x: number; y: number }) => void
}

const ContextMenuCtx = React.createContext<ContextMenuContext | null>(null)

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

/* ------------------------------------------------------------------ */
/* Root                                                                */
/* ------------------------------------------------------------------ */

function ContextMenu({ children, onOpenChange }: ContextMenuProps) {
  const [open, setOpenState] = React.useState(false)
  const [position, setPosition] = React.useState({ x: 0, y: 0 })

  const setOpen = React.useCallback(
    (value: boolean) => {
      setOpenState(value)
      onOpenChange?.(value)
    },
    [onOpenChange]
  )

  const value = React.useMemo(
    () => ({ open, position, setOpen, setPosition }),
    [open, position, setOpen]
  )

  return <ContextMenuCtx.Provider value={value}>{children}</ContextMenuCtx.Provider>
}

/* ------------------------------------------------------------------ */
/* Trigger                                                             */
/* ------------------------------------------------------------------ */

function ContextMenuTrigger({ className, children, ...props }: ContextMenuTriggerProps) {
  const ctx = useContextMenuContext()

  const handleContextMenu = React.useCallback(
    (e: React.MouseEvent) => {
      e.preventDefault()
      ctx.setPosition({ x: e.clientX, y: e.clientY })
      ctx.setOpen(true)
    },
    [ctx]
  )

  return (
    <div
      data-slot="context-menu-trigger"
      className={className}
      onContextMenu={handleContextMenu}
      {...props}
    >
      {children}
    </div>
  )
}

/* ------------------------------------------------------------------ */
/* Content                                                             */
/* ------------------------------------------------------------------ */

function ContextMenuContent({ className, children, ...props }: ContextMenuContentProps) {
  const ctx = useContextMenuContext()
  const contentRef = React.useRef<HTMLDivElement>(null)

  // Close on outside click or Escape
  React.useEffect(() => {
    if (!ctx.open) return

    const handleClick = (e: MouseEvent) => {
      if (contentRef.current && !contentRef.current.contains(e.target as Node)) {
        ctx.setOpen(false)
      }
    }

    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        ctx.setOpen(false)
      }
    }

    document.addEventListener('click', handleClick, true)
    document.addEventListener('keydown', handleKeyDown)
    return () => {
      document.removeEventListener('click', handleClick, true)
      document.removeEventListener('keydown', handleKeyDown)
    }
  }, [ctx, ctx.open])

  // Adjust position to stay within viewport
  const [adjusted, setAdjusted] = React.useState(ctx.position)
  React.useEffect(() => {
    if (!ctx.open || !contentRef.current) {
      setAdjusted(ctx.position)
      return
    }

    const rect = contentRef.current.getBoundingClientRect()
    let x = ctx.position.x
    let y = ctx.position.y

    if (x + rect.width > window.innerWidth) x = window.innerWidth - rect.width - 4
    if (y + rect.height > window.innerHeight) y = window.innerHeight - rect.height - 4
    if (x < 0) x = 4
    if (y < 0) y = 4

    setAdjusted({ x, y })
  }, [ctx.open, ctx.position])

  if (!ctx.open) return null

  return (
    <div
      ref={contentRef}
      data-slot="context-menu-content"
      role="menu"
      className={cn(
        'fixed z-50 min-w-[10rem] overflow-hidden rounded-xl border p-1 shadow-xl',
        'border-border-glass bg-popover backdrop-blur-xl',
        'animate-in fade-in-0 zoom-in-95',
        className
      )}
      style={{ left: adjusted.x, top: adjusted.y }}
      {...props}
    >
      {children}
    </div>
  )
}

/* ------------------------------------------------------------------ */
/* Item                                                                */
/* ------------------------------------------------------------------ */

function ContextMenuItem({
  className,
  disabled,
  onSelect,
  children,
  ...props
}: React.ComponentProps<'div'> & {
  disabled?: boolean
  onSelect?: () => void
}) {
  const ctx = useContextMenuContext()

  const handleClick = React.useCallback(() => {
    if (disabled === true) return
    onSelect?.()
    ctx.setOpen(false)
  }, [ctx, disabled, onSelect])

  return (
    <div
      data-slot="context-menu-item"
      role="menuitem"
      aria-disabled={disabled === true ? true : undefined}
      className={cn(
        'relative flex cursor-pointer items-center rounded-lg px-3 py-2 text-sm transition-colors outline-none select-none',
        'text-foreground hover:bg-accent focus:bg-accent',
        disabled === true && 'pointer-events-none opacity-50',
        className
      )}
      onClick={handleClick}
      {...props}
    >
      {children}
    </div>
  )
}

/* ------------------------------------------------------------------ */
/* Separator                                                           */
/* ------------------------------------------------------------------ */

function ContextMenuSeparator({ className, ...props }: React.ComponentProps<'div'>) {
  return (
    <div
      data-slot="context-menu-separator"
      role="separator"
      className={cn('bg-border-glass my-1 h-px', className)}
      {...props}
    />
  )
}

/* ------------------------------------------------------------------ */
/* Label                                                               */
/* ------------------------------------------------------------------ */

function ContextMenuLabel({ className, ...props }: React.ComponentProps<'div'>) {
  return (
    <div
      data-slot="context-menu-label"
      className={cn('text-foreground-muted px-3 py-1.5 text-xs font-semibold', className)}
      {...props}
    />
  )
}

/* ------------------------------------------------------------------ */
/* Shortcut                                                            */
/* ------------------------------------------------------------------ */

function ContextMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {
  return (
    <span
      data-slot="context-menu-shortcut"
      className={cn('text-foreground-muted ml-auto pl-4 text-xs tracking-widest', className)}
      {...props}
    />
  )
}

export {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuSeparator,
  ContextMenuLabel,
  ContextMenuShortcut,
}
