import type { Directive, DirectiveBinding } from 'vue'
import { getZoomFactor } from '../utils/dropdown'

interface TooltipState {
  el: HTMLElement
  tooltipEl: HTMLElement | null
  text: string
  showTimeout: number | null
  hideTimeout: number | null
  placement: 'top' | 'bottom' | 'left' | 'right' | 'auto'
  onMouseEnter: () => void
  onMouseLeave: () => void
  onFocus: () => void
  onBlur: () => void
  onKeyDown: (e: KeyboardEvent) => void
  onPointerDown: () => void
}

const stateMap = new WeakMap<HTMLElement, TooltipState>()
const SHOW_DELAY = 400
const HIDE_DELAY = 100

function createTooltipEl(): HTMLElement {
  const tip = document.createElement('div')
  tip.setAttribute('role', 'tooltip')
  tip.className = [
    'fixed z-[9999] px-2 py-1 rounded-lg pointer-events-none',
    'bg-bg-elevated border border-border-subtle',
    'text-[0.6875rem] text-text-primary whitespace-nowrap',
    'opacity-0 transition-opacity duration-150',
  ].join(' ')

  const arrow = document.createElement('div')
  arrow.setAttribute('aria-hidden', 'true')
  arrow.dataset.arrow = ''
  arrow.className = 'absolute w-0 h-0'
  tip.appendChild(arrow)

  const content = document.createElement('span')
  content.dataset.content = ''
  tip.appendChild(content)

  return tip
}

function positionTooltip(state: TooltipState) {
  const tip = state.tooltipEl
  if (!tip) return

  const rect = state.el.getBoundingClientRect()
  const zoom = getZoomFactor()
  const margin = 6
  const arrowSize = 4

  // Measure tooltip
  tip.style.left = '-9999px'
  tip.style.top = '-9999px'
  tip.style.opacity = '0'
  const tipRect = tip.getBoundingClientRect()
  const tipW = tipRect.width / zoom
  const tipH = tipRect.height / zoom

  const elTop = rect.top / zoom
  const elLeft = rect.left / zoom
  const elW = rect.width / zoom
  const elH = rect.height / zoom
  const vpW = window.innerWidth / zoom
  const vpH = window.innerHeight / zoom

  // Determine placement
  let placement = state.placement
  if (placement === 'auto') {
    const spaceAbove = elTop
    const spaceBelow = vpH - elTop - elH
    const spaceRight = vpW - elLeft - elW
    const spaceLeft = elLeft

    // Prefer top, then bottom, then right, then left
    if (spaceAbove >= tipH + margin + arrowSize) placement = 'top'
    else if (spaceBelow >= tipH + margin + arrowSize) placement = 'bottom'
    else if (spaceRight >= tipW + margin + arrowSize) placement = 'right'
    else if (spaceLeft >= tipW + margin + arrowSize) placement = 'left'
    else placement = 'top'
  }

  let top = 0
  let left = 0
  const arrow = tip.querySelector('[data-arrow]') as HTMLElement

  // Reset arrow classes
  arrow.className = 'absolute w-0 h-0'

  switch (placement) {
    case 'top':
      top = elTop - tipH - margin
      left = elLeft + elW / 2 - tipW / 2
      arrow.className = 'absolute w-0 h-0 border-x-4 border-x-transparent border-t-4 left-1/2 -translate-x-1/2'
      arrow.style.cssText = 'bottom: -4px; border-top-color: var(--color-bg-elevated);'
      break
    case 'bottom':
      top = elTop + elH + margin
      left = elLeft + elW / 2 - tipW / 2
      arrow.className = 'absolute w-0 h-0 border-x-4 border-x-transparent border-b-4 left-1/2 -translate-x-1/2'
      arrow.style.cssText = 'top: -4px; border-bottom-color: var(--color-bg-elevated);'
      break
    case 'right':
      top = elTop + elH / 2 - tipH / 2
      left = elLeft + elW + margin
      arrow.className = 'absolute w-0 h-0 border-y-4 border-y-transparent border-r-4 top-1/2 -translate-y-1/2'
      arrow.style.cssText = 'left: -4px; border-right-color: var(--color-bg-elevated);'
      break
    case 'left':
      top = elTop + elH / 2 - tipH / 2
      left = elLeft - tipW - margin
      arrow.className = 'absolute w-0 h-0 border-y-4 border-y-transparent border-l-4 top-1/2 -translate-y-1/2'
      arrow.style.cssText = 'right: -4px; border-left-color: var(--color-bg-elevated);'
      break
  }

  // Clamp to viewport
  left = Math.max(4, Math.min(left, vpW - tipW - 4))
  top = Math.max(4, Math.min(top, vpH - tipH - 4))

  tip.style.left = `${left}px`
  tip.style.top = `${top}px`
  tip.style.opacity = '1'
}

