// fallback for browsers that don't support GPU acceleration
import { IDENTITY_MATRIX, resolveColorMatrix, type ASSImage } from '../util.ts'

import { CanvasRendererBase } from './canvas-base.ts'

// clamp to 0-255 with rounding, matching rgba8unorm GPU behaviour
const clamp = (v: number) => (v < 0 ? 0 : v > 1 ? 255 : Math.round(v * 255))

export class Canvas2DRenderer extends CanvasRendererBase {
  ctx
  bufferCanvas = new OffscreenCanvas(1, 1)
  bufferCtx = this.bufferCanvas.getContext('2d', {
    alpha: true,
    desynchronized: true,
    willReadFrequently: false
  })!

  colorMatrix: Float32Array = IDENTITY_MATRIX

  constructor (canvas: OffscreenCanvas) {
    super(canvas)
    this.ctx = canvas.getContext('2d', {
      alpha: true,
      desynchronized: true,
      willReadFrequently: false
    })!

    if (!this.ctx || !this.bufferCtx) throw new Error('Could not get 2D context')
  }

  // this should be an svg filter which is cheaper, but not supported
  // https://issues.chromium.org/u/1/issues/40910142
  setColorMatrix (subtitleColorSpace?: 'BT601' | 'BT709' | 'SMPTE240M' | 'FCC', videoColorSpace?: 'BT601' | 'BT709') {
    this.colorMatrix = resolveColorMatrix(subtitleColorSpace, videoColorSpace)
  }

  // this is horribly inefficient, but it's a fallback for systems without a GPU, this is the least of their problems
  render (images: ASSImage[], heap: Uint8Array): void {
    this.applyScheduledResize()

    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)

    for (const img of images) {
      if (img.w <= 0 || img.h <= 0) continue
      const imageData = new ImageData(img.w, img.h)
      const pixels = new Uint32Array(imageData.data.buffer)

      // libass 0xRRGGBBAA → ImageData little-endian RGBA, apply colour matrix per image
      const m = this.colorMatrix
      const r = ((img.color >>> 24) & 0xff) / 255
      const g = ((img.color >>> 16) & 0xff) / 255
      const bl = ((img.color >>> 8) & 0xff) / 255
      const cr = clamp(m[0]! * r + m[3]! * g + m[6]! * bl)
      const cg = clamp(m[1]! * r + m[4]! * g + m[7]! * bl)
      const cb = clamp(m[2]! * r + m[5]! * g + m[8]! * bl)
      const color = (cb << 16) | (cg << 8) | cr
      const alpha = (255 - (img.color & 255)) / 255

      const stride = img.stride
      const h = img.h
      const w = img.w

      for (let y = h + 1, pos = img.bitmap, res = 0; --y; pos += stride) {
        for (let z = 0; z < w; ++z, ++res) {
          const k = heap[pos + z]!
          if (k !== 0) pixels[res] = (Math.round(alpha * k) << 24) | color
        }
      }

      this.bufferCanvas.width = w
      this.bufferCanvas.height = h
      this.bufferCtx.putImageData(imageData, 0, 0)
      this.ctx.drawImage(this.bufferCanvas, img.dst_x, img.dst_y)
    }
  }

  destroy () {
    this.ctx = this.bufferCtx = this.bufferCanvas = null!
  }
}
