/**
 * RadioGroup - Radio button group with keyboard navigation
 * @countermeasure/web-components/primitives/radio
 */

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

export class RadioGroup {
  public readonly el: HTMLElement

  private readonly radioEls: HTMLInputElement[] = []
  private readonly labelEls: HTMLLabelElement[] = []
  private config: RadioGroupConfig
  private groupName: string
  private selectedValue: string | null = null
  private isDestroyed = false

  private readonly handleChange: (e: Event) => void
  private readonly handleKeydown: (e: KeyboardEvent) => void

  constructor(config: RadioGroupConfig) {
    this.config = config
    this.groupName = config.name ?? uniqueId('cmm-radio-group')

    if (config.value !== undefined) {
      this.selectedValue = config.value
    }

    const orientation = config.orientation ?? 'vertical'

    this.el = document.createElement('div')
    this.el.className = `cmm-radio-group cmm-radio-group--${orientation}`
    this.el.setAttribute('role', 'radiogroup')
    this.el.id = uniqueId('cmm-radio-group')

    if (config.ariaLabel) {
      this.el.setAttribute('aria-label', config.ariaLabel)
    }

    if (config.className) {
      this.el.classList.add(config.className)
    }

    this.buildRadios(config.options)

    this.handleChange = (e: Event) => {
      if (this.isDestroyed) {
        return
      }
      const target = e.target as HTMLInputElement
      this.selectedValue = target.value
      this.config.onChange?.(target.value)
    }

    this.handleKeydown = (e: KeyboardEvent) => {
      if (this.isDestroyed) {
        return
      }
      this.onKeydown(e)
    }

    this.el.addEventListener('change', this.handleChange)
    this.el.addEventListener('keydown', this.handleKeydown)

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

  getValue(): string | null {
    return this.selectedValue
  }

  setValue(value: string): void {
    this.selectedValue = value
    for (const radio of this.radioEls) {
      radio.checked = radio.value === value
    }
  }

  setDisabled(disabled: boolean): void {
    for (let i = 0; i < this.radioEls.length; i++) {
      const radio = this.radioEls[i]
      const label = this.labelEls[i]
      if (radio !== undefined) {
        radio.disabled = disabled
      }
      if (label !== undefined) {
        if (disabled) {
          label.classList.add('cmm-radio--disabled')
        } else {
          label.classList.remove('cmm-radio--disabled')
        }
      }
    }
  }

  destroy(): void {
    this.isDestroyed = true
    this.el.removeEventListener('change', this.handleChange)
    this.el.removeEventListener('keydown', this.handleKeydown)
    this.el.remove()
  }

  private buildRadios(options: RadioOption[]): void {
    for (const option of options) {
      const label = document.createElement('label')
      label.className = 'cmm-radio'

      const isDisabled = this.config.disabled === true || option.disabled === true
      if (isDisabled) {
        label.classList.add('cmm-radio--disabled')
      }

      const input = document.createElement('input')
      input.type = 'radio'
      input.className = 'cmm-radio__input'
      input.name = this.groupName
      input.value = option.value
      input.id = uniqueId('cmm-radio')

      if (option.value === this.selectedValue) {
        input.checked = true
      }

      if (isDisabled) {
        input.disabled = true
      }

      const circle = document.createElement('span')
      circle.className = 'cmm-radio__circle'
      circle.setAttribute('aria-hidden', 'true')

      const labelText = document.createElement('span')
      labelText.className = 'cmm-radio__label'
      labelText.textContent = option.label

      label.appendChild(input)
      label.appendChild(circle)
      label.appendChild(labelText)

      this.el.appendChild(label)
      this.radioEls.push(input)
      this.labelEls.push(label)
    }
  }

  private onKeydown(e: KeyboardEvent): void {
    const enabledRadios = this.radioEls.filter(r => !r.disabled)
    if (enabledRadios.length === 0) {
      return
    }

    let currentIndex = -1
    const activeEl = document.activeElement
    if (activeEl !== null) {
      currentIndex = enabledRadios.indexOf(activeEl as HTMLInputElement)
    }

    let nextIndex: number

    switch (e.key) {
      case 'ArrowDown':
      case 'ArrowRight': {
        e.preventDefault()
        if (currentIndex < 0) {
          nextIndex = 0
        } else {
          nextIndex = currentIndex < enabledRadios.length - 1 ? currentIndex + 1 : 0
        }
        break
      }
      case 'ArrowUp':
      case 'ArrowLeft': {
        e.preventDefault()
        if (currentIndex < 0) {
          nextIndex = enabledRadios.length - 1
        } else {
          nextIndex = currentIndex > 0 ? currentIndex - 1 : enabledRadios.length - 1
        }
        break
      }
      default:
        return
    }

    const nextRadio = enabledRadios[nextIndex]
    if (nextRadio !== undefined) {
      nextRadio.checked = true
      nextRadio.focus()
      this.selectedValue = nextRadio.value
      this.config.onChange?.(nextRadio.value)
    }
  }
}

export { type RadioGroupConfig, type RadioOption } from './types'
