import Jimp from 'npm:jimp@0.22.8'

function floydSteinberg (data: Buffer, w: number, h: number) {
  const errorMultiplier = .6 // 1 is normal, 0.5 is "reduced color bleeding"
  const filter = [
    [0, 0, 0, 7 / 48, 5 / 48],
    [3 / 48, 5 / 48, 7 / 48, 5 / 48, 3 / 48],
    [1 / 48, 3 / 48, 5 / 48, 3 / 48, 1 / 48]
  ]
  const error = Array(h)

  let r: number
  let g: number
  let b: number

  for (let y = 0; y < h; y++) error[y] = new Float32Array(w)

  for (let y = 0; y < h; y++) {
    for (let x = 0; x < w; x++) {
      const id = ((y * w) + x) * 4
      r = data[id]
      g = data[id + 1]
      b = data[id + 2]

      let avg = (r + g + b) / 3
      avg -= error[y][x] * errorMultiplier

      let e = 0
      if (avg < 128) {
        e = -avg
        avg = 0
      } else {
        e = 255 - avg
        avg = 255
      }

      data[id] = data[id + 1] = data[id + 2] = avg
      data[id + 3] = 255

      for (let yy = 0; yy < 3; yy++) {
        for (let xx = -2; xx <= 2; xx++) {
          if (y + yy < 0 || h <= y + yy || x + xx < 0 || w <= x + xx) continue
          error[y + yy][x + xx] += e * filter[yy][xx + 2]
        }
      }
    }
  }

  return data
}

export default async function process (source: string, destinationWidth?: number, destinationHeight?: number, storeAs?: string): Uint8ClampedArray | null {
  try {
    const image = await Jimp.read(source)
    const width = destinationWidth ?? image.bitmap.width
    const height = destinationHeight ?? image.bitmap.height

    image.grayscale().cover(width, height)
    const data = floydSteinberg(image.bitmap.data, width, height)

    if (storeAs) {
      new Jimp({data, width, height }, (err, image) => image.write(storeAs))
    }

    return new Uint8ClampedArray(data)
  } catch (error) {
    console.error('failed to process image', error)
    return null
  }
}