/**
 * Pan and zoom controller for canvas charts
 * @countermeasure/web-components/charts/zoom
 */

/** Represents the current zoom/pan transformation state */
export interface ZoomTransform {
  scaleX: number
  scaleY: number
  translateX: number
  translateY: number
}

const MIN_SCALE = 0.1
const MAX_SCALE = 10
const ZOOM_SPEED = 0.001

/** Handles mousewheel zoom and mouse drag pan on a canvas element */
export class ChartZoom {
  private canvas: HTMLCanvasElement
  private onZoom: (transform: ZoomTransform) => void
  private transform: ZoomTransform

  private dragging = false
  private lastX = 0
  private lastY = 0

  private readonly handleWheel: (e: WheelEvent) => void
  private readonly handleMouseDown: (e: MouseEvent) => void
  private readonly handleMouseMove: (e: MouseEvent) => void
  private readonly handleMouseUp: (e: MouseEvent) => void

  constructor(canvas: HTMLCanvasElement, onZoom: (transform: ZoomTransform) => void) {
    this.canvas = canvas
    this.onZoom = onZoom
    this.transform = {
      scaleX: 1,
      scaleY: 1,
      translateX: 0,
      translateY: 0,
    }

    this.handleWheel = (e: WheelEvent): void => {
      e.preventDefault()
      const delta = -e.deltaY * ZOOM_SPEED
      const newScaleX = clampScale(this.transform.scaleX + delta)
      const newScaleY = clampScale(this.transform.scaleY + delta)
      this.transform = {
        ...this.transform,
        scaleX: newScaleX,
        scaleY: newScaleY,
      }
      this.onZoom(this.getTransform())
    }

    this.handleMouseDown = (e: MouseEvent): void => {
      this.dragging = true
      this.lastX = e.clientX
      this.lastY = e.clientY
      this.canvas.style.cursor = 'grabbing'
    }

    this.handleMouseMove = (e: MouseEvent): void => {
      if (!this.dragging) return
      const dx = e.clientX - this.lastX
      const dy = e.clientY - this.lastY
      this.lastX = e.clientX
      this.lastY = e.clientY
      this.transform = {
        ...this.transform,
        translateX: this.transform.translateX + dx,
        translateY: this.transform.translateY + dy,
      }
      this.onZoom(this.getTransform())
    }

    this.handleMouseUp = (): void => {
      this.dragging = false
      this.canvas.style.cursor = ''
    }

    this.canvas.addEventListener('wheel', this.handleWheel, { passive: false })
    this.canvas.addEventListener('mousedown', this.handleMouseDown)
    window.addEventListener('mousemove', this.handleMouseMove)
    window.addEventListener('mouseup', this.handleMouseUp)
  }

  /** Reset the transform to identity (no zoom, no pan) */
  reset(): void {
    this.transform = {
      scaleX: 1,
      scaleY: 1,
      translateX: 0,
      translateY: 0,
    }
    this.onZoom(this.getTransform())
  }

  /** Return a copy of the current transform state */
  getTransform(): ZoomTransform {
    return { ...this.transform }
  }

  /** Remove all event listeners */
  destroy(): void {
    this.canvas.removeEventListener('wheel', this.handleWheel)
    this.canvas.removeEventListener('mousedown', this.handleMouseDown)
    window.removeEventListener('mousemove', this.handleMouseMove)
    window.removeEventListener('mouseup', this.handleMouseUp)
  }
}

function clampScale(value: number): number {
  return Math.min(Math.max(value, MIN_SCALE), MAX_SCALE)
}
