/**
 * Waterfall chart component
 * @countermeasure/web-components/charts/waterfall
 */

import { InteractiveChart } from '../interactive'
import type { LegendItem } from '../types'
import { scaleLinear, scaleBand, niceScale, drawFilledBar, hitTestRect } from '../utils'
import { drawXAxis, drawYAxis } from '../axis'
import type { WaterfallChartConfig, WaterfallItem } from './types'

const DEFAULT_INCREASE_COLOR = '#22c55e'
const DEFAULT_DECREASE_COLOR = '#ef4444'
const DEFAULT_TOTAL_COLOR = '#3b82f6'
const DEFAULT_BAR_RADIUS = 0
const CONNECTOR_LINE_COLOR = '#94a3b8'

interface ComputedBar {
  x: number
  y: number
  width: number
  height: number
  color: string
  label: string
  value: number
  startValue: number
  endValue: number
}

export class WaterfallChart extends InteractiveChart {
  private items: WaterfallItem[]
  private readonly config: WaterfallChartConfig
  private computedBars: ComputedBar[]
  private hoveredBar: ComputedBar | null

  constructor(config: WaterfallChartConfig) {
    // Always show legend for waterfall charts
    super({ ...config, legend: config.legend ?? { visible: true } })
    this.config = config
    this.items = config.items
    this.computedBars = []
    this.hoveredBar = null
    this.render()
  }

  private getColors(): { increase: string; decrease: string; total: string } {
    return {
      increase: this.config.colors?.increase ?? DEFAULT_INCREASE_COLOR,
      decrease: this.config.colors?.decrease ?? DEFAULT_DECREASE_COLOR,
      total: this.config.colors?.total ?? DEFAULT_TOTAL_COLOR,
    }
  }

  private resolveItemType(item: WaterfallItem): 'increase' | 'decrease' | 'total' {
    if (item.type !== undefined) return item.type
    return item.value >= 0 ? 'increase' : 'decrease'
  }

  private computeWaterfallData(): {
    label: string
    value: number
    type: 'increase' | 'decrease' | 'total'
    startValue: number
    endValue: number
  }[] {
    const result: {
      label: string
      value: number
      type: 'increase' | 'decrease' | 'total'
      startValue: number
      endValue: number
    }[] = []
    let cumulative = 0

    for (const item of this.items) {
      const itemType = this.resolveItemType(item)

      if (itemType === 'total') {
        result.push({
          label: item.label,
          value: cumulative,
          type: 'total',
          startValue: 0,
          endValue: cumulative,
        })
      } else {
        const start = cumulative
        cumulative += item.value
        result.push({
          label: item.label,
          value: item.value,
          type: itemType,
          startValue: start,
          endValue: cumulative,
        })
      }
    }

    return result
  }

  render(): void {
    this.clear()
    this.computedBars = []

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

    const plot = this.plotArea
    const labels = data.map(d => d.label)
    const colors = this.getColors()
    const barRadius = this.config.barRadius ?? DEFAULT_BAR_RADIUS
    const showConnectors = this.config.showConnectors !== false

    // Compute y range
    let yMin = 0
    let yMax = 0
    for (const d of data) {
      const lo = Math.min(d.startValue, d.endValue)
      const hi = Math.max(d.startValue, d.endValue)
      if (lo < yMin) yMin = lo
      if (hi > yMax) yMax = hi
    }

    const tickCount = this.config.yAxis?.tickCount ?? 5
    const nice = niceScale(
      this.config.yAxis?.min ?? yMin,
      this.config.yAxis?.max ?? yMax,
      tickCount
    )

    // Draw axes
    drawXAxis(this.ctx, plot, labels, this.config.xAxis)
    drawYAxis(this.ctx, plot, nice.min, nice.max, this.config.yAxis)

    // Create scales
    const xScale = scaleBand(labels, [plot.x, plot.x + plot.width], 8)
    const yScale = scaleLinear([nice.min, nice.max], [plot.y + plot.height, plot.y])
    const bandwidth = xScale.bandwidth()

    // Draw bars and connectors
    for (let i = 0; i < data.length; i++) {
      const d = data[i]
      if (d === undefined) continue

      const barX = xScale(d.label)
      const topY = yScale(Math.max(d.startValue, d.endValue))
      const bottomY = yScale(Math.min(d.startValue, d.endValue))
      const barHeight = bottomY - topY

      let barColor: string
      if (d.type === 'total') {
        barColor = colors.total
      } else if (d.type === 'increase') {
        barColor = colors.increase
      } else {
        barColor = colors.decrease
      }

      const bar: ComputedBar = {
        x: barX,
        y: topY,
        width: bandwidth,
        height: Math.max(barHeight, 1),
        color: barColor,
        label: d.label,
        value: d.value,
        startValue: d.startValue,
        endValue: d.endValue,
      }
      this.computedBars.push(bar)

      // Draw connector line from previous bar
      if (showConnectors && i > 0) {
        const prevData = data[i - 1]
        if (prevData !== undefined) {
          const prevBarX = xScale(prevData.label)
          const connectorY = yScale(prevData.endValue)
          this.ctx.beginPath()
          this.ctx.moveTo(prevBarX + bandwidth, connectorY)
          this.ctx.lineTo(barX, connectorY)
          this.ctx.strokeStyle = CONNECTOR_LINE_COLOR
          this.ctx.lineWidth = 1
          this.ctx.setLineDash([3, 3])
          this.ctx.stroke()
          this.ctx.setLineDash([])
        }
      }

      // Draw bar with hover highlight
      drawFilledBar(
        this.ctx,
        bar.x,
        bar.y,
        bar.width,
        bar.height,
        barRadius,
        barColor,
        this.isBarHovered(bar)
      )
    }

    // Update legend
    if (this.legend !== null) {
      const legendItems: LegendItem[] = [
        { label: 'Increase', color: colors.increase, active: true },
        { label: 'Decrease', color: colors.decrease, active: true },
        { label: 'Total', color: colors.total, active: true },
      ]
      this.legend.update(legendItems)
    }
  }

  private isBarHovered(bar: ComputedBar): boolean {
    if (this.hoveredBar === null) return false
    return (
      this.hoveredBar.x === bar.x &&
      this.hoveredBar.y === bar.y &&
      this.hoveredBar.label === bar.label
    )
  }

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

    let found: ComputedBar | null = null
    for (const bar of this.computedBars) {
      if (hitTestRect(mouseX, mouseY, bar)) {
        found = bar
        break
      }
    }

    const previousHovered = this.hoveredBar
    this.hoveredBar = found

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

    if (found !== null && this.tooltip !== null) {
      let content: string
      if (this.config.tooltip?.format !== undefined) {
        content = this.config.tooltip.format(found.value, found.label, found.label)
      } else {
        const sign = found.value >= 0 ? '+' : ''
        content = `${found.label}: ${sign}${String(found.value)}`
      }
      this.tooltip.show(found.x + found.width / 2, found.y, content)
    } else if (this.tooltip !== null) {
      this.tooltip.hide()
    }
  }

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

  public update(items: WaterfallItem[]): void {
    this.items = items
    this.render()
  }
}

export type { WaterfallChartConfig, WaterfallItem } from './types'
