/**
 * MetricTile - Compact metric display primitive
 * @countermeasure/web-components/display/metric-tile
 */

import { type MetricTileConfig, type MetricTileTone } from './types'
import { resolveContainer, uniqueId } from '../../primitives/utils'
import { setSafeIconHtml } from '../../utils/sanitize'

export type { MetricTileConfig, MetricTileTone } from './types'

export class MetricTile {
  public readonly el: HTMLDivElement

  private readonly metricTileId: string
  private readonly clickHandler: (() => void) | undefined
  private keydownHandler: ((e: KeyboardEvent) => void) | null = null
  private labelEl: HTMLSpanElement
  private valueEl: HTMLDivElement
  private detailEl: HTMLDivElement | null = null
  private sparklineEl: HTMLDivElement | null = null

  constructor(config: MetricTileConfig) {
    this.metricTileId = uniqueId('cmm-metric-tile')
    this.clickHandler = config.onClick

    this.el = document.createElement('div')
    this.el.id = this.metricTileId
    this.el.className = this.buildClassName(config)

    const headerEl = document.createElement('div')
    headerEl.className = 'cmm-metric-tile__header'

    if (config.icon !== undefined) {
      const iconEl = document.createElement('span')
      iconEl.className = 'cmm-metric-tile__icon'
      iconEl.setAttribute('aria-hidden', 'true')
      this.setIconContent(iconEl, config.icon)
      headerEl.appendChild(iconEl)
    }

    this.labelEl = document.createElement('span')
    this.labelEl.className = 'cmm-metric-tile__label'
    this.labelEl.textContent = config.label
    headerEl.appendChild(this.labelEl)
    this.el.appendChild(headerEl)

    this.valueEl = document.createElement('div')
    this.valueEl.className = 'cmm-metric-tile__value'
    this.valueEl.textContent = this.formatValue(config.value)
    this.el.appendChild(this.valueEl)

    if (config.detail !== undefined) {
      this.detailEl = this.createDetailEl(config.detail)
      this.el.appendChild(this.detailEl)
    }

    if (config.sparkline !== undefined) {
      this.sparklineEl = this.createSparklineEl(config.sparkline)
      this.el.appendChild(this.sparklineEl)
    }

    if (this.clickHandler !== undefined) {
      this.el.setAttribute('role', 'button')
      this.el.setAttribute('tabindex', '0')
      this.el.classList.add('cmm-metric-tile--clickable')
      this.el.addEventListener('click', this.clickHandler)

      this.keydownHandler = (e: KeyboardEvent) => {
        if (e.key === 'Enter' || e.key === ' ') {
          e.preventDefault()
          this.clickHandler?.()
        }
      }
      this.el.addEventListener('keydown', this.keydownHandler)
    }

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

  setLabel(label: string): void {
    this.labelEl.textContent = label
  }

  setValue(value: string | number): void {
    this.valueEl.textContent = this.formatValue(value)
  }

  setDetail(detail: string | HTMLElement | null): void {
    if (detail === null) {
      this.detailEl?.remove()
      this.detailEl = null
      return
    }

    if (this.detailEl === null) {
      this.detailEl = this.createDetailEl(detail)
      if (this.sparklineEl !== null) {
        this.el.insertBefore(this.detailEl, this.sparklineEl)
      } else {
        this.el.appendChild(this.detailEl)
      }
      return
    }

    this.setTextOrElement(this.detailEl, detail)
  }

  setSparkline(sparkline: HTMLElement | SVGElement | null): void {
    if (sparkline === null) {
      this.sparklineEl?.remove()
      this.sparklineEl = null
      return
    }

    if (this.sparklineEl === null) {
      this.sparklineEl = this.createSparklineEl(sparkline)
      this.el.appendChild(this.sparklineEl)
      return
    }

    this.sparklineEl.textContent = ''
    this.sparklineEl.appendChild(sparkline)
  }

  setTone(tone: MetricTileTone): void {
    for (const className of Array.from(this.el.classList)) {
      if (className.startsWith('cmm-metric-tile--tone-')) {
        this.el.classList.remove(className)
      }
    }
    this.el.classList.add(`cmm-metric-tile--tone-${tone}`)
  }

  destroy(): void {
    if (this.clickHandler !== undefined) {
      this.el.removeEventListener('click', this.clickHandler)
    }
    if (this.keydownHandler !== null) {
      this.el.removeEventListener('keydown', this.keydownHandler)
    }
    this.el.remove()
  }

  private buildClassName(config: MetricTileConfig): string {
    const tone = config.tone ?? 'default'
    const classes = ['cmm-metric-tile', `cmm-metric-tile--tone-${tone}`]

    if (config.className) {
      classes.push(config.className)
    }

    return classes.join(' ')
  }

  private createDetailEl(detail: string | HTMLElement): HTMLDivElement {
    const detailEl = document.createElement('div')
    detailEl.className = 'cmm-metric-tile__detail'
    this.setTextOrElement(detailEl, detail)
    return detailEl
  }

  private createSparklineEl(sparkline: HTMLElement | SVGElement): HTMLDivElement {
    const sparklineEl = document.createElement('div')
    sparklineEl.className = 'cmm-metric-tile__sparkline'
    sparklineEl.appendChild(sparkline)
    return sparklineEl
  }

  private setIconContent(iconEl: HTMLSpanElement, icon: string | HTMLElement | SVGElement): void {
    if (typeof icon === 'string') {
      setSafeIconHtml(iconEl, icon)
      return
    }

    iconEl.appendChild(icon)
  }

  private setTextOrElement(element: HTMLElement, content: string | HTMLElement): void {
    element.textContent = ''

    if (typeof content === 'string') {
      element.textContent = content
    } else {
      element.appendChild(content)
    }
  }

  private formatValue(value: string | number): string {
    return typeof value === 'number' ? String(value) : value
  }
}
