import { Controller } from "@hotwired/stimulus"
import { autoUpdate } from "@floating-ui/dom"

export default class extends Controller {
  static targets = ["button", "menu", "input", "content"]
  static values = {
    menuPlacement: { type: String, default: "auto" },
    offset: { type: Number, default: 6 }
  }

  connect() {
    this.isOpen = false
    this.boundOutsideClick = this.handleOutsideClick.bind(this)
    this.boundKeydown = this.handleKeydown.bind(this)
    this.boundTurboLoad = this.handleTurboLoad.bind(this)

    document.addEventListener("click", this.boundOutsideClick)
    document.addEventListener("turbo:load", this.boundTurboLoad)
    this.element.addEventListener("keydown", this.boundKeydown)

    this.observeMenuResize()
  }

  disconnect() {
    document.removeEventListener("click", this.boundOutsideClick)
    document.removeEventListener("turbo:load", this.boundTurboLoad)
    this.element.removeEventListener("keydown", this.boundKeydown)
    this.stopAutoUpdate()
    if (this.resizeObserver) this.resizeObserver.disconnect()
  }

  toggle = () => {
    this.isOpen ? this.close() : this.openMenu()
  }

  openMenu() {
    this.isOpen = true
    this.menuTarget.classList.remove("hidden")
    this.buttonTarget.setAttribute("aria-expanded", "true")
    this.startAutoUpdate()
    this.clearSearch()
    requestAnimationFrame(() => {
      this.menuTarget.classList.remove("opacity-0", "-translate-y-1", "pointer-events-none")
      this.menuTarget.classList.add("opacity-100", "translate-y-0")
      this.updatePosition()
      this.scrollToSelected()
    })
  }

  close() {
    this.isOpen = false
    this.stopAutoUpdate()
    this.menuTarget.classList.remove("opacity-100", "translate-y-0")
    this.menuTarget.classList.add("opacity-0", "-translate-y-1", "pointer-events-none")
    this.buttonTarget.setAttribute("aria-expanded", "false")
    setTimeout(() => { if (!this.isOpen && this.hasMenuTarget) this.menuTarget.classList.add("hidden") }, 150)
  }

  select(event) {
    const selectedElement = event.currentTarget
    const value = selectedElement.dataset.value
    const label = selectedElement.dataset.filterName || selectedElement.textContent.trim()

    this.buttonTarget.textContent = label
    if (this.hasInputTarget) {
      this.inputTarget.value = value
      this.inputTarget.dispatchEvent(new Event("change", { bubbles: true }))
    }

    const previousSelected = this.menuTarget.querySelector("[aria-selected='true']")
    if (previousSelected) {
      previousSelected.setAttribute("aria-selected", "false")
      previousSelected.classList.remove("bg-container-inset")
      const prevIcon = previousSelected.querySelector(".check-icon")
      if (prevIcon) prevIcon.classList.add("hidden")
    }

    selectedElement.setAttribute("aria-selected", "true")
    selectedElement.classList.add("bg-container-inset")
    const selectedIcon = selectedElement.querySelector(".check-icon")
    if (selectedIcon) selectedIcon.classList.remove("hidden")

    this.element.dispatchEvent(new CustomEvent("dropdown:select", {
      detail: { value, label },
      bubbles: true
    }))

    this.close()
    this.buttonTarget.focus()
  }

  focusSearch() {
    const input = this.menuTarget.querySelector('input[type="search"]')
    if (input) { input.focus({ preventScroll: true }); return true }
    return false
  }

  focusFirstElement() {
    const selector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    const el = this.menuTarget.querySelector(selector)
    if (el) el.focus({ preventScroll: true })
  }

  scrollToSelected() {
    const selected = this.menuTarget.querySelector(".bg-container-inset")
    if (!selected) return

    const container = this.hasContentTarget ? this.contentTarget : this.menuTarget
    const containerRect = container.getBoundingClientRect()
    const selectedRect = selected.getBoundingClientRect()
    const delta = selectedRect.top - containerRect.top - (container.clientHeight - selectedRect.height) / 2

    const nextScrollTop = container.scrollTop + delta
    const maxScrollTop = Math.max(0, container.scrollHeight - container.clientHeight)
    container.scrollTop = this.clamp(nextScrollTop, 0, maxScrollTop)
  }

  clamp(value, min, max) {
    return Math.min(max, Math.max(min, value))
  }

  placementMode() {
    const mode = (this.menuPlacementValue || "auto").toLowerCase()
    return ["auto", "down", "up"].includes(mode) ? mode : "auto"
  }

  handleOutsideClick(event) {
    if (this.isOpen && !this.element.contains(event.target)) this.close()
  }

  handleKeydown(event) {
    if (!this.isOpen) return
    if (event.key === "Escape") { this.close(); this.buttonTarget.focus() }
    if (event.key === "Enter" && event.target.dataset.value) { event.preventDefault(); event.target.click() }
  }

  handleTurboLoad() { if (this.isOpen) this.close() }

  clearSearch() {
    const input = this.menuTarget.querySelector('input[type="search"]')
    if (!input) return
    input.value = ""
    input.dispatchEvent(new Event("input", { bubbles: true }))
  }

  startAutoUpdate() {
    if (!this._cleanup && this.buttonTarget && this.menuTarget) {
      this._cleanup = autoUpdate(this.buttonTarget, this.menuTarget, () => this.updatePosition())
    }
  }

  stopAutoUpdate() {
    if (this._cleanup) { this._cleanup(); this._cleanup = null }
  }

  observeMenuResize() {
    this.resizeObserver = new ResizeObserver(() => {
      if (this.isOpen) requestAnimationFrame(() => this.updatePosition())
    })
    this.resizeObserver.observe(this.menuTarget)
  }

  getScrollParent(element) {
    let parent = element.parentElement
    while (parent) {
      const style = getComputedStyle(parent)
      const overflowY = style.overflowY
      if (overflowY === "auto" || overflowY === "scroll") return parent
      parent = parent.parentElement
    }
    return document.documentElement
  }

  updatePosition() {
    if (!this.buttonTarget || !this.menuTarget || !this.isOpen) return

    const container = this.getScrollParent(this.element)
    const containerRect = container.getBoundingClientRect()
    const buttonRect = this.buttonTarget.getBoundingClientRect()
    const menuHeight = this.menuTarget.scrollHeight

    const spaceBelow = containerRect.bottom - buttonRect.bottom
    const spaceAbove = buttonRect.top - containerRect.top
    const placement = this.placementMode()
    const shouldOpenUp = placement === "up" || (placement === "auto" && spaceBelow < menuHeight && spaceAbove > spaceBelow)

    this.menuTarget.style.left = "0"
    this.menuTarget.style.width = "100%"
    this.menuTarget.style.top = ""
    this.menuTarget.style.bottom = ""
    this.menuTarget.style.overflowY = "auto"

    if (shouldOpenUp) {
      this.menuTarget.style.bottom = "100%"
      this.menuTarget.style.maxHeight = `${Math.max(0, spaceAbove - this.offsetValue)}px`
    } else {
      this.menuTarget.style.top = "100%"
      this.menuTarget.style.maxHeight = `${Math.max(0, spaceBelow - this.offsetValue)}px`
    }
  }
}