/**
 * TimePicker - Scrollable hour/minute/second column picker
 * @countermeasure/web-components/form/date-picker
 */

import { type TimePickerConfig } from './types'
import { resolveContainer, uniqueId } from '../../primitives/utils'
import { announce } from '../../utils/a11y'

interface TimeValue {
  hours: number
  minutes: number
  seconds: number
}

function padTwo(n: number): string {
  return String(n).padStart(2, '0')
}

export class TimePicker {
  public readonly el: HTMLElement

  private readonly timerId: string
  private config: TimePickerConfig
  private hours: number
  private minutes: number
  private seconds: number
  private isPM = false
  private isDestroyed = false

  private hourCol!: HTMLElement
  private minuteCol!: HTMLElement
  private secondCol: HTMLElement | null = null
  private periodCol: HTMLElement | null = null

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

  constructor(config: TimePickerConfig) {
    this.config = config
    this.timerId = uniqueId('cmm-timepicker')

    const val = config.value ?? { hours: 0, minutes: 0, seconds: 0 }
    this.hours = val.hours
    this.minutes = val.minutes
    this.seconds = val.seconds ?? 0

    if (config.use24Hour !== true) {
      this.isPM = this.hours >= 12
      if (this.hours > 12) this.hours -= 12
      if (this.hours === 0) this.hours = 12
    }

    this.el = document.createElement('div')
    this.el.className = this.buildRootClass()
    this.el.id = this.timerId
    this.el.setAttribute('role', 'group')
    this.el.setAttribute('aria-label', 'Time picker')

    this.buildColumns()

    this.handleKeydown = (e: KeyboardEvent) => {
      if (this.isDestroyed) return
      this.onKeydown(e)
    }
    this.el.addEventListener('keydown', this.handleKeydown)

    const parent = resolveContainer(config.container)
    parent.appendChild(this.el)
  }

  getValue(): TimeValue {
    let h = this.hours
    if (this.config.use24Hour !== true) {
      if (this.isPM && h < 12) h += 12
      if (!this.isPM && h === 12) h = 0
    }
    return { hours: h, minutes: this.minutes, seconds: this.seconds }
  }

  setValue(time: { hours: number; minutes: number; seconds?: number }): void {
    this.hours = time.hours
    this.minutes = time.minutes
    this.seconds = time.seconds ?? 0

    if (this.config.use24Hour !== true) {
      this.isPM = this.hours >= 12
      if (this.hours > 12) this.hours -= 12
      if (this.hours === 0) this.hours = 12
    }

    this.updateColumnHighlights()
    this.emitChange()
  }

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

  private buildRootClass(): string {
    const classes = ['cmm-time-picker']
    if (this.config.className) classes.push(this.config.className)
    return classes.join(' ')
  }

  private buildColumns(): void {
    const use24 = this.config.use24Hour === true
    const maxHour = use24 ? 23 : 12
    const minHour = use24 ? 0 : 1

    // Hours column
    this.hourCol = this.buildColumn('Hours', minHour, maxHour, this.hours, val => {
      this.hours = val
      this.emitChange()
    })
    this.el.appendChild(this.hourCol)

    // Separator
    this.el.appendChild(this.buildSeparator())

    // Minutes column
    this.minuteCol = this.buildColumn('Minutes', 0, 59, this.minutes, val => {
      this.minutes = val
      this.emitChange()
    })
    this.el.appendChild(this.minuteCol)

    // Seconds column
    if (this.config.showSeconds !== false) {
      this.el.appendChild(this.buildSeparator())
      this.secondCol = this.buildColumn('Seconds', 0, 59, this.seconds, val => {
        this.seconds = val
        this.emitChange()
      })
      this.el.appendChild(this.secondCol)
    }

    // AM/PM column
    if (!use24) {
      this.periodCol = document.createElement('div')
      this.periodCol.className = 'cmm-time-picker__column cmm-time-picker__period'
      this.periodCol.setAttribute('role', 'listbox')
      this.periodCol.setAttribute('aria-label', 'Period')
      this.periodCol.tabIndex = 0

      for (const label of ['AM', 'PM']) {
        const item = document.createElement('button')
        item.type = 'button'
        item.className = 'cmm-time-picker__item'
        item.setAttribute('role', 'option')
        item.textContent = label
        item.setAttribute('data-value', label)

        const isActive = label === 'PM' ? this.isPM : !this.isPM
        if (isActive) {
          item.classList.add('cmm-time-picker__item--selected')
          item.setAttribute('aria-selected', 'true')
        } else {
          item.setAttribute('aria-selected', 'false')
        }

        item.addEventListener('click', () => {
          if (this.isDestroyed) return
          this.isPM = label === 'PM'
          this.updatePeriodHighlight()
          this.emitChange()
        })

        this.periodCol.appendChild(item)
      }

      this.el.appendChild(this.periodCol)
    }
  }

