/**
 * Pie / donut chart using Canvas 2D
 * @countermeasure/web-components/charts/pie
 */

import { InteractiveChart } from '../interactive'
import type { LegendItem } from '../types'
import { defaultColors, hexToRgba, formatNumber } from '../utils'
import type { PieChartConfig, PieSlice } from './types'

const DEG_TO_RAD = Math.PI / 180
const LABEL_FONT = '11px system-ui, sans-serif'
const VALUE_FONT = 'bold 11px system-ui, sans-serif'
const HOVER_PULL = 6
const HOVER_ALPHA = 0.15

interface ResolvedSlice {
  label: string
  value: number
  color: string
  startAngle: number
  endAngle: number
  midAngle: number
  percentage: number
}

export class PieChart extends InteractiveChart {
  private slices: PieSlice[]
  private resolved: ResolvedSlice[] = []
  private colors: string[]
  private donut: boolean
  private donutRatio: number
  private showLabels: boolean
  private showValues: boolean
  private startAngle: number
  private hoveredIndex = -1

  private activeSlices: boolean[]

  constructor(config: PieChartConfig) {
    super(config)
    this.slices = config.slices
    this.colors = defaultColors()
    this.donut = config.donut ?? false
    this.donutRatio = config.donutRatio ?? 0.6
    this.showLabels = config.showLabels ?? false
    this.showValues = config.showValues ?? false
    this.startAngle = config.startAngle ?? -90
    this.activeSlices = config.slices.map(() => true)

    if (this.legend !== null) {
      this.legend.onToggle((index, active) => {
        if (index < this.activeSlices.length) {
          this.activeSlices[index] = active
        }
        this.resolveSlices()
        this.render()
      })
    }

    this.resolveSlices()
    this.render()
  }

  /** Replace the slices and re-render */
  update(slices: PieSlice[]): void {
    this.slices = slices
    this.activeSlices = slices.map(() => true)
    this.resolveSlices()
    this.updateLegend()
    this.render()
  }

  render(): void {
    this.clear()
    const plot = this.plotArea
    const cx = plot.x + plot.width / 2
    const cy = plot.y + plot.height / 2
    const radius = Math.min(plot.width, plot.height) / 2
    const innerRadius = this.donut ? radius * this.donutRatio : 0

    for (let i = 0; i < this.resolved.length; i++) {
      const slice = this.resolved[i]
      if (slice === undefined) continue

      const isHovered = i === this.hoveredIndex
      let pullX = 0
      let pullY = 0

      if (isHovered) {
        const midRad = slice.midAngle * DEG_TO_RAD
        pullX = Math.cos(midRad) * HOVER_PULL
        pullY = Math.sin(midRad) * HOVER_PULL
      }

      const sCx = cx + pullX
      const sCy = cy + pullY

      this.ctx.save()
      this.ctx.beginPath()
      this.ctx.moveTo(
        sCx + Math.cos(slice.startAngle * DEG_TO_RAD) * innerRadius,
        sCy + Math.sin(slice.startAngle * DEG_TO_RAD) * innerRadius
      )
      this.ctx.arc(sCx, sCy, radius, slice.startAngle * DEG_TO_RAD, slice.endAngle * DEG_TO_RAD)
      this.ctx.arc(
        sCx,
        sCy,
        innerRadius,
        slice.endAngle * DEG_TO_RAD,
        slice.startAngle * DEG_TO_RAD,
        true
      )
      this.ctx.closePath()

      this.ctx.fillStyle = isHovered ? hexToRgba(slice.color, 1 - HOVER_ALPHA) : slice.color
      this.ctx.fill()

      // Thin white border between slices
      this.ctx.strokeStyle = '#ffffff'
      this.ctx.lineWidth = 1.5
      this.ctx.stroke()
      this.ctx.restore()

      // Labels and values at midpoint of arc
      if (this.showLabels || this.showValues) {
        const labelRadius = innerRadius + (radius - innerRadius) * 0.65
        const midRad = slice.midAngle * DEG_TO_RAD
        const lx = cx + Math.cos(midRad) * labelRadius
        const ly = cy + Math.sin(midRad) * labelRadius

        this.ctx.save()
        this.ctx.textAlign = 'center'
        this.ctx.textBaseline = 'middle'
        this.ctx.fillStyle = '#ffffff'

        if (this.showLabels && this.showValues) {
          this.ctx.font = LABEL_FONT
          this.ctx.fillText(slice.label, lx, ly - 7)
          this.ctx.font = VALUE_FONT
          this.ctx.fillText(`${slice.percentage.toFixed(1)}%`, lx, ly + 7)
        } else if (this.showLabels) {
          this.ctx.font = LABEL_FONT
          this.ctx.fillText(slice.label, lx, ly)
        } else {
          this.ctx.font = VALUE_FONT
          this.ctx.fillText(`${slice.percentage.toFixed(1)}%`, lx, ly)
        }

        this.ctx.restore()
      }
    }

    this.updateLegend()
  }

