# OTPField — accessible headless one-time password input
#
# Multi-digit code input with auto-advance, backspace navigation, and
# paste support. Each digit gets its own input box. Ships zero CSS.
#
# Usage:
#   OTPField length: 6, value <=> code, @complete: handleVerify

export OTPField = component
  @length := 6
  @value := ''
  @disabled := false
  @mask := false

  _id =! "otp-#{Math.random().toString(36).slice(2, 8)}"

  _getInputs: ->
    return [] unless @_root
    Array.from(@_root.querySelectorAll('input'))

  _focusAt: (idx) ->
    inputs = @_getInputs()
    inputs[idx]?.focus()
    inputs[idx]?.select()

  _updateValue: ->
    inputs = @_getInputs()
    digits = inputs.map (el) -> el.value
    @value = digits.join('')
    @emit 'input', @value
    if @value.length is @length and digits.every (d) -> d.length is 1
      @emit 'complete', @value

  _onInput: (e, idx) ->
    ch = e.target.value.slice(-1)
    e.target.value = ch
    @_updateValue()
    @_focusAt(idx + 1) if ch and idx < @length - 1

  _onKeydown: (e, idx) ->
    switch e.key
      when 'Backspace'
        if not e.target.value and idx > 0
          @_focusAt(idx - 1)
          inputs = @_getInputs()
          inputs[idx - 1]?.value = ''
          @_updateValue()
      when 'ArrowLeft'
        e.preventDefault()
        @_focusAt(idx - 1) if idx > 0
      when 'ArrowRight'
        e.preventDefault()
        @_focusAt(idx + 1) if idx < @length - 1
      when 'Home'
        e.preventDefault()
        @_focusAt(0)
      when 'End'
        e.preventDefault()
        @_focusAt(@length - 1)

  _onPaste: (e) ->
    e.preventDefault()
    text = (e.clipboardData?.getData('text') or '').replace(/\D/g, '').slice(0, @length)
    return unless text
    inputs = @_getInputs()
    for ch, idx in text.split('')
      inputs[idx]?.value = ch
    @_updateValue()
    @_focusAt(Math.min(text.length, @length - 1))

  _onFocus: (e) -> e.target.select()

  render
    div ref: "_root", role: "group", aria-label: "One-time password"
      $disabled: @disabled?!
      $complete: (@value.length is @length)?!
      for idx in [0...@length]
        input id: "#{_id}-#{idx}"
          type: if @mask then "password" else "text"
          inputmode: "numeric"
          autocomplete: "one-time-code"
          maxlength: "1"
          aria-label: "Digit #{idx + 1} of #{@length}"
          disabled: @disabled
          $filled: (@value[idx])?!
          @input: (e) => @_onInput(e, idx)
          @keydown: (e) => @_onKeydown(e, idx)
          @paste: @_onPaste
          @focus: @_onFocus
