import { FocusableElement, tabbable } from 'tabbable'
import { announce } from './aria'
import { localizeShortcut } from '../platform'

interface FocusableGroup {
  first: FocusableElement
  last: FocusableElement
}

/*
NOTE: This function will not perform as expected with the Safari Browser. Safari by default
does not tab to anchor and button elements. Therefore, if the item being checked is a button or anchor tag,
it will not be triggered since Safari will not tab to that element. For this functionality to work with Safari, the following 
must be set in the Safari settings: 'Press Tab to highlight each item on a webpage' in the 'advanced' section.
*/
export function trapFocusWithin(
  e: KeyboardEvent,
  elementGroups: FocusableGroup[],
  cycle = true,
) {
  let keyType
  if (e.key === 'Tab' && e.shiftKey) {
    keyType = 'back'
  } else if (e.key === 'Tab') {
    keyType = 'forward'
  } else {
    return
  }

  /*
   * NOTE: The following guard is a temporary workound for users who get stuck in a focus trap due to poor DOM
   * ordering not playing nicely with our focus trap logic. This code will become unnecessary once we reorder
   * key elements in the DOM to match visual/tabbing order.
   */
  if (!cycle) {
    guardAgainstFocusLoop(e, elementGroups, keyType)
  }

  for (let i = 0; i < elementGroups.length; i++) {
    const group = elementGroups[i]

    if (keyType === 'forward' && e.target === group.last) {
      const nextGroup =
        elementGroups[i + 1] || (cycle ? elementGroups[0] : undefined)
      if (nextGroup) {
        e.preventDefault()
        nextGroup.first?.focus()
        return
      }
    } else if (keyType === 'back' && e.target === group.first) {
      const prevGroup =
        elementGroups[i - 1] ||
        (cycle ? elementGroups[elementGroups.length - 1] : undefined)
      if (prevGroup) {
        e.preventDefault()
        prevGroup.last?.focus()
        return
      }
    }
  }
}

/*
 * Checks if one of the following conditions are met, and shows an escape hint if so:
 * 1. User is tabbing forward from the last element of the last group, and there are elements
 *    from earlier groups positioned after the last element in DOM order.
 * 2. User is tabbing backward from the first element of the first group, and there are elements
 *    from later groups positioned before the first element in DOM order.
 */
function guardAgainstFocusLoop(
  e: KeyboardEvent,
  elementGroups: FocusableGroup[],
  keyType: string,
) {
  const lastGroup = elementGroups[elementGroups.length - 1]
  const firstGroup = elementGroups[0]

  if (keyType === 'forward' && e.target === lastGroup?.last) {
    // Check if any element from an earlier group comes after the last element in DOM order
    const lastElement = lastGroup.last

    for (let i = 0; i < elementGroups.length - 1; i++) {
      const group = elementGroups[i]
      if (
        comesAfter(group.first, lastElement) ||
        comesAfter(group.last, lastElement)
      ) {
        showEscapeHint()
        return
      }
    }
  }

  if (keyType === 'back' && e.target === firstGroup?.first) {
    // Check if any element from a later group comes before the first element in DOM order
    const firstElement = firstGroup.first

    for (let i = 1; i < elementGroups.length; i++) {
      const group = elementGroups[i]
      if (
        comesBefore(group.first, firstElement) ||
        comesBefore(group.last, firstElement)
      ) {
        showEscapeHint()
        return
      }
    }
  }
}

/*
 * Checks if an element comes after a reference element in DOM order.
 */
function comesAfter(element: Element, reference: Element): boolean {
  const position = reference.compareDocumentPosition(element)
  return Boolean(position & Node.DOCUMENT_POSITION_FOLLOWING)
}

/*
 * Checks if an element comes before a reference element in DOM order.
 */
function comesBefore(element: Element, reference: Element): boolean {
  const position = reference.compareDocumentPosition(element)
  return Boolean(position & Node.DOCUMENT_POSITION_PRECEDING)
}

/*
 * Shows a hint to the user about how to escape the focus trap.
 * This hint is shown at most once every HINT_COOLDOWN milliseconds.
 */
let lastHintTime = 0
const HINT_COOLDOWN = 5000

function showEscapeHint() {
  const now = Date.now()
  if (now - lastHintTime < HINT_COOLDOWN) return
  lastHintTime = now

  const raw = window.BRIGHTSPOT?.ui.tooltips?.focusTrapHint
  if (!raw) return
  const message = localizeShortcut(raw)

  // ARIA announcement for screen readers
  announce(message)

  // Visual popover for sighted users
  showVisualPopover(message)
}

// A rudimentary visual popover that should be replaced later on with a list of landmark skip links.
function showVisualPopover(message: string) {
  const existingPopover = document.getElementById('focus-escape-hint')
  if (existingPopover) existingPopover.remove()

  const popover = document.createElement('div')
  popover.id = 'focus-escape-hint'
  popover.textContent = message
  popover.setAttribute('role', 'status')
  popover.style.position = 'fixed'
  popover.style.bottom = '20px'
  popover.style.right = '20px'
  popover.style.padding = '12px 16px'
  popover.style.backgroundColor = '#333'
  popover.style.color = '#fff'
  popover.style.borderRadius = '4px'
  popover.style.fontSize = '14px'
  popover.style.zIndex = '10000'
  popover.style.boxShadow = '0 2px 8px rgba(0,0,0,0.3)'
  popover.style.pointerEvents = 'none'

  document.body.appendChild(popover)

  setTimeout(() => {
    popover.remove()
  }, 4000)
}
