// lib/mines/ocr.js
//
// OCR fallback for scanned PDFs in the Mines ingestion pipeline.
//
// Default engine: tesseract.js v5 (WASM, in-process, spa+eng multilingual, no
// external network, no credentials). Both tesseract.js and the PDF→image
// rasterizer are OPTIONAL runtime dependencies loaded via bundler-ignored
// dynamic imports. When either is missing, ocrPdf reports `available: false`
// and the document-extract adapter marks the document 'ocr_skipped' instead of
// failing. See specs/active/mines-engineering-design.md §8B for the swap seam.

// Scanned-PDF detection threshold (chars per page). Digital PDFs average
// 800–1,250 chars/page; scanned pages extract near zero. Calibrated to the
// real corpus per the engineering design; override with the env var.
export const SCANNED_PDF_THRESHOLD = Number(process.env.MINES_OCR_THRESHOLD_CHARS_PER_PAGE) || 80;

const OCR_LANGS = process.env.MINES_OCR_LANGS || 'spa+eng';

let _tesseractPromise;

async function getTesseract() {
  if (_tesseractPromise === undefined) {
    _tesseractPromise = (async () => {
      try {
        return await import(/* webpackIgnore: true */ /* turbopackIgnore: true */ 'tesseract.js');
      } catch {
        return null;
      }
    })().catch(() => null);
  }
  return _tesseractPromise;
}

let _rasterizerPromise;

// Optional PDF→PNG rasterizer. Tesseract operates on images, so a PDF must be
// rendered to page bitmaps first. We probe a couple of common packages; if none
// is installed, OCR degrades to `ocr_skipped` rather than throwing.
async function getRasterizer() {
  if (_rasterizerPromise === undefined) {
    _rasterizerPromise = (async () => {
      try {
        const mod = await import(/* webpackIgnore: true */ /* turbopackIgnore: true */ 'pdf-to-png-converter');
        if (typeof mod?.pdfToPng === 'function') {
          return async (filePath) => {
            const pages = await mod.pdfToPng(filePath, { viewportScale: 2.0 });
            return pages.map((p, i) => ({ page: p.pageNumber || i + 1, buffer: p.content }));
          };
        }
      } catch {
        // fall through
      }
      return null;
    })().catch(() => null);
  }
  return _rasterizerPromise;
}

/**
 * Classify a PDF as scanned (image-only) based on extracted character density.
 *
 * @param {string} text          Text extracted by the digital parser (may be '').
 * @param {number} [charsPerPage] Average chars/page. When omitted, derived from
 *                                `text` treated as a single page.
 * @returns {boolean} true when the document looks scanned and needs OCR.
 */
export function isScannedPdf(text, charsPerPage) {
  let cpp = charsPerPage;
  if (cpp === undefined || cpp === null || Number.isNaN(Number(cpp))) {
    cpp = String(text ?? '').trim().length;
  }
  return Number(cpp) < SCANNED_PDF_THRESHOLD;
}

/** True when both the OCR engine and a PDF rasterizer are installed. */
export async function isOcrAvailable() {
  const [t, r] = await Promise.all([getTesseract(), getRasterizer()]);
  return Boolean(t && r);
}

/**
 * OCR a single image buffer with tesseract.js.
 * @returns {Promise<{ text: string, confidence: number }|null>} null if engine unavailable.
 */
export async function ocrImage(imageBuffer, lang = OCR_LANGS) {
  const tesseract = await getTesseract();
  const recognize = tesseract?.recognize || tesseract?.default?.recognize;
  if (typeof recognize !== 'function') return null;
  try {
    const { data } = await recognize(imageBuffer, lang);
    return { text: String(data?.text || ''), confidence: Number(data?.confidence ?? 0) };
  } catch {
    return null;
  }
}

/**
 * OCR a (scanned) PDF page-by-page.
 *
 * @param {string} filePath        Absolute path to the PDF (validated by caller).
 * @param {[number, number]} [pageRange]  Optional inclusive 1-based [start, end].
 * @returns {Promise<{ available: boolean, pages: Array<{ page, text, confidence }> }>}
 *          `available: false` means no OCR engine/rasterizer is installed —
 *          the caller marks the document 'ocr_skipped'.
 */
export async function ocrPdf(filePath, pageRange) {
  const rasterize = await getRasterizer();
  const tesseract = await getTesseract();
  if (!rasterize || !tesseract) return { available: false, pages: [] };

  let images;
  try {
    images = await rasterize(filePath);
  } catch {
    return { available: false, pages: [] };
  }

  const [start, end] = Array.isArray(pageRange) ? pageRange : [null, null];
  const pages = [];
  for (const img of images) {
    if (start != null && img.page < start) continue;
    if (end != null && img.page > end) continue;
    const res = await ocrImage(img.buffer);
    pages.push({ page: img.page, text: res?.text || '', confidence: res?.confidence ?? 0 });
  }
  return { available: true, pages };
}

/** Test-only: reset cached optional-dependency probes. */
export function __resetOcrForTests() {
  _tesseractPromise = undefined;
  _rasterizerPromise = undefined;
}
