# Resizable — accessible headless resizable panels
#
# Container with draggable handles between panels for resizing.
# Panel sizes are stored as percentages and exposed via CSS
# custom properties. Place [data-handle] elements between [data-panel]
# elements. Ships zero CSS.
#
# Usage:
#   Resizable
#     div $panel: true
#       p "Left panel"
#     div $handle: true
#     div $panel: true
#       p "Right panel"
#
#   Resizable orientation: "vertical", minSize: 20
#     div $panel: true, "Top"
#     div $handle: true
#     div $panel: true, "Bottom"

export Resizable = component
  @orientation:: "horizontal" | "vertical" := "horizontal"
  @minSize:: number := 10
  @maxSize:: number := 90

  _ready := false
  _dragging = null
  _startPos = 0
  _startSizes = []
  sizes := []

  _panels: ->
    return [] unless @_root
    Array.from(@_root.querySelectorAll(':scope > [data-panel]') or [])

  _handles: ->
    return [] unless @_root
    Array.from(@_root.querySelectorAll(':scope > [data-handle]') or [])

  mounted: ->
    _ready = true
    panels = @_panels()
    count = panels.length
    if count and not sizes.length
      even = 100 / count
      sizes = Array.from {length: count}, -> even

    @_handles().forEach (handle, idx) =>
      handle.setAttribute 'role', 'separator'
      handle.setAttribute 'tabindex', '0'
      handle.addEventListener 'pointerdown', (e) => @_onPointerDown(idx, e)
      handle.addEventListener 'keydown', (e) => @_onKeydown(idx, e)

  _getPos: (e) ->
    if @orientation is 'horizontal' then e.clientX else e.clientY

  _getContainerSize: ->
    rect = @_root?.getBoundingClientRect()
    return 0 unless rect
    if @orientation is 'horizontal' then rect.width else rect.height

  _onPointerDown: (handleIdx, e) ->
    e.preventDefault()
    _dragging = handleIdx
    _startPos = @_getPos(e)
    _startSizes = [...sizes]
    e.target.setPointerCapture(e.pointerId)
    e.target.toggleAttribute 'data-dragging', true

  _onPointerMove: (e) ->
    return unless _dragging?
    total = @_getContainerSize()
    return unless total
    delta = @_getPos(e) - _startPos
    pctDelta = (delta / total) * 100
    @_applyResize(_dragging, _startSizes[_dragging] + pctDelta, _startSizes[_dragging + 1] - pctDelta)

  _onPointerUp: (e) ->
    return unless _dragging?
    handle = @_handles()[_dragging]
    handle?.removeAttribute 'data-dragging'
    _dragging = null
    @emit 'resize', sizes

  _applyResize: (idx, newLeft, newRight) ->
    newLeft  = Math.max(@minSize, Math.min(@maxSize, newLeft))
    newRight = Math.max(@minSize, Math.min(@maxSize, newRight))
    combined = sizes[idx] + sizes[idx + 1]
    newRight = combined - newLeft
    return if newRight < @minSize or newRight > @maxSize
    updated = [...sizes]
    updated[idx] = newLeft
    updated[idx + 1] = newRight
    sizes = updated

  _onKeydown: (handleIdx, e) ->
    step = 10
    horiz = @orientation is 'horizontal'
    delta = switch e.key
      when (if horiz then 'ArrowRight' else 'ArrowDown') then step
      when (if horiz then 'ArrowLeft' else 'ArrowUp') then -step
      else null
    return unless delta?
    e.preventDefault()
    @_applyResize(handleIdx, sizes[handleIdx] + delta, sizes[handleIdx + 1] - delta)
    @emit 'resize', sizes

  ~>
    return unless _ready
    @_panels().forEach (el, idx) =>
      pct = sizes[idx] or 0
      el.style.setProperty '--panel-size', "#{pct}%"
      el.style.flexBasis = "#{pct}%"
    @_handles().forEach (handle, idx) =>
      handle.setAttribute 'aria-valuenow', Math.round(sizes[idx] or 0)
      handle.setAttribute 'aria-orientation', @orientation

  render
    div ref: "_root", $orientation: @orientation
      @pointermove: @_onPointerMove
      @pointerup: @_onPointerUp
      style: "display:flex; flex-direction:#{if @orientation is 'horizontal' then 'row' else 'column'}"
      slot
