/**
 * Xbox-style controller / gamepad navigation for Archipelago.
 * - Left: Go to side menu only when on leftmost main content
 * - Right: Go to main content (from side menu)
 * - Main: spatial/grid navigation (up/down/left/right like a game)
 * - Enter enters container's inner actions; actions get celebratory sound
 */

import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useControllerStore } from '@/stores/controller'
import { useSpotlightStore } from '@/stores/spotlight'
import { useCLIStore } from '@/stores/cli'
import { useAppLauncherStore } from '@/stores/appLauncher'
import { playNavSound } from '@/composables/useNavSounds'

const FOCUSABLE_SELECTOR = [
  'a[href]',
  'button:not([disabled])',
  'input:not([disabled])',
  'select:not([disabled])',
  'textarea:not([disabled])',
  '[tabindex]:not([tabindex="-1"])',
  '[data-controller-focus]',
  '[data-controller-container]',
].join(', ')

function getFocusableElements(container: Document | HTMLElement = document): HTMLElement[] {
  return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(
    (el) =>
      !el.hasAttribute('disabled') &&
      el.offsetParent !== null &&
      !el.hasAttribute('data-controller-ignore') &&
      !el.closest('[data-controller-ignore]')
  )
}

function getElementsInZone(zone: 'sidebar' | 'main'): HTMLElement[] {
  const container = document.querySelector(`[data-controller-zone="${zone}"]`) as HTMLElement | null
  if (!container) return []
  return getFocusableElements(container)
}

function isInZone(el: HTMLElement | null, zone: 'sidebar' | 'main'): boolean {
  if (!el) return false
  return !!el.closest(`[data-controller-zone="${zone}"]`)
}

function getInnerFocusables(container: HTMLElement): HTMLElement[] {
  return getFocusableElements(container).filter((el) => el !== container && !el.hasAttribute('data-controller-container'))
}

function isInsideContainer(el: HTMLElement | null): boolean {
  if (!el) return false
  const container = el.closest('[data-controller-container]')
  return !!container && container !== el
}

/** Spatial navigation: find nearest focusable in direction (game-style grid) */
function findNearestInDirection(
  from: HTMLElement,
  candidates: HTMLElement[],
  direction: 'up' | 'down' | 'left' | 'right'
): HTMLElement | null {
  const fromRect = from.getBoundingClientRect()
  const fromCenterX = fromRect.left + fromRect.width / 2
  const fromCenterY = fromRect.top + fromRect.height / 2
  const threshold = 50 // px overlap allowed

  const filtered = candidates.filter((el) => {
    if (el === from) return false
    const r = el.getBoundingClientRect()

    switch (direction) {
      case 'left':
        return r.right <= fromRect.left + threshold
      case 'right':
        return r.left >= fromRect.right - threshold
      case 'up':
        return r.bottom <= fromRect.top + threshold
      case 'down':
        return r.top >= fromRect.bottom - threshold
      default:
        return false
    }
  })

  if (filtered.length === 0) return null

  // Pick best: most overlap on perpendicular axis, then closest
  const scored = filtered.map((el) => {
    const r = el.getBoundingClientRect()
    const centerX = r.left + r.width / 2
    const centerY = r.top + r.height / 2

    let overlap: number
    let dist: number
    switch (direction) {
      case 'left':
      case 'right':
        overlap = Math.max(0, Math.min(fromRect.bottom, r.bottom) - Math.max(fromRect.top, r.top))
        dist = Math.abs(centerX - fromCenterX)
        break
      case 'up':
      case 'down':
        overlap = Math.max(0, Math.min(fromRect.right, r.right) - Math.max(fromRect.left, r.left))
        dist = Math.abs(centerY - fromCenterY)
        break
      default:
        overlap = 0
        dist = Infinity
    }
    return { el, overlap, dist }
  })

  scored.sort((a, b) => {
    if (b.overlap !== a.overlap) return b.overlap - a.overlap
    return a.dist - b.dist
  })
  return scored[0]?.el ?? null
}