function show(state: TooltipState) {
  if (state.hideTimeout) {
    clearTimeout(state.hideTimeout)
    state.hideTimeout = null
  }
  if (state.tooltipEl) return
  if (!state.text) return

  state.showTimeout = window.setTimeout(() => {
    const tip = createTooltipEl()
    const content = tip.querySelector('[data-content]') as HTMLElement
    content.textContent = state.text

    // WCAG: link tooltip to trigger via aria-describedby
    const id = 'tooltip-' + Math.random().toString(36).slice(2, 9)
    tip.id = id
    state.el.setAttribute('aria-describedby', id)

    const container = document.getElementById('app') || document.body
    container.appendChild(tip)
    state.tooltipEl = tip

    // Position after DOM insertion (need measured size)
    requestAnimationFrame(() => positionTooltip(state))
  }, SHOW_DELAY)
}

function hide(state: TooltipState) {
  if (state.showTimeout) {
    clearTimeout(state.showTimeout)
    state.showTimeout = null
  }
  if (!state.tooltipEl) return

  state.hideTimeout = window.setTimeout(() => {
    state.el.removeAttribute('aria-describedby')
    state.tooltipEl?.remove()
    state.tooltipEl = null
    state.hideTimeout = null
  }, HIDE_DELAY)
}

function parseBinding(binding: DirectiveBinding): { text: string; placement: 'top' | 'bottom' | 'left' | 'right' | 'auto' } {
  let text = ''
  let placement: 'top' | 'bottom' | 'left' | 'right' | 'auto' = 'auto'

  if (typeof binding.value === 'string') {
    text = binding.value
  } else if (binding.value && typeof binding.value === 'object') {
    text = binding.value.text || ''
    placement = binding.value.placement || 'auto'
  }

  // Modifier overrides
  if (binding.modifiers.top) placement = 'top'
  if (binding.modifiers.bottom) placement = 'bottom'
  if (binding.modifiers.left) placement = 'left'
  if (binding.modifiers.right) placement = 'right'

  return { text, placement }
}

export const vTooltip: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const { text, placement } = parseBinding(binding)

    const state: TooltipState = {
      el,
      tooltipEl: null,
      text,
      showTimeout: null,
      hideTimeout: null,
      placement,
      onMouseEnter: () => show(state),
      onMouseLeave: () => hide(state),
      onFocus: () => show(state),
      onBlur: () => hide(state),
      onKeyDown: (e: KeyboardEvent) => {
        if (e.key === 'Escape' && state.tooltipEl) {
          hide(state)
        }
      },
      onPointerDown: () => hide(state),
    }

    el.addEventListener('mouseenter', state.onMouseEnter)
    el.addEventListener('mouseleave', state.onMouseLeave)
    el.addEventListener('focus', state.onFocus)
    el.addEventListener('blur', state.onBlur)
    el.addEventListener('keydown', state.onKeyDown)
    el.addEventListener('pointerdown', state.onPointerDown)

    stateMap.set(el, state)
  },

  updated(el: HTMLElement, binding: DirectiveBinding) {
    const state = stateMap.get(el)
    if (!state) return
    const { text, placement } = parseBinding(binding)
    state.text = text
    state.placement = placement

    // Update live tooltip text if visible
    if (state.tooltipEl) {
      const content = state.tooltipEl.querySelector('[data-content]')
      if (content) content.textContent = text
    }
  },

  beforeUnmount(el: HTMLElement) {
    const state = stateMap.get(el)
    if (!state) return

    if (state.showTimeout) clearTimeout(state.showTimeout)
    if (state.hideTimeout) clearTimeout(state.hideTimeout)
    state.el.removeAttribute('aria-describedby')
    state.tooltipEl?.remove()

    el.removeEventListener('mouseenter', state.onMouseEnter)
    el.removeEventListener('mouseleave', state.onMouseLeave)
    el.removeEventListener('focus', state.onFocus)
    el.removeEventListener('blur', state.onBlur)
    el.removeEventListener('keydown', state.onKeyDown)
    el.removeEventListener('pointerdown', state.onPointerDown)

    stateMap.delete(el)
  },
}
