// KI-Shield — lokale PDF-/Bild-Extraktion im isolierten Content-Script.
//
// Die Verarbeitung bleibt vollständig im Browser. Textbasierte PDF-Seiten
// werden mit pdf.js gelesen; nur Seiten ohne verwertbare Textebene laufen
// durch den gebündelten Tesseract-Worker. Es gibt bewusst weder eine feste
// Seitenzahl- noch eine OCR-Seitenobergrenze.
(function exposeDocumentExtractor(global) {
  'use strict';

  const PDF_TEXT_LAYER_MIN_CHARS = 24;
  const OCR_WORKER_INIT_TIMEOUT_MS = 25000;
  const IMAGE_FILE_PATTERN = /\.(png|jpe?g|webp|bmp|gif|tiff?)$/i;

  function reportProgress(options, details) {
    if (!options || typeof options.onProgress !== 'function') return;
    try {
      options.onProgress(details);
    } catch (_) {
      // Die Fortschrittsanzeige darf die lokale Extraktion nie abbrechen.
    }
  }

  function extensionUrl(relativePath) {
    return chrome.runtime.getURL(relativePath);
  }

  function supports(file) {
    const mime = String((file && file.type) || '').toLowerCase();
    const name = String((file && file.name) || '').toLowerCase();
    return mime === 'application/pdf' ||
      name.endsWith('.pdf') ||
      mime.startsWith('image/') ||
      IMAGE_FILE_PATTERN.test(name);
  }

  function isPdf(file) {
    const mime = String((file && file.type) || '').toLowerCase();
    const name = String((file && file.name) || '').toLowerCase();
    return mime === 'application/pdf' || name.endsWith('.pdf');
  }

  function shouldOcrPdfPage(text) {
    return String(text || '').replace(/\s+/g, '').length < PDF_TEXT_LAYER_MIN_CHARS;
  }

  function normalizeError(error) {
    if (error instanceof Error) return error;
    return new Error(String(error || 'unbekannter OCR-Fehler'));
  }

  async function createOcrWorker(options) {
    if (!global.Tesseract || typeof global.Tesseract.createWorker !== 'function') {
      throw new Error('OCR-Bibliothek nicht geladen.');
    }

    const configuredTimeout = Number(options && options.workerInitTimeoutMs);
    const timeoutMs = Math.max(
      1,
      Number.isFinite(configuredTimeout) && configuredTimeout > 0
        ? configuredTimeout
        : OCR_WORKER_INIT_TIMEOUT_MS
    );
    let timeoutHandle = null;
    let abandoned = false;
    let timedOut = false;
    let rejectWorkerError = null;

    reportProgress(options, {
      phase: 'ocr-init',
      stage: 'starting',
      ocr: true,
    });

    const workerErrorPromise = new Promise((_resolve, reject) => {
      rejectWorkerError = reject;
    });
    const workerPromise = Promise.resolve().then(() =>
      global.Tesseract.createWorker('deu+eng', 1, {
        cacheMethod: 'none',
        corePath: extensionUrl('vendor/tesseract/'),
        workerPath: extensionUrl('vendor/tesseract/worker.min.js'),
        langPath: extensionUrl('vendor/tesseract/lang/'),
        // In Content-Scripts darf ein chrome-extension:// Worker nicht direkt
        // aus der Seiten-Origin gestartet werden. Tesseract lädt ihn deshalb
        // lokal und erzeugt daraus einen Blob-Worker in der richtigen Origin.
        workerBlobURL: true,
        logger(message) {
          const status = String((message && message.status) || '');
          const progress = Math.max(
            0,
            Math.min(1, Number(message && message.progress) || 0)
          );
          if (status && status !== 'recognizing text') {
            reportProgress(options, {
              phase: 'ocr-init',
              stage: 'progress',
              status,
              progress,
              ocr: true,
            });
          }
        },
        errorHandler(error) {
          if (rejectWorkerError) {
            rejectWorkerError(new Error(
              'OCR-Workerfehler: ' + normalizeError(error).message
            ));
          }
        },
      })
    ).then((worker) => {
      if (abandoned && worker && typeof worker.terminate === 'function') {
        Promise.resolve(worker.terminate()).catch(() => {});
      }
      return worker;
    });

    const timeoutPromise = new Promise((_resolve, reject) => {
      timeoutHandle = setTimeout(() => {
        timedOut = true;
        reject(new Error(
          'OCR-Initialisierung hat das Zeitlimit von ' +
          Math.ceil(timeoutMs / 1000) +
          ' Sekunden überschritten.'
        ));
      }, timeoutMs);
    });

    try {
      const worker = await Promise.race([
        workerPromise,
        workerErrorPromise,
        timeoutPromise,
      ]);
      if (timeoutHandle) clearTimeout(timeoutHandle);
      rejectWorkerError = null;
      reportProgress(options, {
        phase: 'ocr-init',
        stage: 'ready',
        ocr: true,
      });
      return worker;
    } catch (error) {
      abandoned = true;
      if (timeoutHandle) clearTimeout(timeoutHandle);
      rejectWorkerError = null;
      const normalized = normalizeError(error);
      reportProgress(options, {
        phase: 'ocr-init',
        stage: timedOut ? 'timeout' : 'error',
        error: normalized.message,
        ocr: true,
      });
      if (timedOut) throw normalized;
      if (/^OCR-/i.test(normalized.message)) throw normalized;
      throw new Error('OCR-Initialisierung fehlgeschlagen: ' + normalized.message);
    }
  }

  async function extractPdf(file, options) {
    if (!global.pdfjsLib || typeof global.pdfjsLib.getDocument !== 'function') {
      throw new Error('PDF-Bibliothek nicht geladen.');
    }
    if (global.pdfjsLib.GlobalWorkerOptions) {
      global.pdfjsLib.GlobalWorkerOptions.workerSrc =
        extensionUrl('vendor/js/pdf.worker.min.js');
    }

    const arrayBuffer = await file.arrayBuffer();
    const loadingTask = global.pdfjsLib.getDocument({ data: arrayBuffer });
    const pdf = await loadingTask.promise;
    const pages = [];
    let ocrWorker = null;
    let ocrPageCount = 0;

    try {
      for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++) {
        reportProgress(options, {
          phase: 'page',
          page: pageNumber,
          totalPages: pdf.numPages,
          ocr: false,
        });

        const page = await pdf.getPage(pageNumber);
        try {
          const content = await page.getTextContent();
          let text = Array.from(content.items || [], (item) =>
            String((item && item.str) || '')
          ).join(' ').trim();
          let source = 'text';

          if (shouldOcrPdfPage(text)) {
            if (!ocrWorker) ocrWorker = await createOcrWorker(options);
            const viewport = page.getViewport({ scale: 1.75 });
            const canvas = document.createElement('canvas');
            canvas.width = Math.max(1, Math.ceil(viewport.width));
            canvas.height = Math.max(1, Math.ceil(viewport.height));
            const canvasContext = canvas.getContext('2d', { alpha: false });
            if (!canvasContext) throw new Error('PDF-Seite konnte nicht gerendert werden.');

            reportProgress(options, {
              phase: 'ocr',
              page: pageNumber,
              totalPages: pdf.numPages,
              ocr: true,
            });
            try {
              await page.render({ canvasContext, viewport }).promise;
              const result = await ocrWorker.recognize(canvas);
              text = String(
                (result && result.data && result.data.text) || ''
              ).trim();
              source = 'ocr';
              ocrPageCount++;
            } finally {
              canvas.width = 0;
              canvas.height = 0;
            }
          }

          pages.push({ number: pageNumber, text, source });
        } finally {
          try {
            if (typeof page.cleanup === 'function') page.cleanup();
          } catch (_) {}
        }
      }
    } finally {
      if (ocrWorker && typeof ocrWorker.terminate === 'function') {
        try {
          await ocrWorker.terminate();
        } catch (_) {}
      }
      try {
        if (typeof pdf.cleanup === 'function') pdf.cleanup();
      } catch (_) {}
      try {
        if (typeof pdf.destroy === 'function') await pdf.destroy();
      } catch (_) {}
    }

    return {
      pages,
      pageCount: pages.length,
      ocrPageCount,
    };
  }

  async function extractImage(file, options) {
    const worker = await createOcrWorker(options);
    try {
      reportProgress(options, {
        phase: 'ocr',
        page: 1,
        totalPages: 1,
        ocr: true,
      });
      const result = await worker.recognize(file);
      const text = String(
        (result && result.data && result.data.text) || ''
      ).trim();
      return {
        pages: [{ number: 1, text, source: 'ocr' }],
        pageCount: 1,
        ocrPageCount: 1,
      };
    } finally {
      try {
        await worker.terminate();
      } catch (_) {}
    }
  }

  async function extract(file, options) {
    if (!supports(file)) {
      throw new Error(
        'Nicht unterstützter Dateityp: ' +
        String((file && (file.type || file.name)) || 'unbekannt')
      );
    }
    return isPdf(file)
      ? extractPdf(file, options || {})
      : extractImage(file, options || {});
  }

  global.KIShieldDocumentExtractor = Object.freeze({
    supports,
    extract,
    extractPdf,
    extractImage,
  });
})(self);
