/**
 * Sparkline chart using Canvas 2D
 * @countermeasure/web-components/charts/sparkline
 */

import { BaseChart } from '../base'
import { defaultColors, hexToRgba } from '../utils'
import type { SparklineChartConfig } from './types'

const DEFAULT_LINE_WIDTH = 1.5
const DEFAULT_SPARKLINE_PADDING = 2
const DOT_RADIUS = 2.5
const MIN_MAX_DOT_RADIUS = 2

export class SparklineChart extends BaseChart {
  private data: number[]
  private color: string
  private fillColor: string | undefined
  private lineWidth: number
  private showEndDot: boolean
  private showMinMax: boolean
  private chartType: 'line' | 'bar'

  constructor(config: SparklineChartConfig) {
    // Override padding to minimal values for sparklines
    const sparkConfig = {
      ...config,
      padding: {
        top: config.padding?.top ?? DEFAULT_SPARKLINE_PADDING,
        right: config.padding?.right ?? DEFAULT_SPARKLINE_PADDING,
        bottom: config.padding?.bottom ?? DEFAULT_SPARKLINE_PADDING,
        left: config.padding?.left ?? DEFAULT_SPARKLINE_PADDING,
      },
    }
    super(sparkConfig)
    this.data = config.data
    this.color = config.color ?? defaultColors()[0] ?? '#3b82f6'
    this.fillColor = config.fillColor
    this.lineWidth = config.lineWidth ?? DEFAULT_LINE_WIDTH
    this.showEndDot = config.showEndDot ?? false
    this.showMinMax = config.showMinMax ?? false
    this.chartType = config.type ?? 'line'

    this.render()
  }

  /** Replace data and re-render */
  update(data: number[]): void {
    this.data = data
    this.render()
  }

  render(): void {
    this.clear()

    if (this.data.length === 0) return

    if (this.chartType === 'bar') {
      this.renderBars()
    } else {
      this.renderLine()
    }
  }

  private renderLine(): void {
    const plot = this.plotArea
    const count = this.data.length
    if (count === 0) return

    const { dataMin, dataMax } = this.computeMinMax()
    const range = dataMax - dataMin
    const effectiveRange = range === 0 ? 1 : range

    const stepX = count > 1 ? plot.width / (count - 1) : 0

    // Build points
    const points: { x: number; y: number }[] = []
    let minIndex = 0
    let maxIndex = 0
    let minVal = Infinity
    let maxVal = -Infinity

    for (let i = 0; i < count; i++) {
      const val = this.data[i]
      if (val === undefined) continue

      const px = plot.x + stepX * i
      const py = plot.y + plot.height - ((val - dataMin) / effectiveRange) * plot.height
      points.push({ x: px, y: py })

      if (val < minVal) {
        minVal = val
        minIndex = points.length - 1
      }
      if (val >= maxVal) {
        maxVal = val
        maxIndex = points.length - 1
      }
    }

    if (points.length === 0) return

    // Optional fill area below line
    if (this.fillColor !== undefined) {
      this.ctx.save()
      this.ctx.beginPath()
      const first = points[0]
      if (first !== undefined) {
        this.ctx.moveTo(first.x, plot.y + plot.height)
        this.ctx.lineTo(first.x, first.y)
      }
      for (let i = 1; i < points.length; i++) {
        const p = points[i]
        if (p !== undefined) {
          this.ctx.lineTo(p.x, p.y)
        }
      }
      const last = points[points.length - 1]
      if (last !== undefined) {
        this.ctx.lineTo(last.x, plot.y + plot.height)
      }
      this.ctx.closePath()
      this.ctx.fillStyle = this.fillColor
      this.ctx.fill()
      this.ctx.restore()
    }

    // Draw line
    this.ctx.save()
    this.ctx.beginPath()
    const first = points[0]
    if (first !== undefined) {
      this.ctx.moveTo(first.x, first.y)
    }
    for (let i = 1; i < points.length; i++) {
      const p = points[i]
      if (p !== undefined) {
        this.ctx.lineTo(p.x, p.y)
      }
    }
    this.ctx.strokeStyle = this.color
    this.ctx.lineWidth = this.lineWidth
    this.ctx.lineJoin = 'round'
    this.ctx.lineCap = 'round'
    this.ctx.stroke()
    this.ctx.restore()

    // End dot
    if (this.showEndDot && points.length > 0) {
      const endPoint = points[points.length - 1]
      if (endPoint !== undefined) {
        this.drawDot(endPoint.x, endPoint.y, DOT_RADIUS, this.color)
      }
    }

    // Min/max dots
    if (this.showMinMax && points.length > 1) {
      const minPoint = points[minIndex]
      const maxPoint = points[maxIndex]
      if (minPoint !== undefined) {
        this.drawDot(minPoint.x, minPoint.y, MIN_MAX_DOT_RADIUS, '#ef4444')
      }
      if (maxPoint !== undefined) {
        this.drawDot(maxPoint.x, maxPoint.y, MIN_MAX_DOT_RADIUS, '#22c55e')
      }
    }
  }

  private renderBars(): void {
    const plot = this.plotArea
    const count = this.data.length
    if (count === 0) return

    const { dataMin, dataMax } = this.computeMinMax()
    const range = dataMax - dataMin
    const effectiveRange = range === 0 ? 1 : range

    const barWidth = Math.max(1, plot.width / count - 1)
    const stepX = plot.width / count

    this.ctx.save()

    for (let i = 0; i < count; i++) {
      const val = this.data[i]
      if (val === undefined) continue

      const barHeight = ((val - dataMin) / effectiveRange) * plot.height
      const bx = plot.x + stepX * i + (stepX - barWidth) / 2
      const by = plot.y + plot.height - barHeight

      this.ctx.fillStyle = hexToRgba(this.color, 0.8)
      this.ctx.fillRect(bx, by, barWidth, barHeight)
    }

    this.ctx.restore()
  }

  private computeMinMax(): { dataMin: number; dataMax: number } {
    let dataMin = Infinity
    let dataMax = -Infinity

    for (const v of this.data) {
      if (v < dataMin) dataMin = v
      if (v > dataMax) dataMax = v
    }

    if (!isFinite(dataMin)) dataMin = 0
    if (!isFinite(dataMax)) dataMax = 1

    return { dataMin, dataMax }
  }

  private drawDot(x: number, y: number, r: number, color: string): void {
    this.ctx.save()
    this.ctx.beginPath()
    this.ctx.arc(x, y, r, 0, Math.PI * 2)
    this.ctx.fillStyle = color
    this.ctx.fill()
    this.ctx.strokeStyle = '#ffffff'
    this.ctx.lineWidth = 1
    this.ctx.stroke()
    this.ctx.restore()
  }
}