export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
  const route = useRoute()
  const router = useRouter()
  const store = useControllerStore()
  const isControllerActive = ref(false)
  const gamepadCount = ref(0)

  watch([isControllerActive, gamepadCount], () => {
    store.setActive(isControllerActive.value)
    store.setGamepadCount(gamepadCount.value)
  }, { immediate: true })
  let keyNavTimeout: ReturnType<typeof setTimeout> | null = null
  let pollIntervalId: ReturnType<typeof setInterval> | null = null

  function checkGamepads() {
    const gamepads = navigator.getGamepads?.()
    const count = gamepads ? Array.from(gamepads).filter((g) => g?.connected).length : 0
    if (count !== gamepadCount.value) {
      gamepadCount.value = count
      isControllerActive.value = count > 0
    }
  }

  function handleKeyDown(e: KeyboardEvent) {
    const navKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Enter', 'Escape']
    if (!navKeys.includes(e.key)) return

    const target = e.target as HTMLElement
    if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
      if (e.key !== 'Escape') return
    }

    const root = containerRef?.value ?? document
    const focusable = getFocusableElements(root)
    const currentIndex = focusable.indexOf(document.activeElement as HTMLElement)
    const activeEl = document.activeElement as HTMLElement

    // --- ESCAPE ---
    if (e.key === 'Escape') {
      if (useAppLauncherStore().isOpen) {
        useAppLauncherStore().close()
        e.preventDefault()
        e.stopPropagation()
        return
      }
      if (useSpotlightStore().isOpen) {
        useSpotlightStore().close()
        e.preventDefault()
        e.stopPropagation()
        return
      }
      if (useCLIStore().isOpen) {
        useCLIStore().close()
        e.preventDefault()
        e.stopPropagation()
        return
      }

      if (isInsideContainer(activeEl)) {
        const container = activeEl.closest('[data-controller-container]') as HTMLElement | null
        if (container && container.tabIndex >= 0) {
          playNavSound('back')
          container.focus()
          container.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
          e.preventDefault()
          return
        }
      }

      const isDetailPage = /\/apps\/[^/]+$|\/marketplace\/[^/]+$|\/cloud\/[^/]+$/.test(route.path)
      if (isDetailPage) {
        playNavSound('back')
        window.history.back()
        e.preventDefault()
        return
      }

      const sidebarEls = getElementsInZone('sidebar')
      const firstSidebar = sidebarEls[0]
      if (firstSidebar && isInZone(activeEl, 'main')) {
        playNavSound('back')
        firstSidebar.focus()
        firstSidebar.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
        e.preventDefault()
        return
      }

      playNavSound('back')
      window.history.back()
      e.preventDefault()
      return
    }

    // --- ENTER ---
    if (e.key === 'Enter') {
      if (currentIndex >= 0 && focusable[currentIndex]) {
        const el = focusable[currentIndex] as HTMLElement

        if (el.hasAttribute('data-controller-container')) {
          // Marketplace: Enter = install (click install button)
          if (el.hasAttribute('data-controller-install')) {
            const installBtn = el.querySelector<HTMLButtonElement>('[data-controller-install-btn]:not([disabled])')
            if (installBtn) {
              playNavSound('action')
              installBtn.click()
              e.preventDefault()
              return
            }
          }
          // My Apps: Enter = launch (click Launch button when app is runnable)
          if (el.hasAttribute('data-controller-launch')) {
            const launchBtn = el.querySelector<HTMLButtonElement>('[data-controller-launch-btn]:not([disabled])')
            if (launchBtn) {
              playNavSound('action')
              launchBtn.click()
              e.preventDefault()
              return
            }
          }
          // My Apps, etc: Enter = focus first inner control
          const inner = getInnerFocusables(el)
          const firstInner = inner[0]
          if (firstInner) {
            playNavSound('action')
            firstInner.focus()
            firstInner.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
            e.preventDefault()
            return
          }
        }

        playNavSound('action')
        el.click()
      }
      e.preventDefault()
      return
    }

    // --- ARROWS ---
    if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
      isControllerActive.value = true
      if (keyNavTimeout) clearTimeout(keyNavTimeout)
      keyNavTimeout = setTimeout(() => {
        isControllerActive.value = gamepadCount.value > 0
      }, 3000)

      const sidebarEls = getElementsInZone('sidebar')
      const mainEls = getElementsInZone('main')
      const hasZones = sidebarEls.length > 0 && mainEls.length > 0

      // Right: from sidebar → main
      // - On Home: go to My Apps container
      // - On Apps/Marketplace: go to first app container
      // - On Cloud: go to first folder (Pictures)
      // - On Network (server): go to Services container
      // - On Web5: go to Networking Profits container
      // - On Settings: go to Change Password container
      // - Otherwise: go to top right (App Switcher)
      const mainZone = document.querySelector('[data-controller-zone="main"]')
      const isHome = /^\/dashboard(\/)?$/.test(route.path)
      const isAppsOrMarketplace = /^\/dashboard\/(apps|marketplace)(\/|$)/.test(route.path)
      const isCloud = /^\/dashboard\/cloud(\/|$)/.test(route.path)
      const isNetwork = /^\/dashboard\/server(\/|$)/.test(route.path)
      const isWeb5 = /^\/dashboard\/web5(\/|$)/.test(route.path)
      const isSettings = /^\/dashboard\/settings(\/|$)/.test(route.path)
      const firstAppContainer = mainZone?.querySelector<HTMLElement>('[data-controller-container]')
      const topRightEntry = mainZone?.querySelector<HTMLElement>('[data-controller-main-entry]')
      const firstFocusableInTopRight = topRightEntry ? getFocusableElements(topRightEntry)[0] : null
      const firstMain = ((isHome || isAppsOrMarketplace || isCloud || isNetwork || isWeb5 || isSettings) && firstAppContainer)
        ? firstAppContainer
        : (firstFocusableInTopRight ?? mainEls[0])
      if (e.key === 'ArrowRight' && hasZones && isInZone(activeEl, 'sidebar') && firstMain) {
        playNavSound('move')
        firstMain.focus()
        firstMain.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
        e.preventDefault()
        return
      }

      // Main zone: spatial navigation (game-style grid)
      if (hasZones && isInZone(activeEl, 'main')) {
        const dir = e.key === 'ArrowLeft' ? 'left' : e.key === 'ArrowRight' ? 'right' : e.key === 'ArrowUp' ? 'up' : 'down'
        const next = findNearestInDirection(activeEl, mainEls, dir)

        if (next) {
          playNavSound('move')
          next.focus()
          next.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
          e.preventDefault()
          return
        }

        // No element in that direction: Left from leftmost → sidebar (focus active tab, not logout)
        if (e.key === 'ArrowLeft' && dir === 'left') {
          const sidebarZone = document.querySelector('[data-controller-zone="sidebar"]')
          const activeNavTab = sidebarZone?.querySelector<HTMLElement>('.nav-tab-active')
          const target = activeNavTab ?? sidebarEls[0]
          if (target) {
            playNavSound('move')
            target.focus()
            target.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
            e.preventDefault()
            return
          }
        }
      }

      // Inside container: spatial nav among inner elements
      if (isInsideContainer(activeEl)) {
        const container = activeEl.closest('[data-controller-container]') as HTMLElement
        if (container) {
          const inner = getInnerFocusables(container)
          const dir = e.key === 'ArrowLeft' ? 'left' : e.key === 'ArrowRight' ? 'right' : e.key === 'ArrowUp' ? 'up' : 'down'
          const next = findNearestInDirection(activeEl, inner, dir)
          if (next) {
            playNavSound('move')
            next.focus()
            next.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
            e.preventDefault()
            return
          }
        }
      }

      // Sidebar: linear up/down with wrap (Home+Up→Logout, Logout+Down→Home)
      if (isInZone(activeEl, 'sidebar')) {
        const idx = sidebarEls.indexOf(activeEl)
        if (idx >= 0) {
          const isDown = e.key === 'ArrowDown'
          let nextIdx: number
          if (isDown) {
            nextIdx = idx >= sidebarEls.length - 1 ? 0 : idx + 1
          } else {
            nextIdx = idx <= 0 ? sidebarEls.length - 1 : idx - 1
          }
          const next = sidebarEls[nextIdx]
          if (next && next !== activeEl) {
            playNavSound('move')
            next.focus()
            next.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
            if (next.tagName === 'A') {
              const href = (next as HTMLAnchorElement).getAttribute?.('href')
              if (href && href.startsWith('/')) router.push(href).catch(() => {})
            }
            e.preventDefault()
            return
          }
        }
      }

      // Fallback: linear navigation
      let nextIndex = currentIndex
      const isForward = e.key === 'ArrowDown' || e.key === 'ArrowRight'
      if (focusable.length === 0) return

      if (currentIndex < 0) {
        nextIndex = isForward ? 0 : focusable.length - 1
      } else {
        nextIndex = isForward ? currentIndex + 1 : currentIndex - 1
        if (nextIndex < 0) nextIndex = focusable.length - 1
        if (nextIndex >= focusable.length) nextIndex = 0
      }

      const next = focusable[nextIndex]
      if (next) {
        playNavSound('move')
        next.focus()
        next.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
        if (isInZone(next, 'sidebar') && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
          const href = (next as HTMLAnchorElement).getAttribute?.('href')
          if (href && href.startsWith('/') && next.tagName === 'A') {
            router.push(href).catch(() => {})
          }
        }
        e.preventDefault()
      }
    }
  }

  function handleGamepadInput() {
    checkGamepads()
  }

  function handleGamepadConnected() {
    const gamepads = navigator.getGamepads?.()
    gamepadCount.value = gamepads ? Array.from(gamepads).filter((g) => g?.connected).length : 1
    isControllerActive.value = true
  }

  function handleGamepadDisconnected() {
    const gamepads = navigator.getGamepads?.()
    gamepadCount.value = gamepads ? Array.from(gamepads).filter((g) => g?.connected).length : 0
    isControllerActive.value = gamepadCount.value > 0
  }

  /** Find nearest scrollable ancestor (overflow-y auto/scroll) */
  function getScrollableAncestor(el: HTMLElement | null): HTMLElement | null {
    let p = el?.parentElement
    while (p) {
      const style = getComputedStyle(p)
      const oy = style.overflowY
      if ((oy === 'auto' || oy === 'scroll') && p.scrollHeight > p.clientHeight) return p
      p = p.parentElement
    }
    return null
  }

  /** Ensure wheel scrolls the scrollable area containing the focused element */
  function handleWheel(e: WheelEvent) {
    const active = document.activeElement as HTMLElement | null
    if (!active) return
    const scrollable = getScrollableAncestor(active)
    if (!scrollable) return
    if (e.deltaY !== 0) {
      scrollable.scrollTop += e.deltaY
      e.preventDefault()
    }
    if (e.deltaX !== 0 && scrollable.scrollWidth > scrollable.clientWidth) {
      scrollable.scrollLeft += e.deltaX
      e.preventDefault()
    }
  }

  onMounted(() => {
    checkGamepads()
    window.addEventListener('keydown', handleKeyDown, true)
    window.addEventListener('wheel', handleWheel, { passive: false })
    window.addEventListener('gamepadconnected', handleGamepadConnected)
    window.addEventListener('gamepaddisconnected', handleGamepadDisconnected)
    pollIntervalId = setInterval(handleGamepadInput, 500)
  })

  onBeforeUnmount(() => {
    window.removeEventListener('keydown', handleKeyDown, true)
    window.removeEventListener('wheel', handleWheel)
    window.removeEventListener('gamepadconnected', handleGamepadConnected)
    window.removeEventListener('gamepaddisconnected', handleGamepadDisconnected)
    if (pollIntervalId) clearInterval(pollIntervalId)
    if (keyNavTimeout) clearTimeout(keyNavTimeout)
  })

  return {
    isControllerActive,
    gamepadCount,
  }
}
