# Popover — accessible headless popover with anchor positioning
#
# Positions itself relative to the trigger. Dismisses on Escape or click outside.
# Exposes $open, $placement on content. Ships zero CSS.
#
# Usage:
#   Popover placement: "bottom-start"
#     button $trigger: true, "Click me"
#     div $content: true
#       p "Popover content"

export Popover = component
  @placement := 'bottom-start'
  @offset := 4
  @disabled := false
  @openOnHover := false
  @hoverDelay := 300
  @hoverCloseDelay := 200

  open := false
  _ready := false
  _hoverTimer := null
  _hoverCloseTimer := null
  _id =! "pop-#{Math.random().toString(36).slice(2, 8)}"

  mounted: ->
    _ready = true
    trigger = @_content?.querySelector('[data-trigger]')
    if trigger
      trigger.setAttribute 'aria-expanded', false
      trigger.setAttribute 'aria-haspopup', 'dialog'
      trigger.addEventListener 'click', => @toggle()
      trigger.addEventListener 'keydown', (e) =>
        if e.key in ['Enter', ' ', 'ArrowDown']
          e.preventDefault()
          @toggle()
      if @openOnHover
        trigger.addEventListener 'mouseenter', =>
          clearTimeout _hoverCloseTimer if _hoverCloseTimer
          _hoverTimer = setTimeout (=> @openPopover()), @hoverDelay
        trigger.addEventListener 'mouseleave', =>
          clearTimeout _hoverTimer if _hoverTimer
          _hoverCloseTimer = setTimeout (=> @close()), @hoverCloseDelay

  toggle: ->
    return if @disabled
    if open then @close() else @openPopover()

  openPopover: ->
    open = true
    setTimeout => @_position(), 0

  close: ->
    open = false
    @_content?.querySelector('[data-trigger]')?.focus()

  _position: ->
    trigger = @_content?.querySelector('[data-trigger]')
    floating = @_content?.querySelector('[data-content]')
    return unless trigger and floating
    @_content.style.position = 'relative'
    tr = trigger.getBoundingClientRect()
    cr = @_content.getBoundingClientRect()
    fl = floating.getBoundingClientRect()
    [side, align] = @placement.split('-')
    align ?= 'center'
    gap = @offset

    x = switch side
      when 'bottom', 'top'
        switch align
          when 'start' then tr.left - cr.left
          when 'end' then tr.right - cr.left - fl.width
          else tr.left - cr.left + (tr.width - fl.width) / 2
      when 'right' then tr.right - cr.left + gap
      when 'left' then tr.left - cr.left - fl.width - gap

    y = switch side
      when 'bottom' then tr.bottom - cr.top + gap
      when 'top' then tr.top - cr.top - fl.height - gap
      when 'left', 'right'
        switch align
          when 'start' then tr.top - cr.top
          when 'end' then tr.bottom - cr.top - fl.height
          else tr.top - cr.top + (tr.height - fl.height) / 2

    if side is 'bottom' and tr.bottom + gap + fl.height > window.innerHeight
      y = tr.top - cr.top - fl.height - gap
    if side is 'top' and tr.top - fl.height - gap < 0
      y = tr.bottom - cr.top + gap
    if side is 'right' and tr.right + gap + fl.width > window.innerWidth
      x = tr.left - cr.left - fl.width - gap
    if side is 'left' and tr.left - fl.width - gap < 0
      x = tr.right - cr.left + gap

    x = Math.max(4 - cr.left, Math.min(x, window.innerWidth - cr.left - fl.width - 4))

    floating.style.position = 'absolute'
    floating.style.left = "#{x}px"
    floating.style.top = "#{y}px"
    floating.style.zIndex = '50'

  ~>
    return unless _ready
    trigger = @_content?.querySelector('[data-trigger]')
    floating = @_content?.querySelector('[data-content]')
    if trigger
      trigger.setAttribute 'aria-expanded', !!open
    if floating
      floating.hidden = not open
      if open
        floating.setAttribute 'data-open', ''
        floating.setAttribute 'data-placement', @placement
        heading = floating.querySelector('h1,h2,h3,h4,h5,h6')
        if heading
          heading.id ?= "#{_id}-title"
          floating.setAttribute 'aria-labelledby', heading.id
        desc = floating.querySelector('p')
        if desc
          desc.id ?= "#{_id}-desc"
          floating.setAttribute 'aria-describedby', desc.id
      else
        floating.removeAttribute 'data-open'

  ~>
    return unless _ready
    if open
      onDown = (e) =>
        trigger = @_content?.querySelector('[data-trigger]')
        floating = @_content?.querySelector('[data-content]')
        unless trigger?.contains(e.target) or floating?.contains(e.target)
          @close()
      document.addEventListener 'mousedown', onDown
      return -> document.removeEventListener 'mousedown', onDown

  onKeydown: (e) ->
    if e.key is 'Escape' and open
      e.preventDefault()
      @close()

  render
    div ref: "_content"
      slot
