/**
 * InputOTP - OTP code input with individual character boxes
 * @countermeasure/web-components/primitives/input
 */

import { type InputOTPConfig } from './types'
import { resolveContainer, uniqueId } from '../utils'

export class InputOTP {
  public readonly el: HTMLDivElement

  private readonly otpId: string
  private readonly digitInputs: HTMLInputElement[] = []
  private readonly length: number
  private readonly isMasked: boolean
  private readonly onComplete: ((value: string) => void) | undefined
  private readonly onChange: ((value: string) => void) | undefined
  private readonly inputHandlers = new Map<
    HTMLInputElement,
    {
      input: (e: Event) => void
      keydown: (e: KeyboardEvent) => void
      paste: (e: ClipboardEvent) => void
      focus: () => void
    }
  >()

  constructor(config: InputOTPConfig) {
    this.otpId = uniqueId('cmm-otp')
    this.length = config.length ?? 6
    this.isMasked = config.mask === true
    this.onComplete = config.onComplete
    this.onChange = config.onChange

    // Create container
    this.el = document.createElement('div')
    this.el.className = 'cmm-input-otp'
    this.el.setAttribute('role', 'group')

    if (config.ariaLabel !== undefined) {
      this.el.setAttribute('aria-label', config.ariaLabel)
    } else {
      this.el.setAttribute('aria-label', 'One-time password input')
    }

    // Create individual digit inputs
    for (let i = 0; i < this.length; i++) {
      const input = document.createElement('input')
      input.type = this.isMasked ? 'password' : 'text'
      input.className = 'cmm-input-otp__digit'
      input.maxLength = 1
      input.inputMode = 'numeric'
      input.autocomplete = 'one-time-code'
      input.id = `${this.otpId}-${String(i)}`
      input.setAttribute('aria-label', `Digit ${String(i + 1)} of ${String(this.length)}`)

      if (config.disabled === true) {
        input.disabled = true
      }

      const handlers = {
        input: (e: Event) => {
          this.handleDigitInput(i, e)
        },
        keydown: (e: KeyboardEvent) => {
          this.handleDigitKeydown(i, e)
        },
        paste: (e: ClipboardEvent) => {
          this.handlePaste(e)
        },
        focus: () => {
          input.select()
        },
      }

      input.addEventListener('input', handlers.input)
      input.addEventListener('keydown', handlers.keydown)
      input.addEventListener('paste', handlers.paste)
      input.addEventListener('focus', handlers.focus)

      this.inputHandlers.set(input, handlers)
      this.digitInputs.push(input)

      // Add separator after every 3rd digit (for 6-digit codes)
      if (i < this.length - 1 && (i + 1) % 3 === 0 && this.length === 6) {
        this.el.appendChild(input)
        const separator = document.createElement('span')
        separator.className = 'cmm-input-otp__separator'
        separator.textContent = '-'
        separator.setAttribute('aria-hidden', 'true')
        this.el.appendChild(separator)
      } else {
        this.el.appendChild(input)
      }
    }

    // Mount if container provided
    if (config.container !== undefined) {
      const parent = resolveContainer(config.container)
      parent.appendChild(this.el)
    }
  }

  /** Get the concatenated OTP value */
  getValue(): string {
    return this.digitInputs.map(input => input.value).join('')
  }

  /** Set the OTP value by distributing characters across inputs */
  setValue(value: string): void {
    for (let i = 0; i < this.length; i++) {
      const input = this.digitInputs[i]
      const char = value[i]
      if (input !== undefined) {
        input.value = char ?? ''
      }
    }
    this.notifyChange()
  }

  /** Clear all inputs and focus the first one */
  clear(): void {
    for (const input of this.digitInputs) {
      input.value = ''
    }
    const first = this.digitInputs[0]
    if (first !== undefined) {
      first.focus()
    }
    this.notifyChange()
  }

  /** Toggle disabled state on all digit inputs */
  setDisabled(disabled: boolean): void {
    for (const input of this.digitInputs) {
      input.disabled = disabled
    }
  }

  /** Remove from the DOM and clean up event listeners */
  destroy(): void {
    for (const input of this.digitInputs) {
      const handlers = this.inputHandlers.get(input)
      if (handlers !== undefined) {
        input.removeEventListener('input', handlers.input)
        input.removeEventListener('keydown', handlers.keydown)
        input.removeEventListener('paste', handlers.paste)
        input.removeEventListener('focus', handlers.focus)
      }
    }
    this.inputHandlers.clear()
    this.el.remove()
  }

  private handleDigitInput(index: number, _e: Event): void {
    const input = this.digitInputs[index]
    if (input === undefined) {
      return
    }

    // Only allow single characters
    if (input.value.length > 1) {
      input.value = input.value.slice(0, 1)
    }

    // Auto-focus next input on character entry
    if (input.value.length === 1) {
      const next = this.digitInputs[index + 1]
      if (next !== undefined) {
        next.focus()
      }
    }

    this.notifyChange()

    // Check if all inputs are filled
    const value = this.getValue()
    if (value.length === this.length) {
      this.onComplete?.(value)
    }
  }

  private handleDigitKeydown(index: number, e: KeyboardEvent): void {
    const input = this.digitInputs[index]
    if (input === undefined) {
      return
    }

    if (e.key === 'Backspace') {
      if (input.value === '') {
        // Move to previous input on backspace when current is empty
        const prev = this.digitInputs[index - 1]
        if (prev !== undefined) {
          prev.focus()
          prev.value = ''
          this.notifyChange()
        }
      }
    } else if (e.key === 'ArrowLeft') {
      const prev = this.digitInputs[index - 1]
      if (prev !== undefined) {
        prev.focus()
      }
    } else if (e.key === 'ArrowRight') {
      const next = this.digitInputs[index + 1]
      if (next !== undefined) {
        next.focus()
      }
    }
  }

  private handlePaste(e: ClipboardEvent): void {
    e.preventDefault()
    const pastedData = e.clipboardData?.getData('text') ?? ''
    const chars = pastedData.replace(/\s/g, '').slice(0, this.length)

    for (let i = 0; i < this.length; i++) {
      const input = this.digitInputs[i]
      const char = chars[i]
      if (input !== undefined) {
        input.value = char ?? ''
      }
    }

    // Focus the input after the last pasted character or the last input
    const focusIndex = Math.min(chars.length, this.length - 1)
    const target = this.digitInputs[focusIndex]
    if (target !== undefined) {
      target.focus()
    }

    this.notifyChange()

    if (chars.length >= this.length) {
      this.onComplete?.(this.getValue())
    }
  }

  private notifyChange(): void {
    this.onChange?.(this.getValue())
  }
}
