/**
 * Candlestick chart - OHLC financial data visualization
 * @countermeasure/web-components/charts/candlestick
 */

import { BaseChart } from '../base'
import type { CandlestickChartConfig, CandlestickItem } from './types'
import { scaleLinear, formatNumber, niceScale, hexToRgba } from '../utils'
import { drawXAxis, drawYAxis } from '../axis'
import { ChartTooltip } from '../tooltip'

const DEFAULT_UP_COLOR = '#22c55e'
const DEFAULT_DOWN_COLOR = '#ef4444'
const VOLUME_RATIO = 0.2
const WICK_WIDTH = 1

export class CandlestickChart extends BaseChart {
  private data: CandlestickItem[]
  private upColor: string
  private downColor: string
  private showVolume: boolean
  private tooltipEnabled: boolean
  private tooltipFormat: ((value: number, label: string, series: string) => string) | null
  private tooltip: ChartTooltip | null
  private xAxisConfig: CandlestickChartConfig['xAxis']
  private yAxisConfig: CandlestickChartConfig['yAxis']
  private handleMouseMove: ((e: MouseEvent) => void) | null
  private handleMouseLeave: (() => void) | null
  private hoveredIndex: number

  constructor(config: CandlestickChartConfig) {
    super(config)

    this.data = config.data
    this.upColor = config.upColor ?? DEFAULT_UP_COLOR
    this.downColor = config.downColor ?? DEFAULT_DOWN_COLOR
    this.showVolume = config.showVolume === true
    this.tooltipEnabled = config.tooltip?.enabled !== false
    this.tooltipFormat = config.tooltip?.format ?? null
    this.tooltip = null
    this.xAxisConfig = config.xAxis
    this.yAxisConfig = config.yAxis
    this.handleMouseMove = null
    this.handleMouseLeave = null
    this.hoveredIndex = -1

    if (this.tooltipEnabled) {
      this.setupTooltip()
    }

    this.render()
  }

  private getPriceRange(): { min: number; max: number } {
    if (this.data.length === 0) {
      return { min: 0, max: 100 }
    }
    let min = Infinity
    let max = -Infinity
    for (const item of this.data) {
      if (item.low < min) min = item.low
      if (item.high > max) max = item.high
    }
    if (min === max) {
      return { min: min - 1, max: max + 1 }
    }
    return { min, max }
  }

  private getVolumeMax(): number {
    let max = 0
    for (const item of this.data) {
      const vol = item.volume ?? 0
      if (vol > max) max = vol
    }
    return max > 0 ? max : 1
  }

  private getPlotAreas(): {
    priceArea: { x: number; y: number; width: number; height: number }
    volumeArea: { x: number; y: number; width: number; height: number } | null
  } {
    const plot = this.plotArea

    if (this.showVolume) {
      const volumeHeight = plot.height * VOLUME_RATIO
      const priceHeight = plot.height - volumeHeight - 10
      return {
        priceArea: {
          x: plot.x,
          y: plot.y,
          width: plot.width,
          height: priceHeight,
        },
        volumeArea: {
          x: plot.x,
          y: plot.y + priceHeight + 10,
          width: plot.width,
          height: volumeHeight,
        },
      }
    }

    return { priceArea: plot, volumeArea: null }
  }

  private setupTooltip(): void {
    this.tooltip = new ChartTooltip(this.container)

    this.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
      if (this.data.length === 0) return

      const barWidth = plot.width / this.data.length
      const index = Math.floor((mx - plot.x) / barWidth)

      if (
        index < 0 ||
        index >= this.data.length ||
        mx < plot.x ||
        mx > plot.x + plot.width ||
        my < plot.y ||
        my > plot.y + plot.height
      ) {
        if (this.hoveredIndex !== -1) {
          this.hoveredIndex = -1
          this.render()
        }
        this.tooltip?.hide()
        return
      }

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

      const item = this.data[index]
      if (item === undefined) {
        this.tooltip?.hide()
        return
      }

      let content: string
      if (this.tooltipFormat !== null) {
        content = this.tooltipFormat(item.close, item.date, 'candlestick')
      } else {
        const lines = [
          `<strong>${item.date}</strong>`,
          `Open: ${formatNumber(item.open)}`,
          `High: ${formatNumber(item.high)}`,
          `Low: ${formatNumber(item.low)}`,
          `Close: ${formatNumber(item.close)}`,
        ]
        if (item.volume !== undefined) {
          lines.push(`Volume: ${formatNumber(item.volume)}`)
        }
        content = lines.join('<br/>')
      }

      this.tooltip?.show(mx, my, content)
    }

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

