import { ref, onUnmounted } from 'vue'

export function useFocusTrap() {
  const trapElement = ref<HTMLElement | null>(null)
  let previouslyFocused: HTMLElement | null = null
  let isActive = false

  function getFocusableElements(el: HTMLElement): HTMLElement[] {
    const selectors = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
    return Array.from(el.querySelectorAll<HTMLElement>(selectors)).filter(e => e.offsetParent !== null)
  }

  function onKeydown(e: KeyboardEvent) {
    if (e.key === 'Escape') {
      e.preventDefault()
      deactivate()
      return
    }
    if (e.key !== 'Tab' || !trapElement.value) return
    const focusable = getFocusableElements(trapElement.value)
    if (focusable.length === 0) return
    const first = focusable[0]
    const last = focusable[focusable.length - 1]
    if (e.shiftKey) {
      if (document.activeElement === first) {
        e.preventDefault()
        last.focus()
      }
    } else {
      if (document.activeElement === last) {
        e.preventDefault()
        first.focus()
      }
    }
  }

  let onDeactivate: (() => void) | null = null

  function activate(el: HTMLElement, opts?: { onDeactivate?: () => void, initialFocus?: HTMLElement }) {
    if (isActive) deactivate()
    isActive = true
    trapElement.value = el
    previouslyFocused = document.activeElement as HTMLElement
    onDeactivate = opts?.onDeactivate || null
    document.addEventListener('keydown', onKeydown)
    const target = opts?.initialFocus || getFocusableElements(el)[0]
    if (target) {
      requestAnimationFrame(() => {
        if (trapElement.value === el) target.focus()
      })
    }
  }

  function deactivate() {
    if (!isActive) return
    isActive = false
    document.removeEventListener('keydown', onKeydown)
    trapElement.value = null
    if (onDeactivate) onDeactivate()
    if (previouslyFocused) {
      previouslyFocused.focus()
      previouslyFocused = null
    }
  }

  onUnmounted(() => {
    if (isActive) deactivate()
  })

  return { activate, deactivate }
}