  private resolveSlices(): void {
    const total = this.computeTotal()
    if (total <= 0) {
      this.resolved = []
      return
    }

    const result: ResolvedSlice[] = []
    let currentAngle = this.startAngle

    for (let i = 0; i < this.slices.length; i++) {
      const s = this.slices[i]
      if (s === undefined) continue

      const isActive = this.activeSlices[i] ?? true
      if (!isActive) continue

      const percentage = (s.value / total) * 100
      const sweep = (s.value / total) * 360
      const color = s.color ?? this.colors[i % this.colors.length] ?? '#3b82f6'

      result.push({
        label: s.label,
        value: s.value,
        color,
        startAngle: currentAngle,
        endAngle: currentAngle + sweep,
        midAngle: currentAngle + sweep / 2,
        percentage,
      })

      currentAngle += sweep
    }

    this.resolved = result
  }

  private computeTotal(): number {
    let total = 0
    for (let i = 0; i < this.slices.length; i++) {
      const s = this.slices[i]
      if (s === undefined) continue
      const isActive = this.activeSlices[i] ?? true
      if (isActive) {
        total += s.value
      }
    }
    return total
  }

  private updateLegend(): void {
    if (this.legend === null) return
    const items: LegendItem[] = []
    for (let i = 0; i < this.slices.length; i++) {
      const s = this.slices[i]
      if (s === undefined) continue
      const color = s.color ?? this.colors[i % this.colors.length] ?? '#3b82f6'
      const active = this.activeSlices[i] ?? true
      items.push({ label: s.label, color, active })
    }
    this.legend.update(items)
  }

  protected handleMouseMove(e: MouseEvent): void {
    const rect = this.canvas.getBoundingClientRect()
    const mx = e.clientX - rect.left
    const my = e.clientY - rect.top

    const plot = this.plotArea
    const cx = plot.x + plot.width / 2
    const cy = plot.y + plot.height / 2
    const radius = Math.min(plot.width, plot.height) / 2
    const innerRadius = this.donut ? radius * this.donutRatio : 0

    const dx = mx - cx
    const dy = my - cy
    const dist = Math.sqrt(dx * dx + dy * dy)

    if (dist < innerRadius || dist > radius) {
      if (this.hoveredIndex !== -1) {
        this.hoveredIndex = -1
        if (this.tooltip !== null) {
          this.tooltip.hide()
        }
        this.render()
      }
      return
    }

    // Compute angle in degrees matching our coordinate system
    let angle = Math.atan2(dy, dx) * (180 / Math.PI)

    // Normalize angle to be within range of our slices
    const firstSlice = this.resolved[0]
    if (firstSlice !== undefined) {
      while (angle < firstSlice.startAngle) {
        angle += 360
      }
    }

    let found = -1
    for (let i = 0; i < this.resolved.length; i++) {
      const slice = this.resolved[i]
      if (slice === undefined) continue
      let start = slice.startAngle
      let end = slice.endAngle
      // Normalize for comparison
      while (start > angle + 360) {
        start -= 360
        end -= 360
      }
      while (end < angle - 360) {
        start += 360
        end += 360
      }
      if (angle >= start && angle < end) {
        found = i
        break
      }
    }

    if (found !== this.hoveredIndex) {
      this.hoveredIndex = found
      this.render()
    }

    if (found !== -1 && this.tooltip !== null) {
      const slice = this.resolved[found]
      if (slice !== undefined) {
        const content = `<strong>${slice.label}</strong><br/>${formatNumber(slice.value)} (${slice.percentage.toFixed(1)}%)`
        this.tooltip.show(mx, my, content)
      }
    } else if (this.tooltip !== null) {
      this.tooltip.hide()
    }
  }

  protected handleMouseLeave(): void {
    if (this.hoveredIndex !== -1) {
      this.hoveredIndex = -1
      if (this.tooltip !== null) {
        this.tooltip.hide()
      }
      this.render()
    }
  }
}
