/**
 * Axis drawing functions for canvas charts
 * @countermeasure/web-components/charts/axis
 */

import type { AxisConfig, PlotArea } from './types'
import { niceScale, formatNumber } from './utils'

const AXIS_COLOR = '#94a3b8'
const GRID_COLOR = '#e2e8f0'
const LABEL_COLOR = '#64748b'
const LABEL_FONT = '11px system-ui, sans-serif'
const TITLE_FONT = '12px system-ui, sans-serif'

const TICK_SIZE = 5

/** Draw a bottom x-axis with categorical labels, ticks, optional grid lines, and optional axis label */
export function drawXAxis(
  ctx: CanvasRenderingContext2D,
  plotArea: PlotArea,
  labels: string[],
  config?: AxisConfig
): void {
  const { x, y, width, height } = plotArea
  const bottom = y + height

  ctx.save()

  // Axis line
  ctx.strokeStyle = AXIS_COLOR
  ctx.lineWidth = 1
  ctx.beginPath()
  ctx.moveTo(x, bottom)
  ctx.lineTo(x + width, bottom)
  ctx.stroke()

  // Labels and ticks
  ctx.fillStyle = LABEL_COLOR
  ctx.font = LABEL_FONT
  ctx.textAlign = 'center'
  ctx.textBaseline = 'top'

  const step = labels.length > 0 ? width / labels.length : 0

  for (let i = 0; i < labels.length; i++) {
    const label = labels[i]
    if (label === undefined) continue

    const tickX = x + step * i + step / 2

    // Tick mark
    ctx.strokeStyle = AXIS_COLOR
    ctx.beginPath()
    ctx.moveTo(tickX, bottom)
    ctx.lineTo(tickX, bottom + TICK_SIZE)
    ctx.stroke()

    // Label
    ctx.fillStyle = LABEL_COLOR
    ctx.fillText(label, tickX, bottom + TICK_SIZE + 3)

    // Grid line
    if (config?.gridLines === true) {
      ctx.strokeStyle = GRID_COLOR
      ctx.lineWidth = 0.5
      ctx.beginPath()
      ctx.moveTo(tickX, y)
      ctx.lineTo(tickX, bottom)
      ctx.stroke()
      ctx.lineWidth = 1
    }
  }

  // Axis label
  if (config?.label !== undefined) {
    ctx.fillStyle = LABEL_COLOR
    ctx.font = TITLE_FONT
    ctx.textAlign = 'center'
    ctx.textBaseline = 'top'
    ctx.fillText(config.label, x + width / 2, bottom + TICK_SIZE + 18)
  }

  ctx.restore()
}

/** Draw a left y-axis with auto-computed ticks, optional grid lines, and optional axis label */
export function drawYAxis(
  ctx: CanvasRenderingContext2D,
  plotArea: PlotArea,
  min: number,
  max: number,
  config?: AxisConfig
): void {
  const { x, y, width, height } = plotArea
  const bottom = y + height
  const tickCount = config?.tickCount ?? 5
  const format = config?.format ?? formatNumber

  const nice = niceScale(min, max, tickCount)

  ctx.save()

  // Axis line
  ctx.strokeStyle = AXIS_COLOR
  ctx.lineWidth = 1
  ctx.beginPath()
  ctx.moveTo(x, y)
  ctx.lineTo(x, bottom)
  ctx.stroke()

  // Ticks and labels
  ctx.fillStyle = LABEL_COLOR
  ctx.font = LABEL_FONT
  ctx.textAlign = 'right'
  ctx.textBaseline = 'middle'

  const steps = nice.step > 0 ? Math.round((nice.max - nice.min) / nice.step) : 0
  const domainSpan = nice.max - nice.min

  for (let i = 0; i <= steps; i++) {
    const value = nice.min + i * nice.step
    const ratio = domainSpan > 0 ? (value - nice.min) / domainSpan : 0
    const tickY = bottom - ratio * height

    // Tick mark
    ctx.strokeStyle = AXIS_COLOR
    ctx.beginPath()
    ctx.moveTo(x - TICK_SIZE, tickY)
    ctx.lineTo(x, tickY)
    ctx.stroke()

    // Label
    ctx.fillStyle = LABEL_COLOR
    ctx.fillText(format(value), x - TICK_SIZE - 3, tickY)

    // Grid line
    if (config?.gridLines === true) {
      ctx.strokeStyle = GRID_COLOR
      ctx.lineWidth = 0.5
      ctx.beginPath()
      ctx.moveTo(x, tickY)
      ctx.lineTo(x + width, tickY)
      ctx.stroke()
      ctx.lineWidth = 1
    }
  }

  // Axis label (rotated)
  if (config?.label !== undefined) {
    ctx.fillStyle = LABEL_COLOR
    ctx.font = TITLE_FONT
    ctx.textAlign = 'center'
    ctx.textBaseline = 'bottom'
    ctx.save()
    ctx.translate(x - TICK_SIZE - 30, y + height / 2)
    ctx.rotate(-Math.PI / 2)
    ctx.fillText(config.label, 0, 0)
    ctx.restore()
  }

  ctx.restore()
}

/** Draw grid lines at specified horizontal (y) and vertical (x) positions within the plot area */
export function drawGridLines(
  ctx: CanvasRenderingContext2D,
  plotArea: PlotArea,
  horizontal: number[],
  vertical: number[]
): void {
  const { x, y, width, height } = plotArea

  ctx.save()
  ctx.strokeStyle = GRID_COLOR
  ctx.lineWidth = 0.5

  for (const hy of horizontal) {
    ctx.beginPath()
    ctx.moveTo(x, hy)
    ctx.lineTo(x + width, hy)
    ctx.stroke()
  }

  for (const vx of vertical) {
    ctx.beginPath()
    ctx.moveTo(vx, y)
    ctx.lineTo(vx, y + height)
    ctx.stroke()
  }

  ctx.restore()
}