    this.canvas.addEventListener('mousemove', this.handleMouseMove)
    this.canvas.addEventListener('mouseleave', this.handleMouseLeave)
  }

  public render(): void {
    this.clear()

    const { ctx } = this
    const { priceArea, volumeArea } = this.getPlotAreas()
    const count = this.data.length

    if (count === 0) return

    const priceRange = this.getPriceRange()
    const nice = niceScale(
      this.yAxisConfig?.min ?? priceRange.min,
      this.yAxisConfig?.max ?? priceRange.max,
      this.yAxisConfig?.tickCount ?? 6
    )

    const yScale = scaleLinear([nice.min, nice.max], [priceArea.y + priceArea.height, priceArea.y])

    const barWidth = priceArea.width / count
    const bodyWidth = Math.max(1, barWidth * 0.7)

    // Draw Y axis
    drawYAxis(ctx, priceArea, nice.min, nice.max, this.yAxisConfig)

    // Draw X axis labels
    const dateLabels = this.data.map(item => item.date)
    const maxLabels = Math.floor(priceArea.width / 60)
    const step = Math.max(1, Math.ceil(count / maxLabels))
    const visibleLabels: string[] = []
    for (let i = 0; i < count; i++) {
      const label = dateLabels[i]
      if (i % step === 0 && label !== undefined) {
        visibleLabels.push(label)
      } else {
        visibleLabels.push('')
      }
    }
    drawXAxis(ctx, priceArea, visibleLabels, this.xAxisConfig)

    // Draw candlesticks
    ctx.save()
    for (let i = 0; i < count; i++) {
      const item = this.data[i]
      if (item === undefined) continue

      const isUp = item.close >= item.open
      const color = isUp ? this.upColor : this.downColor
      const isHovered = i === this.hoveredIndex

      const centerX = priceArea.x + barWidth * i + barWidth / 2
      const highY = yScale(item.high)
      const lowY = yScale(item.low)
      const openY = yScale(item.open)
      const closeY = yScale(item.close)

      const bodyTop = Math.min(openY, closeY)
      const bodyHeight = Math.max(1, Math.abs(closeY - openY))

      // Draw wick
      ctx.strokeStyle = color
      ctx.lineWidth = WICK_WIDTH
      ctx.beginPath()
      ctx.moveTo(centerX, highY)
      ctx.lineTo(centerX, lowY)
      ctx.stroke()

      // Draw body
      ctx.fillStyle = isHovered ? hexToRgba(color, 0.85) : color
      ctx.fillRect(centerX - bodyWidth / 2, bodyTop, bodyWidth, bodyHeight)

      // Hollow body for up candles (optional visual distinction)
      if (isUp && bodyHeight > 2) {
        ctx.strokeStyle = color
        ctx.lineWidth = 1
        ctx.strokeRect(centerX - bodyWidth / 2, bodyTop, bodyWidth, bodyHeight)
      }

      // Hover highlight
      if (isHovered) {
        ctx.strokeStyle = 'rgba(100,116,139,0.3)'
        ctx.lineWidth = 1
        ctx.setLineDash([2, 2])
        ctx.beginPath()
        ctx.moveTo(centerX, priceArea.y)
        ctx.lineTo(centerX, priceArea.y + priceArea.height)
        ctx.stroke()
        ctx.setLineDash([])
      }
    }
    ctx.restore()

    // Draw volume bars
    if (volumeArea !== null) {
      const volMax = this.getVolumeMax()
      const volScale = scaleLinear([0, volMax], [0, volumeArea.height])

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

        const vol = item.volume ?? 0
        const isUp = item.close >= item.open
        const color = isUp ? this.upColor : this.downColor
        const isHovered = i === this.hoveredIndex

        const centerX = volumeArea.x + barWidth * i + barWidth / 2
        const volHeight = volScale(vol)

        ctx.fillStyle = isHovered ? hexToRgba(color, 0.6) : hexToRgba(color, 0.3)
        ctx.fillRect(
          centerX - bodyWidth / 2,
          volumeArea.y + volumeArea.height - volHeight,
          bodyWidth,
          volHeight
        )
      }
      ctx.restore()
    }
  }

  public update(data: CandlestickItem[]): void {
    this.data = data
    this.hoveredIndex = -1
    this.render()
  }

  public override destroy(): void {
    if (this.handleMouseMove !== null) {
      this.canvas.removeEventListener('mousemove', this.handleMouseMove)
    }
    if (this.handleMouseLeave !== null) {
      this.canvas.removeEventListener('mouseleave', this.handleMouseLeave)
    }
    this.tooltip?.destroy()
    super.destroy()
  }
}
