import { describe, expect, it } from "vitest";
import { binarizeForOcr, computeLuminanceHistogram, otsuThreshold, type Bitmap } from "./ocrPreprocess";

// Build a BGRA bitmap from a grid of [b,g,r] pixels.
function bitmapFrom(pixels: Array<[number, number, number]>, width: number, height: number): Bitmap {
  const data = Buffer.alloc(width * height * 4);
  pixels.forEach(([b, g, r], index) => {
    data[index * 4] = b;
    data[index * 4 + 1] = g;
    data[index * 4 + 2] = r;
    data[index * 4 + 3] = 255;
  });
  return { data, width, height };
}

describe("ocrPreprocess", () => {
  it("computes a luminance histogram over all pixels", () => {
    const bitmap = bitmapFrom([
      [0, 0, 0],
      [255, 255, 255],
      [0, 0, 0],
      [255, 255, 255],
    ], 2, 2);
    const histogram = computeLuminanceHistogram(bitmap);
    expect(histogram[0]).toBe(2);
    expect(histogram[255]).toBe(2);
    expect(histogram.reduce((sum, count) => sum + count, 0)).toBe(4);
  });

  it("otsu splits a clean bimodal image between the two peaks", () => {
    const histogram = new Array<number>(256).fill(0);
    histogram[20] = 50;
    histogram[220] = 50;
    const threshold = otsuThreshold(histogram);
    expect(threshold).toBeGreaterThanOrEqual(20);
    expect(threshold).toBeLessThan(220);
  });

  it("otsu is safe on an empty histogram", () => {
    expect(otsuThreshold(new Array<number>(256).fill(0))).toBe(127);
  });

  it("inverts bright foreground to black-on-white by default", () => {
    // Bright text pixel + dark background pixel.
    const bitmap = bitmapFrom([
      [255, 255, 255], // bright -> should become black
      [0, 0, 0], // dark -> should become white
    ], 2, 1);
    const out = binarizeForOcr(bitmap, { threshold: 128 });
    expect([out.data[0], out.data[1], out.data[2]]).toEqual([0, 0, 0]);
    expect([out.data[4], out.data[5], out.data[6]]).toEqual([255, 255, 255]);
    expect(out.data[3]).toBe(255);
  });

  it("keeps bright foreground white when inversion is disabled", () => {
    const bitmap = bitmapFrom([
      [255, 255, 255],
      [0, 0, 0],
    ], 2, 1);
    const out = binarizeForOcr(bitmap, { threshold: 128, invertBrightForeground: false });
    expect(out.data[0]).toBe(255);
    expect(out.data[4]).toBe(0);
  });

  it("preserves dimensions and always emits opaque pixels", () => {
    const bitmap = bitmapFrom(Array.from({ length: 9 }, () => [100, 100, 100] as [number, number, number]), 3, 3);
    const out = binarizeForOcr(bitmap);
    expect(out.width).toBe(3);
    expect(out.height).toBe(3);
    for (let pixel = 0; pixel < 9; pixel++) {
      expect(out.data[pixel * 4 + 3]).toBe(255);
    }
  });
});
