/**
 * Renders PDF pages to PNG buffers using pdfjs-dist and node-canvas.
 */
import { createRequire } from "node:module";
import { readFileSync } from "node:fs";
import path from "node:path";
import { arrayBuffer } from "node:stream/consumers";
import { createCanvas, DOMMatrix, ImageData } from "canvas";
import * as pdfjs from "pdfjs-dist/legacy/build/pdf.mjs";

const PDF_DATA_PREFIX = "data:application/pdf;base64,";

if (!globalThis.DOMMatrix) {
  globalThis.DOMMatrix = DOMMatrix;
}
if (!globalThis.ImageData) {
  globalThis.ImageData = ImageData;
}
if (!globalThis.navigator?.language) {
  globalThis.navigator = {
    language: "en-US",
    platform: "",
    userAgent: "",
  };
}

class CanvasPackageFactory {
  constructor({ enableHWA = false } = {}) {
    this.enableHWA = enableHWA;
  }

  create(width, height) {
    const canvas = createCanvas(width, height);
    return {
      canvas,
      context: canvas.getContext("2d"),
    };
  }

  reset(canvasAndContext, width, height) {
    canvasAndContext.canvas.width = width;
    canvasAndContext.canvas.height = height;
  }

  destroy(canvasAndContext) {
    canvasAndContext.canvas.width = 0;
    canvasAndContext.canvas.height = 0;
    canvasAndContext.canvas = null;
    canvasAndContext.context = null;
  }
}

function getPdfjsDataDir() {
  if (process.env.FC_PDFJS_DATA) {
    return process.env.FC_PDFJS_DATA;
  }

  const candidates = [
    "/usr/lib/farmcontrol-server/pdfjs-data",
    path.join(path.dirname(process.execPath), "pdfjs-data"),
  ];

  for (const candidate of candidates) {
    try {
      readFileSync(path.join(candidate, "standard_fonts", "FoxitFixed.pfb"));
      return candidate;
    } catch {}
  }

  const require = createRequire(import.meta.url);
  return path.dirname(require.resolve("pdfjs-dist/package.json"));
}

async function parsePdfInput(input) {
  if (Buffer.isBuffer(input)) {
    return Uint8Array.from(input);
  }
  if (input instanceof Uint8Array) {
    return input;
  }
  if (typeof input === "string") {
    if (input.startsWith(PDF_DATA_PREFIX)) {
      return Uint8Array.from(
        Buffer.from(input.slice(PDF_DATA_PREFIX.length), "base64"),
      );
    }
    return new Uint8Array(readFileSync(input));
  }
  if (typeof input === "object" && input && Symbol.asyncIterator in input) {
    return new Uint8Array(await arrayBuffer(input));
  }

  throw new Error(
    "pdfToImg received an unexpected input. Provide a path, data URL, Uint8Array, Buffer, or ReadableStream.",
  );
}

export async function pdfToImg(pdfInput, options = {}) {
  const { page_numbers, scale = 2, password, docInitParams } = options;

  const pdfjsDataDir = getPdfjsDataDir();
  const data = await parsePdfInput(pdfInput);

  const pdfDocument = await pdfjs.getDocument({
    password,
    standardFontDataUrl: path.join(pdfjsDataDir, "standard_fonts") + path.sep,
    cMapUrl: path.join(pdfjsDataDir, "cmaps") + path.sep,
    cMapPacked: true,
    isEvalSupported: false,
    CanvasFactory: CanvasPackageFactory,
    data,
    ...docInitParams,
  }).promise;

  const outputImages = [];
  const pageCount = pdfDocument.numPages;
  const pagesToRender =
    page_numbers && Array.isArray(page_numbers) && page_numbers.length > 0
      ? page_numbers
      : Array.from({ length: pageCount }, (_, index) => index + 1);

  for (const pageNum of pagesToRender) {
    const page = await pdfDocument.getPage(pageNum);
    const viewport = page.getViewport({ scale });
    const canvasFactory = new CanvasPackageFactory();
    const { canvas, context } = canvasFactory.create(
      viewport.width,
      viewport.height,
    );

    await page.render({
      canvas,
      viewport,
    }).promise;

    outputImages.push(canvas.toBuffer("image/png"));
    canvasFactory.destroy({ canvas, context });
  }

  await pdfDocument.destroy();
  return outputImages;
}
