import type { Placement } from '@floating-ui/dom'
import { arrow, autoUpdate, offset } from '@floating-ui/dom'
import type { Directive, DirectiveBinding } from 'vue'
import { updateFloatingUi } from '@/utils/floatingUi'

type ElementWithTooltip = HTMLElement & {
  $tooltip?: HTMLDivElement
  $cleanup?: Closure
  $tooltipListenersAttached?: boolean
}

const getOrCreateTooltip = (el: ElementWithTooltip): HTMLElement => {
  if (el.$tooltip) {
    return el.$tooltip
  }

  el.$tooltip = document.createElement('div')
  el.$tooltip.classList.add('tooltip')

  const arrow = document.createElement('div')
  arrow.classList.add('tooltip-arrow')

  const content = document.createElement('div')
  content.classList.add('tooltip-content')

  el.$tooltip.appendChild(content)
  el.$tooltip.appendChild(arrow)

  document.body.appendChild(el.$tooltip)

  return el.$tooltip
}

const init = (el: ElementWithTooltip, binding: DirectiveBinding) => {
  const $tooltip = getOrCreateTooltip(el)

  // make sure the actual title is removed from the element, but keep a backup for the updated() hook calls
  $tooltip.querySelector<HTMLDivElement>('.tooltip-content')!.textContent =
    binding.value || el.title || el.getAttribute('data-title') || el.textContent

  if (el.title && !el.getAttribute('data-title')) {
    el.setAttribute('data-title', el.title)
    el.removeAttribute('title')
  }

  const $arrow = $tooltip.querySelector<HTMLDivElement>('.tooltip-arrow')!

  let placement: Placement = 'top'

  ;(['left', 'right', 'top', 'bottom'] as Placement[]).forEach(p => {
    if (binding.modifiers[p]) {
      placement = p
    }
  })

  const update = async () =>
    await updateFloatingUi(
      el,
      $tooltip,
      {
        placement,
        middleware: [arrow({ element: $arrow }), offset(8)],
      },
      $arrow,
    )

  el.$cleanup = el.$cleanup || autoUpdate(el, $tooltip, update)

  if (el.$tooltipListenersAttached) {
    return
  }

  el.$tooltipListenersAttached = true

  const showTooltip = async () => {
    $tooltip.classList.add('show')
    await update()
  }

  const hideTooltip = () => $tooltip.classList.remove('show')

  el.addEventListener('mouseenter', showTooltip)
  el.addEventListener('mouseleave', hideTooltip)
  el.addEventListener('blur', hideTooltip)
  el.addEventListener('mousedown', hideTooltip)

  el.addEventListener('focus', () => {
    // Only show tooltip for keyboard focus (Tab), not mouse click focus
    if (el.matches(':focus-visible')) {
      showTooltip()
    }
  })
}

export const tooltip: Directive = {
  mounted: init,
  updated: init,

  beforeUnmount: (el: ElementWithTooltip) => {
    el.$cleanup && el.$cleanup()
    el.$tooltip && document.body.removeChild(el.$tooltip)
  },
}
