/**
 * NativeSelect - Styled native select element wrapper
 * @countermeasure/web-components/primitives/select
 */

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

export class NativeSelect {
  public readonly el: HTMLElement

  private readonly selectEl: HTMLSelectElement
  private config: NativeSelectConfig
  private currentOptions: SelectOption[]
  private isDestroyed = false

  private readonly handleChange: () => void

  constructor(config: NativeSelectConfig) {
    this.config = config
    this.currentOptions = [...config.options]

    this.el = document.createElement('div')
    this.el.className = 'cmm-native-select'
    if (config.className) {
      this.el.classList.add(config.className)
    }

    this.selectEl = document.createElement('select')
    this.selectEl.className = 'cmm-native-select__select'
    this.selectEl.id = uniqueId('cmm-native-select')

    if (config.name) {
      this.selectEl.name = config.name
    }

    if (config.required === true) {
      this.selectEl.required = true
    }

    if (config.disabled === true) {
      this.selectEl.disabled = true
      this.el.classList.add('cmm-native-select--disabled')
    }

    const nativeAriaLabel = config.ariaLabel ?? 'Select an option'
    this.selectEl.setAttribute('aria-label', nativeAriaLabel)

    this.populateOptions()

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

    const chevron = document.createElement('span')
    chevron.className = 'cmm-native-select__chevron'
    chevron.setAttribute('aria-hidden', 'true')
    chevron.innerHTML =
      '<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>'

    this.el.appendChild(this.selectEl)
    this.el.appendChild(chevron)

    this.handleChange = () => {
      if (this.isDestroyed) {
        return
      }
      this.config.onChange?.(this.selectEl.value)
    }

    this.selectEl.addEventListener('change', this.handleChange)

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

  getValue(): string {
    return this.selectEl.value
  }

  setValue(value: string): void {
    this.selectEl.value = value
  }

  setOptions(options: SelectOption[]): void {
    this.currentOptions = [...options]
    this.populateOptions()
  }

  setDisabled(disabled: boolean): void {
    this.selectEl.disabled = disabled
    if (disabled) {
      this.el.classList.add('cmm-native-select--disabled')
    } else {
      this.el.classList.remove('cmm-native-select--disabled')
    }
  }

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

  private populateOptions(): void {
    this.selectEl.innerHTML = ''

    const groups = new Map<string, SelectOption[]>()
    const ungrouped: SelectOption[] = []

    for (const option of this.currentOptions) {
      if (option.group) {
        const groupList = groups.get(option.group)
        if (groupList !== undefined) {
          groupList.push(option)
        } else {
          groups.set(option.group, [option])
        }
      } else {
        ungrouped.push(option)
      }
    }

    for (const option of ungrouped) {
      this.selectEl.appendChild(this.createOptionEl(option))
    }

    for (const [groupName, groupOptions] of groups) {
      const optgroup = document.createElement('optgroup')
      optgroup.label = groupName
      for (const option of groupOptions) {
        optgroup.appendChild(this.createOptionEl(option))
      }
      this.selectEl.appendChild(optgroup)
    }
  }

  private createOptionEl(option: SelectOption): HTMLOptionElement {
    const optionEl = document.createElement('option')
    optionEl.value = option.value
    optionEl.textContent = option.label
    if (option.disabled === true) {
      optionEl.disabled = true
    }
    return optionEl
  }
}

export { type NativeSelectConfig } from './types'
