(function initFivaOcrEngine(global) {
  "use strict";

  const LOG = "[FIVA]";
  let workerPromise = null;

  function runtimeUrl(path) {
    return chrome.runtime.getURL(path);
  }

  function loadImage(source) {
    return new Promise((resolve, reject) => {
      const image = new Image();
      image.onload = () => resolve(image);
      image.onerror = () => reject(new Error("Could not load image for OCR preprocessing."));
      image.src = source;
    });
  }

  async function preprocess(source, options = {}) {
    const image = await loadImage(source);
    const maxSide = Math.max(900, Math.min(Number(options.maxSide || 1800), 2200));
    const scale = Math.min(1, maxSide / Math.max(image.naturalWidth || image.width, image.naturalHeight || image.height));
    const width = Math.max(1, Math.round((image.naturalWidth || image.width) * scale));
    const height = Math.max(1, Math.round((image.naturalHeight || image.height) * scale));
    const canvas = document.createElement("canvas");
    const context = canvas.getContext("2d", { alpha: false });
    canvas.width = width;
    canvas.height = height;
    context.drawImage(image, 0, 0, width, height);

    const imageData = context.getImageData(0, 0, width, height);
    const data = imageData.data;
    for (let index = 0; index < data.length; index += 4) {
      const gray = (data[index] * 0.299) + (data[index + 1] * 0.587) + (data[index + 2] * 0.114);
      const contrasted = Math.max(0, Math.min(255, ((gray - 128) * 1.16) + 128));
      const value = contrasted > 172 ? 255 : contrasted < 92 ? 0 : contrasted;
      data[index] = value;
      data[index + 1] = value;
      data[index + 2] = value;
      data[index + 3] = 255;
    }
    context.putImageData(imageData, 0, 0);
    return canvas.toDataURL("image/png");
  }

  async function getWorker(onProgress) {
    if (!global.Tesseract?.createWorker) {
      throw new Error("Tesseract.js is not available.");
    }

    if (!workerPromise) {
      workerPromise = global.Tesseract.createWorker("eng", 1, {
        workerPath: runtimeUrl("fiva/vendor/tesseract/worker.min.js"),
        corePath: runtimeUrl("fiva/vendor/tesseract/core"),
        langPath: runtimeUrl("fiva/vendor/tesseract/lang"),
        cacheMethod: "readOnly",
        workerBlobURL: false,
        logger(message) {
          if (typeof onProgress === "function") onProgress(message);
        }
      });
    }

    return workerPromise;
  }

  async function recognize(source, options = {}) {
    const worker = await getWorker(options.onProgress);
    const result = await worker.recognize(source);
    console.log(LOG, "OCR extraction complete");
    return {
      text: result?.data?.text || "",
      confidence: Number(result?.data?.confidence || 0)
    };
  }

  async function recognizeMany(sources = [], options = {}) {
    const chunks = [];
    let confidenceTotal = 0;
    let confidenceCount = 0;

    for (const source of sources) {
      const result = await recognize(source, options);
      if (result.text) chunks.push(result.text);
      if (result.confidence) {
        confidenceTotal += result.confidence;
        confidenceCount += 1;
      }
    }

    return {
      text: chunks.join("\n\n"),
      confidence: confidenceCount ? Math.round(confidenceTotal / confidenceCount) : 0
    };
  }

  async function terminate() {
    if (!workerPromise) return;
    const worker = await workerPromise.catch(() => null);
    workerPromise = null;
    await worker?.terminate?.();
  }

  const OcrEngine = Object.freeze({
    preprocess,
    recognize,
    recognizeMany,
    terminate
  });

  global.FIVA = Object.assign(global.FIVA || {}, { OcrEngine });
})(typeof globalThis !== "undefined" ? globalThis : this);