  private buildColumn(
    label: string,
    min: number,
    max: number,
    current: number,
    onSelect: (val: number) => void
  ): HTMLElement {
    const col = document.createElement('div')
    col.className = 'cmm-time-picker__column'
    col.setAttribute('role', 'listbox')
    col.setAttribute('aria-label', label)
    col.tabIndex = 0

    for (let i = min; i <= max; i++) {
      const item = document.createElement('button')
      item.type = 'button'
      item.className = 'cmm-time-picker__item'
      item.setAttribute('role', 'option')
      item.textContent = padTwo(i)
      item.setAttribute('data-value', String(i))

      if (i === current) {
        item.classList.add('cmm-time-picker__item--selected')
        item.setAttribute('aria-selected', 'true')
      } else {
        item.setAttribute('aria-selected', 'false')
      }

      item.addEventListener('click', () => {
        if (this.isDestroyed) return
        this.clearColumnSelection(col)
        item.classList.add('cmm-time-picker__item--selected')
        item.setAttribute('aria-selected', 'true')
        onSelect(i)
        item.scrollIntoView({ block: 'nearest' })
      })

      col.appendChild(item)
    }

    // Scroll selected into view after mount
    requestAnimationFrame(() => {
      const selected = col.querySelector('.cmm-time-picker__item--selected')
      selected?.scrollIntoView({ block: 'center' })
    })

    return col
  }

  private buildSeparator(): HTMLElement {
    const sep = document.createElement('span')
    sep.className = 'cmm-time-picker__separator'
    sep.textContent = ':'
    sep.setAttribute('aria-hidden', 'true')
    return sep
  }

  private clearColumnSelection(col: HTMLElement): void {
    const items = col.querySelectorAll('.cmm-time-picker__item')
    for (const item of items) {
      item.classList.remove('cmm-time-picker__item--selected')
      item.setAttribute('aria-selected', 'false')
    }
  }

  private updateColumnHighlights(): void {
    this.selectValueInColumn(this.hourCol, this.hours)
    this.selectValueInColumn(this.minuteCol, this.minutes)
    if (this.secondCol !== null) {
      this.selectValueInColumn(this.secondCol, this.seconds)
    }
    if (this.periodCol !== null) {
      this.updatePeriodHighlight()
    }
  }

  private selectValueInColumn(col: HTMLElement, value: number): void {
    this.clearColumnSelection(col)
    const item = col.querySelector<HTMLElement>(`[data-value="${String(value)}"]`)
    if (item !== null) {
      item.classList.add('cmm-time-picker__item--selected')
      item.setAttribute('aria-selected', 'true')
      item.scrollIntoView({ block: 'center' })
    }
  }

  private updatePeriodHighlight(): void {
    if (this.periodCol === null) return
    const items = this.periodCol.querySelectorAll<HTMLElement>('.cmm-time-picker__item')
    for (const item of items) {
      const val = item.getAttribute('data-value')
      const isActive = val === 'PM' ? this.isPM : !this.isPM
      if (isActive) {
        item.classList.add('cmm-time-picker__item--selected')
        item.setAttribute('aria-selected', 'true')
      } else {
        item.classList.remove('cmm-time-picker__item--selected')
        item.setAttribute('aria-selected', 'false')
      }
    }
  }

  private emitChange(): void {
    const val = this.getValue()
    this.config.onChange?.(val)
  }

  private onKeydown(e: KeyboardEvent): void {
    const col = (e.target as HTMLElement).closest<HTMLElement>('.cmm-time-picker__column')
    if (col === null) return

    const items = Array.from(col.querySelectorAll<HTMLElement>('.cmm-time-picker__item'))
    const currentIdx = items.findIndex(item =>
      item.classList.contains('cmm-time-picker__item--selected')
    )

    let nextIdx = currentIdx
    let handled = true

    switch (e.key) {
      case 'ArrowUp':
        nextIdx = currentIdx > 0 ? currentIdx - 1 : items.length - 1
        break
      case 'ArrowDown':
        nextIdx = currentIdx < items.length - 1 ? currentIdx + 1 : 0
        break
      case 'PageUp':
        // Jump by 5 items (like hours or 5 minutes at a time)
        nextIdx = Math.max(0, currentIdx - 5)
        break
      case 'PageDown':
        // Jump by 5 items
        nextIdx = Math.min(items.length - 1, currentIdx + 5)
        break
      case 'Home':
        nextIdx = 0
        break
      case 'End':
        nextIdx = items.length - 1
        break
      default:
        handled = false
    }

    if (handled && nextIdx !== currentIdx) {
      e.preventDefault()
      const item = items[nextIdx]
      if (item !== undefined) {
        item.click()

        // Announce the selected value
        const value = item.textContent
        const label = col.getAttribute('aria-label')
        announce(`${label ?? 'Value'}: ${value}`, 'polite')
      }
    }
  }
}
