/**
 * Progress - Linear and circular progress indicator
 * @countermeasure/web-components/display/progress
 */

import { resolveContainer, uniqueId } from '../../primitives/utils'
import { type ProgressConfig, type ProgressVariant, type ProgressSize } from './types'

export type { ProgressConfig, ProgressVariant, ProgressSize } from './types'

/** Circumference for SVG circle with r=16: 2 * PI * 16 */
const CIRCUMFERENCE = 2 * Math.PI * 16

/** Clamp a number between 0 and 100 */
function clampValue(value: number): number {
  return Math.min(100, Math.max(0, value))
}

export class Progress {
  public readonly el: HTMLDivElement

  private readonly progressId: string
  private readonly variant: ProgressVariant
  private readonly size: ProgressSize
  private readonly showLabel: boolean
  private currentValue: number | undefined
  private currentLabel: string | undefined

  /** References to inner elements for updates */
  private fillEl: HTMLDivElement | null = null
  private labelEl: HTMLSpanElement | SVGTextElement | null = null
  private circleFillEl: SVGCircleElement | null = null

  constructor(config: ProgressConfig) {
    this.progressId = uniqueId('cmm-progress')
    this.variant = config.variant ?? 'linear'
    this.size = config.size ?? 'md'
    this.showLabel = config.showLabel === true
    this.currentLabel = config.label
    this.currentValue = config.value !== undefined ? clampValue(config.value) : undefined

    // Create root element
    this.el = document.createElement('div')
    this.el.id = this.progressId
    this.el.setAttribute('role', 'progressbar')
    this.el.setAttribute('aria-valuemin', '0')
    this.el.setAttribute('aria-valuemax', '100')

    if (this.currentValue !== undefined) {
      this.el.setAttribute('aria-valuenow', String(this.currentValue))
    }

    this.el.setAttribute('aria-label', config.ariaLabel ?? 'Progress')

    this.el.className = this.buildClassName(config.className)

    if (config.color) {
      this.el.style.setProperty('--cmm-progress-color', config.color)
    }

    if (this.variant === 'linear') {
      this.buildLinear()
    } else {
      this.buildCircular()
    }

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

  /** Update the progress value (0-100) */
  setValue(value: number): void {
    this.currentValue = clampValue(value)
    this.el.setAttribute('aria-valuenow', String(this.currentValue))
    this.el.classList.remove('cmm-progress--indeterminate')

    if (this.variant === 'linear') {
      if (this.fillEl) {
        this.fillEl.style.width = `${String(this.currentValue)}%`
      }
    } else if (this.circleFillEl) {
      const offset = CIRCUMFERENCE * (1 - this.currentValue / 100)
      this.circleFillEl.setAttribute('stroke-dashoffset', String(offset))
    }

    this.updateLabelText()
  }

  /** Set a custom label or null to revert to percentage */
  setLabel(label: string | null): void {
    if (label !== null) {
      this.currentLabel = label
    } else {
      this.currentLabel = undefined
    }
    this.updateLabelText()
  }

  /** Remove the progress from the DOM */
  destroy(): void {
    this.el.remove()
  }

  private buildClassName(extra?: string): string {
    const classes = ['cmm-progress', `cmm-progress--${this.variant}`, `cmm-progress--${this.size}`]

    if (this.currentValue === undefined) {
      classes.push('cmm-progress--indeterminate')
    }

    if (extra) {
      classes.push(extra)
    }

    return classes.join(' ')
  }

  private buildLinear(): void {
    const track = document.createElement('div')
    track.className = 'cmm-progress__track'

    const fill = document.createElement('div')
    fill.className = 'cmm-progress__fill'

    if (this.currentValue !== undefined) {
      fill.style.width = `${String(this.currentValue)}%`
    }

    track.appendChild(fill)
    this.el.appendChild(track)
    this.fillEl = fill

    if (this.showLabel) {
      const label = document.createElement('span')
      label.className = 'cmm-progress__label'
      label.textContent = this.getLabelText()
      this.el.appendChild(label)
      this.labelEl = label
    }
  }

  private buildCircular(): void {
    const ns = 'http://www.w3.org/2000/svg'
    const svg = document.createElementNS(ns, 'svg')
    svg.setAttribute('viewBox', '0 0 36 36')

    // Background circle
    const bgCircle = document.createElementNS(ns, 'circle')
    bgCircle.setAttribute('class', 'cmm-progress__circle-bg')
    bgCircle.setAttribute('cx', '18')
    bgCircle.setAttribute('cy', '18')
    bgCircle.setAttribute('r', '16')
    bgCircle.setAttribute('stroke-width', '3')
    svg.appendChild(bgCircle)

    // Progress circle
    const fillCircle = document.createElementNS(ns, 'circle')
    fillCircle.setAttribute('class', 'cmm-progress__circle-fill')
    fillCircle.setAttribute('cx', '18')
    fillCircle.setAttribute('cy', '18')
    fillCircle.setAttribute('r', '16')
    fillCircle.setAttribute('stroke-width', '3')
    fillCircle.setAttribute('stroke-dasharray', String(CIRCUMFERENCE))

    if (this.currentValue !== undefined) {
      const offset = CIRCUMFERENCE * (1 - this.currentValue / 100)
      fillCircle.setAttribute('stroke-dashoffset', String(offset))
    } else {
      // Indeterminate: show a partial arc
      const offset = CIRCUMFERENCE * 0.75
      fillCircle.setAttribute('stroke-dashoffset', String(offset))
    }

    svg.appendChild(fillCircle)
    this.circleFillEl = fillCircle

    // Optional label inside circle
    if (this.showLabel) {
      const text = document.createElementNS(ns, 'text')
      text.setAttribute('class', 'cmm-progress__circle-label')
      text.setAttribute('x', '18')
      text.setAttribute('y', '18')
      text.textContent = this.getLabelText()
      svg.appendChild(text)
      this.labelEl = text
    }

    this.el.appendChild(svg)
  }

  private getLabelText(): string {
    if (this.currentLabel !== undefined) {
      return this.currentLabel
    }
    if (this.currentValue !== undefined) {
      return `${String(this.currentValue)}%`
    }
    return ''
  }

  private updateLabelText(): void {
    if (this.labelEl) {
      this.labelEl.textContent = this.getLabelText()
    }
  }
}
