'use strict';

// ════════════════════════════════════════════════════════════════════════════
// composeScript.js — Compose-Fenster Content-Script
// ════════════════════════════════════════════════════════════════════════════

(function () {
  const originalImages = new Map();
  const compressedCache = new Map();
  const silentlyRemoved = []; // still entfernte Mini-Bilder (für undoAll)

  // Eingabeformat-Einstellungen (Defaults; werden unten aus storage geladen)
  let inputFormats = { webp: false, gif: true, bmp: true };

  function getPayload(src) {
    const comma = src.indexOf(',');
    return comma < 0 ? null : src.substring(comma + 1);
  }

  function isPngDataUrl(src) {
    if (!src || typeof src !== 'string') return false;
    const match = /^data:([^;,]+)/i.exec(src);
    return match ? match[1].trim().toLowerCase() === 'image/png' : false;
  }

  function isJpegDataUrl(src) {
    if (!src || typeof src !== 'string') return false;
    const match = /^data:([^;,]+)/i.exec(src);
    if (!match) return false;
    const mime = match[1].trim().toLowerCase();
    return mime === 'image/jpeg' || mime === 'image/jpg';
  }

  // ── Eingabeformate: png/jpeg (immer) + webp/bmp/gif (per Einstellung) ──
  // Reine Eingabeformate — die Ausgabe bleibt PNG/JPEG (E-Mail-kompatibel).
  const DATAURL_MIME_FORMAT = {
    'image/png': 'png', 'image/jpeg': 'jpeg', 'image/jpg': 'jpeg',
    'image/webp': 'webp', 'image/bmp': 'bmp', 'image/x-ms-bmp': 'bmp',
    'image/gif': 'gif',
  };

  function isCompressibleDataUrl(src) {
    if (!src || typeof src !== 'string') return false;
    const match = /^data:([^;,]+)/i.exec(src);
    if (!match) return false;
    const format = DATAURL_MIME_FORMAT[match[1].trim().toLowerCase()];
    if (!format) return false;
    if (format === 'webp' && !inputFormats.webp) return false;
    if (format === 'bmp' && !inputFormats.bmp) return false;
    if (format === 'gif' && !inputFormats.gif) return false;
    return true;
  }

  function getFormatFromDataUrl(src) {
    const match = /^data:([^;,]+)/i.exec(src || '');
    const mime = match ? match[1].trim().toLowerCase() : '';
    return DATAURL_MIME_FORMAT[mime] || 'png';
  }

  function dataUrlToBytes(dataUrl) {
    return fetch(dataUrl).then((res) => res.blob()).then((blob) => blob.arrayBuffer()).then((buf) => new Uint8Array(buf));
  }

  function bytesToDataUrl(bytes, mimeType) {
    return new Promise((resolve, reject) => {
      const blob = new Blob([bytes], { type: mimeType || 'image/png' });
      const reader = new FileReader();
      reader.onload = () => resolve(reader.result);
      reader.onerror = () => reject(reader.error);
      reader.readAsDataURL(blob);
    });
  }

  function getDataUrlByteSize(src) {
    const comma = src.indexOf(',');
    if (comma < 0) return 0;
    const base64Len = src.length - comma - 1;
    const padding = src.endsWith('==') ? 2 : (src.endsWith('=') ? 1 : 0);
    return Math.floor(base64Len * 3 / 4) - padding;
  }

  // ── neu: Hash aus Data-URL berechnen (für Ausnahmen-Check in count-Funktionen) ──
  async function computeHashFromSrc(src) {
    const payload = getPayload(src);
    if (!payload) return null;
    const binaryString = atob(payload);
    const bytes = new Uint8Array(binaryString.length);
    for (let i = 0; i < binaryString.length; i++) bytes[i] = binaryString.charCodeAt(i);
    const hashBuffer = await crypto.subtle.digest('SHA-256', bytes);
    return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('');
  }

  function isQuoteImage(img) {
    return img.closest('blockquote') !== null;
  }

  function getInlineImages() {
    return Array.from(document.querySelectorAll('img')).filter((img) => !isQuoteImage(img));
  }

  function getQuoteImageElements() {
    const blockquotes = document.querySelectorAll('blockquote');
    const seen = new Set();
    const elements = [];
    for (const bq of blockquotes) {
      for (const child of bq.querySelectorAll('img, span[data-pngquant-placeholder="1"]')) {
        // FIX: Bei verschachtelten Blockquotes findet die Abfrage des äußeren
        // Zitats auch die Elemente des inneren — jedes Element nur einmal.
        if (seen.has(child)) continue;
        seen.add(child);
        elements.push(child);
      }
    }
    return elements;
  }

  function getQuoteImages() {
    return getQuoteImageElements().filter((el) => el.tagName === 'IMG');
  }

  function storeOriginal(el) {
    if (!originalImages.has(el)) {
      originalImages.set(el, {
        src: el.getAttribute('src') || '',
        width: el.getAttribute('width') || '',
        height: el.getAttribute('height') || '',
        naturalWidth: el.naturalWidth || 0,
        naturalHeight: el.naturalHeight || 0,
      });
      if (el.naturalWidth && !el.dataset.pngquantOrigNw) {
        el.dataset.pngquantOrigNw = String(el.naturalWidth);
        el.dataset.pngquantOrigNh = String(el.naturalHeight);
      }
    }
  }

  function generateThumbnail(src) {
    return new Promise((resolve) => {
      const img = new Image();
      img.onload = () => {
        const maxDim = 240;
        const ratio = Math.min(maxDim / img.width, maxDim / img.height, 1);
        const w = Math.max(1, Math.round(img.width * ratio));
        const h = Math.max(1, Math.round(img.height * ratio));
        const canvas = document.createElement('canvas');
        canvas.width = w;
        canvas.height = h;
        canvas.getContext('2d').drawImage(img, 0, 0, w, h);
        resolve(canvas.toDataURL('image/jpeg', 0.85));
      };
      img.onerror = () => resolve('');
      img.src = src;
    });
  }

  async function getImageList() {
    const images = [];
    const thumbJobs = [];
    const inlineImgs = getInlineImages();
    for (let i = 0; i < inlineImgs.length; i++) {
      const img = inlineImgs[i];
      const src = img.getAttribute('src') || '';
      if (!isCompressibleDataUrl(src)) continue;
      if (img.naturalWidth && !img.dataset.pngquantOrigNw) {
        img.dataset.pngquantOrigNw = String(img.naturalWidth);
        img.dataset.pngquantOrigNh = String(img.naturalHeight);
      }
      const w = parseInt(img.getAttribute('width') || img.naturalWidth || 0, 10);
      const h = parseInt(img.getAttribute('height') || img.naturalHeight || 0, 10);
      const cachedNw = img.dataset.pngquantOrigNw ? parseInt(img.dataset.pngquantOrigNw, 10) : 0;
      const cachedNh = img.dataset.pngquantOrigNh ? parseInt(img.dataset.pngquantOrigNh, 10) : 0;
      const nw = img.naturalWidth || cachedNw || w;
      const nh = img.naturalHeight || cachedNh || h;

      const isDone = img.dataset.pngquantDone === '1';
      const isSkipped = img.dataset.pngquantSkipped === '1';
      const orig = originalImages.get(img);
      const hasOrig = isDone && orig && orig.src;
      const origSrc = hasOrig ? orig.src : src;
      const origW = hasOrig && orig.width ? parseInt(orig.width, 10) : w;
      const origH = hasOrig && orig.height ? parseInt(orig.height, 10) : h;
      const origNw = hasOrig && orig.naturalWidth ? orig.naturalWidth
                   : (isDone ? (cachedNw || nw) : nw);
      const origNh = hasOrig && orig.naturalHeight ? orig.naturalHeight
                   : (isDone ? (cachedNh || nh) : nh);

      images.push({
        id: `inline-${i}`, category: 'inline',
        thumbnail: '',
        width: origW, height: origH,
        naturalWidth: origNw, naturalHeight: origNh,
        format: getFormatFromDataUrl(origSrc),
        originalSize: getDataUrlByteSize(origSrc),
        compressedSize: isDone ? getDataUrlByteSize(src) : null,
        compressedFormat: isDone ? getFormatFromDataUrl(src) : null,
        compressedWidth: isDone ? (img.naturalWidth || w) : null,
        compressedHeight: isDone ? (img.naturalHeight || h) : null,
        compressedInlineWidth: isDone ? w : null,
        compressedInlineHeight: isDone ? h : null,
        payload: getPayload(origSrc),
        currentPayload: getPayload(src),
        done: isDone || isSkipped, removed: false,
      });
      const thumbIdx = images.length - 1;
      thumbJobs.push(generateThumbnail(src).then((t) => { images[thumbIdx].thumbnail = t; }));
    }

    const quoteEls = getQuoteImageElements();
    for (let i = 0; i < quoteEls.length; i++) {
      const el = quoteEls[i];
      if (el.tagName === 'SPAN') {
        const w = parseInt(el.style.width || 0, 10);
        const h = parseInt(el.style.height || 0, 10);
        images.push({
          id: `quote-${i}`, category: 'quote', thumbnail: '', width: w, height: h,
          naturalWidth: w, naturalHeight: h,
          format: 'png', originalSize: 0, payload: null, currentPayload: null, done: false, removed: true,
        });
      } else {
        const src = el.getAttribute('src') || '';
        const w = parseInt(el.getAttribute('width') || 0, 10);
        const h = parseInt(el.getAttribute('height') || 0, 10);
        if (el.naturalWidth && !el.dataset.pngquantOrigNw) {
          el.dataset.pngquantOrigNw = String(el.naturalWidth);
          el.dataset.pngquantOrigNh = String(el.naturalHeight);
        }
        const cachedNw = el.dataset.pngquantOrigNw ? parseInt(el.dataset.pngquantOrigNw, 10) : 0;
        const cachedNh = el.dataset.pngquantOrigNh ? parseInt(el.dataset.pngquantOrigNh, 10) : 0;
        const nw = el.naturalWidth || cachedNw || w;
        const nh = el.naturalHeight || cachedNh || h;
        const removed = el.dataset.pngquantQuoteRemoved === '1';
        if (!isCompressibleDataUrl(src)) {
          images.push({ id: `quote-${i}`, category: 'quote', thumbnail: '', width: w, height: h, naturalWidth: nw, naturalHeight: nh, format: 'png', originalSize: 0, payload: null, currentPayload: null, done: false, removed });
          continue;
        }

        const isDone = el.dataset.pngquantDone === '1';
        const isSkipped = el.dataset.pngquantSkipped === '1';
        const orig = originalImages.get(el);
        const hasOrig = isDone && orig && orig.src;
        const origSrc = hasOrig ? orig.src : src;
        const origW = hasOrig && orig.width ? parseInt(orig.width, 10) : w;
        const origH = hasOrig && orig.height ? parseInt(orig.height, 10) : h;
        const origNw = hasOrig && orig.naturalWidth ? orig.naturalWidth
                     : (isDone ? (cachedNw || nw) : nw);
        const origNh = hasOrig && orig.naturalHeight ? orig.naturalHeight
                     : (isDone ? (cachedNh || nh) : nh);

        images.push({
          id: `quote-${i}`, category: 'quote',
          thumbnail: '',
          width: origW, height: origH,
          naturalWidth: origNw, naturalHeight: origNh,
          format: getFormatFromDataUrl(origSrc),
          originalSize: getDataUrlByteSize(origSrc),
          compressedSize: isDone ? getDataUrlByteSize(src) : null,
          compressedFormat: isDone ? getFormatFromDataUrl(src) : null,
          compressedWidth: isDone ? (el.naturalWidth || w) : null,
          compressedHeight: isDone ? (el.naturalHeight || h) : null,
          compressedInlineWidth: isDone ? w : null,
          compressedInlineHeight: isDone ? h : null,
          payload: getPayload(origSrc),
          currentPayload: getPayload(src),
          done: isDone || isSkipped, removed,
        });
        const thumbIdx = images.length - 1;
        thumbJobs.push(generateThumbnail(src).then((t) => { images[thumbIdx].thumbnail = t; }));
      }
    }

    await Promise.all(thumbJobs);
    return { images };
  }

  async function compressWithDedup(img, tryPng, tryJpeg, targetWidth, targetHeight, category, label, displayW = 0, displayH = 0) {
    const src = img.getAttribute('src') || '';
    if (!isCompressibleDataUrl(src)) return false;
    const payload = getPayload(src);
    if (!payload) return false;

    const attrW = (displayW > 0 && displayH > 0) ? displayW : targetWidth;
    const attrH = (displayW > 0 && displayH > 0) ? displayH : targetHeight;

    if (compressedCache.has(payload)) {
      const cached = compressedCache.get(payload);
      storeOriginal(img);
      img.dataset.pngquantDone = '1';
      img.setAttribute('src', cached.dataUrl);
      if (cached.width && cached.height) {
        img.setAttribute('width', String(cached.width));
        img.setAttribute('height', String(cached.height));
      }
      console.log(`[PNGquant] Duplikat erkannt, Cache-Treffer für ${label}`);
      return true;
    }

    const format = getFormatFromDataUrl(src);
    try {
      const bytes = await dataUrlToBytes(src);
      const response = await browser.runtime.sendMessage({
        type: 'compress-image', bytes, format,
        tryPng, tryJpeg, targetWidth, targetHeight, category,
      });
      if (response && response.bytes) {
        storeOriginal(img);
        img.dataset.pngquantDone = '1';
        const compressedBytes = new Uint8Array(response.bytes);
        const outMime = response.format === 'jpeg' ? 'image/jpeg' : 'image/png';
        const dataUrl = await bytesToDataUrl(compressedBytes, outMime);
        img.setAttribute('src', dataUrl);
        if (attrW && attrH) {
          img.setAttribute('width', String(attrW));
          img.setAttribute('height', String(attrH));
        }
        compressedCache.set(payload, { dataUrl, width: attrW, height: attrH });
        console.log(`[PNGquant] ${category} ${format}→${response.format}: ${bytes.length} B → ${compressedBytes.length} B (gecacht)`);
        return true;
      }
      img.dataset.pngquantSkipped = '1';
      console.log(`[PNGquant] ${label}: unzureichende Einsparung, übersprungen`);
      return false;
    } catch (err) {
      console.error(`[PNGquant] Fehler bei ${label}:`, err.message || err);
      return false;
    }
  }

  async function compressSingleImage(category, index, resizePercent, profileName, scale, keepInlineSize, forcePng, resizeAlgorithm) {
    // FIX: gleiche Indizierung wie getImageList (gemischte Liste inkl. Placeholder)
    const images = category === 'quote' ? getQuoteImageElements() : getInlineImages();
    const img = images[index];
    if (!img || img.tagName !== 'IMG') return { success: false, error: browser.i18n.getMessage('error_imageNotFound') };

    const src = img.getAttribute('src') || '';
    if (!isCompressibleDataUrl(src)) return { success: false, error: browser.i18n.getMessage('error_notCompressible') };

    const format = getFormatFromDataUrl(src);
    const bytes = await dataUrlToBytes(src);

    const origDisplayW = parseInt(img.getAttribute('width') || 0, 10);
    const origDisplayH = parseInt(img.getAttribute('height') || 0, 10);

    let targetWidth = null, targetHeight = null;

    if (scale) {
      const dims = getDisplaySize(img);
      if (dims) {
        targetWidth = dims.w;
        targetHeight = dims.h;
      }
    }

    if (resizePercent && resizePercent !== 100) {
      if (targetWidth && targetHeight) {
        targetWidth = Math.round(targetWidth * resizePercent / 100);
        targetHeight = Math.round(targetHeight * resizePercent / 100);
      } else {
        const orig = originalImages.get(img);
        const cachedNw = img.dataset.pngquantOrigNw ? parseInt(img.dataset.pngquantOrigNw, 10) : 0;
        const cachedNh = img.dataset.pngquantOrigNh ? parseInt(img.dataset.pngquantOrigNh, 10) : 0;
        const w = parseInt(orig?.naturalWidth || img.naturalWidth || cachedNw || img.getAttribute('width') || 0, 10);
        const h = parseInt(orig?.naturalHeight || img.naturalHeight || cachedNh || img.getAttribute('height') || 0, 10);
        if (w > 0 && h > 0) {
          targetWidth = Math.round(w * resizePercent / 100);
          targetHeight = Math.round(h * resizePercent / 100);
        }
      }
    }

    if (scale && targetWidth && targetHeight) {
      const orig = originalImages.get(img);
      const cachedNw = img.dataset.pngquantOrigNw ? parseInt(img.dataset.pngquantOrigNw, 10) : 0;
      const cachedNh = img.dataset.pngquantOrigNh ? parseInt(img.dataset.pngquantOrigNh, 10) : 0;
      const natW = parseInt(orig?.naturalWidth || img.naturalWidth || cachedNw || 0, 10);
      const natH = parseInt(orig?.naturalHeight || img.naturalHeight || cachedNh || 0, 10);
      if (natW > 0 && targetWidth > natW) targetWidth = natW;
      if (natH > 0 && targetHeight > natH) targetHeight = natH;
    }

    const response = await browser.runtime.sendMessage({
      type: 'compress-image', bytes, format,
      tryPng: null, tryJpeg: null,
      targetWidth, targetHeight, category, profileName,
      forcePng: !!forcePng,
      resizeAlgorithm,
    });

    if (response && response.bytes) {
      const compressedBytes = new Uint8Array(response.bytes);
      const outMime = response.format === 'jpeg' ? 'image/jpeg' : 'image/png';
      const dataUrl = await bytesToDataUrl(compressedBytes, outMime);
      storeOriginal(img);
      img.dataset.pngquantDone = '1';
      img.setAttribute('src', dataUrl);
      if (keepInlineSize && origDisplayW > 0 && origDisplayH > 0) {
        img.setAttribute('width', String(origDisplayW));
        img.setAttribute('height', String(origDisplayH));
      } else if (targetWidth && targetHeight) {
        img.setAttribute('width', String(targetWidth));
        img.setAttribute('height', String(targetHeight));
      }

      const payload = getPayload(src);
      if (payload) compressedCache.set(payload, { dataUrl, width: targetWidth, height: targetHeight });
      console.log(`[PNGquant] ${category} ${format}→${response.format}: ${bytes.length} B → ${compressedBytes.length} B (keepInline=${keepInlineSize ? 'on' : 'off'}, forcePng=${forcePng ? 'on' : 'off'}, scale=${scale ? 'on' : 'off'})`);
      return { success: true, originalSize: bytes.length, compressedSize: compressedBytes.length, format: response.format, newWidth: targetWidth, newHeight: targetHeight };
    }
    img.dataset.pngquantSkipped = '1';
    return { success: false, reason: 'no_improvement' };
  }

  // Wendet ein bereits komprimiertes Ergebnis (aus dem compressedCache) auf
  // Duplikate desselben Original-Bildes an (gleiche Original-Payload).
  // Anzeigegröße der Duplikate bleibt erhalten; bei Resize ohne keepInlineSize
  // werden sie proportional zur eigenen Anzeigegröße mitverkleinert.
  function applyToDuplicates(message) {
    try {
      const primaryList = message.category === 'quote' ? getQuoteImageElements() : getInlineImages();
      const primary = primaryList[message.index];
      if (primary && primary.tagName !== 'IMG') return { success: false, error: browser.i18n.getMessage('error_imageNotFound') };
      const orig = primary ? originalImages.get(primary) : null;
      const origPayload = orig && orig.src ? getPayload(orig.src) : null;
      const cached = origPayload ? compressedCache.get(origPayload) : null;
      if (!cached) return { success: false, error: browser.i18n.getMessage('error_noCachedResult') };
      let applied = 0;
      // DOM-Listen einmal holen statt pro Duplikat neu abzufragen
      const dupLists = { quote: getQuoteImageElements(), inline: getInlineImages() };
      for (const dup of (message.duplicates || [])) {
        const list = dup.category === 'quote' ? dupLists.quote : dupLists.inline;
        const el = list[dup.index];
        if (!el || el.tagName !== 'IMG') continue;
        if (getPayload(el.getAttribute('src') || '') !== origPayload) continue;
        storeOriginal(el);
        el.dataset.pngquantDone = '1';
        el.setAttribute('src', cached.dataUrl);
        if (!message.keepInlineSize && message.resizePercent && message.resizePercent !== 100) {
          const dw = parseInt(el.getAttribute('width') || 0, 10);
          const dh = parseInt(el.getAttribute('height') || 0, 10);
          if (dw > 0 && dh > 0) {
            el.setAttribute('width', String(Math.max(1, Math.round(dw * message.resizePercent / 100))));
            el.setAttribute('height', String(Math.max(1, Math.round(dh * message.resizePercent / 100))));
          }
        }
        applied++;
      }
      if (applied > 0) console.log(`[PNGquant] ${applied} Duplikat(e) mitkomprimiert`);
      return { success: true, applied };
    } catch (e) {
      console.warn('[PNGquant] Duplikate fehlgeschlagen:', e.message);
      return { success: false, error: e.message };
    }
  }

  function removeSingleQuoteImage(index, replaceText) {
    const els = getQuoteImageElements();
    const el = els[index];
    if (!el || el.tagName !== 'IMG') return { success: false };
    const img = el;
    const w = parseInt(img.getAttribute('width') || 0, 10);
    const h = parseInt(img.getAttribute('height') || 0, 10);
    storeOriginal(img);
    const orig = originalImages.get(img);
    const placeholder = document.createElement('span');
    placeholder.textContent = replaceText || 'Image removed';
    placeholder.style.cssText = 'border:1px dashed #ccc;color:#999;font-size:11px;padding:4px;display:inline-block;background:#f9f9f9;box-sizing:border-box;';
    if (w > 10 && h > 10) { placeholder.style.width = w + 'px'; placeholder.style.height = h + 'px'; placeholder.style.overflow = 'hidden'; }
    placeholder.dataset.pngquantPlaceholder = '1';
    originalImages.set(placeholder, orig);
    originalImages.delete(img);
    img.dataset.pngquantDone = '1';
    img.dataset.pngquantQuoteRemoved = '1';
    if (img.parentNode) img.parentNode.replaceChild(placeholder, img);
    return { success: true };
  }

  function undoRemovedQuoteImage(index) {
    const els = getQuoteImageElements();
    const el = els[index];
    if (!el || el.tagName !== 'SPAN') return { success: false };
    const orig = originalImages.get(el);
    if (orig && orig.src) {
      const img = document.createElement('img');
      img.setAttribute('src', orig.src);
      if (orig.width) img.setAttribute('width', orig.width);
      if (orig.height) img.setAttribute('height', orig.height);
      if (el.parentNode) {
        el.parentNode.replaceChild(img, el);
        originalImages.delete(el);
        return { success: true };
      }
    }
    return { success: false };
  }

  function undoSingleImage(category, index) {
    // FIX: gleiche Indizierung wie getImageList (gemischte Liste inkl. Placeholder)
    const images = category === 'quote' ? getQuoteImageElements() : getInlineImages();
    const img = images[index];
    if (!img || img.tagName !== 'IMG') return { success: false };
    if (originalImages.has(img)) {
      const orig = originalImages.get(img);
      if (orig && orig.src) {
        img.setAttribute('src', orig.src);
        if (orig.width) img.setAttribute('width', orig.width); else img.removeAttribute('width');
        if (orig.height) img.setAttribute('height', orig.height); else img.removeAttribute('height');
        delete img.dataset.pngquantDone;
        delete img.dataset.pngquantSkipped;
        originalImages.delete(img);
        const payload = getPayload(orig.src);
        if (payload) compressedCache.delete(payload);
        return { success: true };
      }
    }
    return { success: false };
  }

  function replaceWithPlaceholder(img, w, h, replaceText) {
    storeOriginal(img);
    const orig = originalImages.get(img);
    const placeholder = document.createElement('span');
    placeholder.textContent = replaceText || 'Image removed';
    placeholder.style.cssText = 'border:1px dashed #ccc;color:#999;font-size:11px;padding:4px;display:inline-block;background:#f9f9f9;box-sizing:border-box;';
    if (w > 10 && h > 10) { placeholder.style.width = w + 'px'; placeholder.style.height = h + 'px'; placeholder.style.overflow = 'hidden'; }
    placeholder.dataset.pngquantPlaceholder = '1';
    originalImages.set(placeholder, orig);
    originalImages.delete(img);
    img.dataset.pngquantDone = '1';
    img.dataset.pngquantQuoteRemoved = '1';
    if (img.parentNode) img.parentNode.replaceChild(placeholder, img);
  }

  function computeTryFlags(format, pngFlag, jpegFlag, crossFlag) {
    return { tryPng: format === 'jpeg' ? crossFlag : pngFlag, tryJpeg: format === 'jpeg' ? jpegFlag : crossFlag };
  }

  function getDisplaySize(img) {
    const w = parseInt(img.getAttribute('width') || 0, 10);
    const h = parseInt(img.getAttribute('height') || 0, 10);
    return (w > 0 && h > 0) ? { w, h } : null;
  }


  async function processInlineImages(pngFlag, jpegFlag, crossFlag, scaleFlag, antialias, force) {
    const imgs = getInlineImages();
    let total = 0, compressed = 0;
    for (const img of imgs) {
      if (!force && (img.dataset.pngquantDone === '1' || img.dataset.pngquantSkipped === '1')) continue;
      const src = img.getAttribute('src') || '';
      if (!isCompressibleDataUrl(src)) continue;
      const format = getFormatFromDataUrl(src);
      const { tryPng, tryJpeg } = computeTryFlags(format, pngFlag, jpegFlag, crossFlag);
      if (!tryPng && !tryJpeg) continue;
      let targetWidth = null, targetHeight = null, displayW = 0, displayH = 0;
        if (scaleFlag) {
          const dims = getDisplaySize(img);
          if (dims) {
            displayW = dims.w;
            displayH = dims.h;
            const f = (typeof antialias === 'number' && antialias > 0) ? antialias : 1.0;
            targetWidth = Math.max(1, Math.round(dims.w * f));
            targetHeight = Math.max(1, Math.round(dims.h * f));
            const natW = parseInt(img.dataset.pngquantOrigNw || 0, 10) || img.naturalWidth || 0;
            const natH = parseInt(img.dataset.pngquantOrigNh || 0, 10) || img.naturalHeight || 0;
            if (natW > 0 && targetWidth > natW) targetWidth = natW;
            if (natH > 0 && targetHeight > natH) targetHeight = natH;
          }
        }
      total++;
      if (await compressWithDedup(img, tryPng, tryJpeg, targetWidth, targetHeight, 'inline', 'Inline', displayW, displayH)) compressed++;
    }
    return { total, compressed };
  }

  async function processQuoteImages(mode, pngFlag, jpegFlag, crossFlag, scaleFlag, antialias, replaceText, force) {
    const imgs = getQuoteImages();
    let processed = 0, total = 0;
    for (const img of imgs) {
      if (!force && img.dataset.pngquantQuoteRemoved === '1') continue;
      total++;
      const w = parseInt(img.getAttribute('width') || 0, 10);
      const h = parseInt(img.getAttribute('height') || 0, 10);
      if ((w > 0 && w <= 5) || (h > 0 && h <= 5)) {
        // FIX: Mini-Bilder (Tracking-Pixel) still entfernen, aber undo-bar machen
        silentlyRemoved.push({ img, parent: img.parentNode, next: img.nextSibling });
        img.remove(); processed++; continue;
      }
      if (mode === 'replace-boxes') { replaceWithPlaceholder(img, w, h, replaceText); processed++; continue; }
      if (!force && (img.dataset.pngquantDone === '1' || img.dataset.pngquantSkipped === '1')) continue;
      const src = img.getAttribute('src') || '';
      if (!isCompressibleDataUrl(src)) continue;
      const format = getFormatFromDataUrl(src);
      const { tryPng, tryJpeg } = computeTryFlags(format, pngFlag, jpegFlag, crossFlag);
      if (!tryPng && !tryJpeg) continue;
      let targetWidth = null, targetHeight = null, displayW = 0, displayH = 0;
        if (scaleFlag && w > 0 && h > 0) {
          displayW = w;
          displayH = h;
          const f = (typeof antialias === 'number' && antialias > 0) ? antialias : 1.0;
          targetWidth = Math.max(1, Math.round(w * f));
          targetHeight = Math.max(1, Math.round(h * f));
          const natW = parseInt(img.dataset.pngquantOrigNw || 0, 10) || img.naturalWidth || 0;
          const natH = parseInt(img.dataset.pngquantOrigNh || 0, 10) || img.naturalHeight || 0;
          if (natW > 0 && targetWidth > natW) targetWidth = natW;
          if (natH > 0 && targetHeight > natH) targetHeight = natH;
        }
      if (await compressWithDedup(img, tryPng, tryJpeg, targetWidth, targetHeight, 'quote', 'Zitat', displayW, displayH)) processed++;
    }
    return { total, processed };
  }

  function undoAll() {
    compressedCache.clear();
    let restored = 0;
    // Still entfernte Mini-Bilder wieder einfügen
    for (const r of silentlyRemoved) {
      if (r.parent && r.img) { r.parent.insertBefore(r.img, r.next || null); restored++; }
    }
    silentlyRemoved.length = 0;
    const placeholders = document.querySelectorAll('span[data-pngquant-placeholder="1"]');
    for (const span of placeholders) {
      const orig = originalImages.get(span);
      if (orig && orig.src) {
        const img = document.createElement('img');
        img.setAttribute('src', orig.src);
        if (orig.width) img.setAttribute('width', orig.width);
        if (orig.height) img.setAttribute('height', orig.height);
        if (span.parentNode) { span.parentNode.replaceChild(img, span); originalImages.delete(span); restored++; }
      }
    }
    const imgs = document.querySelectorAll('img');
    for (const img of imgs) {
      if (originalImages.has(img)) {
        const orig = originalImages.get(img);
        if (orig && orig.src) {
          img.setAttribute('src', orig.src);
          if (orig.width) img.setAttribute('width', orig.width); else img.removeAttribute('width');
          if (orig.height) img.setAttribute('height', orig.height); else img.removeAttribute('height');
          restored++;
        }
        delete img.dataset.pngquantDone;
        delete img.dataset.pngquantSkipped;
        delete img.dataset.pngquantQuoteRemoved;
        originalImages.delete(img);
      }
    }
    console.log(`[PNGquant] Undo: ${restored} Bild(er) wiederhergestellt`);
    return { restored };
  }

  // ── geändert: minBytes + excludedHashes Parameter hinzugefügt ──
  async function countUnprocessedInline(minBytes, excludedHashes) {
    const excludedSet = new Set(excludedHashes || []);
    let count = 0;
    for (const img of getInlineImages()) {
      if (img.dataset.pngquantDone === '1' || img.dataset.pngquantSkipped === '1') continue;
      const src = img.getAttribute('src') || '';
      if (!isCompressibleDataUrl(src)) continue;
      if (minBytes && getDataUrlByteSize(src) < minBytes) continue;
      if (excludedSet.size > 0) {
        try {
          const hash = await computeHashFromSrc(src);
          if (hash && excludedSet.has(hash)) continue;
        } catch (e) { /* konservativ: zählen */ }
      }
      count++;
    }
    return { count };
  }

  // ── geändert: minBytes + isRemove + excludedHashes Parameter hinzugefügt ──
  async function countQuoteImages(minBytes, isRemove, excludedHashes) {
    const excludedSet = new Set(excludedHashes || []);
    let count = 0;
    for (const img of getQuoteImages()) {
      if (img.dataset.pngquantQuoteRemoved === '1') continue;
      if (!isRemove) {
        if (img.dataset.pngquantDone === '1' || img.dataset.pngquantSkipped === '1') continue;
        const src = img.getAttribute('src') || '';
        if (!isCompressibleDataUrl(src)) continue;
        if (minBytes && getDataUrlByteSize(src) < minBytes) continue;
        if (excludedSet.size > 0) {
          try {
            const hash = await computeHashFromSrc(src);
            if (hash && excludedSet.has(hash)) continue;
          } catch (e) { /* konservativ: zählen */ }
        }
      }
      count++;
    }
    return { count };
  }

  // Eingabeformat-Einstellungen laden und live aktualisieren
  function applyInputFormatSettings(s) {
    s = s || {};
    inputFormats = { webp: s.convertWebp ?? false, gif: s.convertGif ?? true, bmp: s.convertBmp ?? true };
  }
  browser.storage.local.get('settings').then((r) => applyInputFormatSettings(r.settings)).catch(() => {});
  browser.storage.onChanged.addListener((changes, area) => {
    if (area === 'local' && changes.settings) applyInputFormatSettings(changes.settings.newValue);
  });

  browser.runtime.onMessage.addListener((message) => {
    if (message?.type === 'get-image-list') return getImageList();
    if (message?.type === 'compress-single') return compressSingleImage(message.category, message.index, message.resizePercent, message.profileName, message.scale, message.keepInlineSize, message.forcePng, message.resizeAlgorithm);
    if (message?.type === 'remove-single') return Promise.resolve(removeSingleQuoteImage(message.index, message.replaceText));
    if (message?.type === 'apply-to-duplicates') return Promise.resolve(applyToDuplicates(message));
    if (message?.type === 'undo-single') return Promise.resolve(undoSingleImage(message.category, message.index));
    if (message?.type === 'undo-removed') return Promise.resolve(undoRemovedQuoteImage(message.index));
    if (message?.type === 'process-inline') return processInlineImages(message.png, message.jpeg, message.cross, message.scale, message.antialias, message.force);
    // ── geändert: Parameter weiterreichen ──
    if (message?.type === 'count-unprocessed-inline') return countUnprocessedInline(message.minBytes, message.excludedHashes);
    if (message?.type === 'process-quotes') return processQuoteImages(message.mode, message.png, message.jpeg, message.cross, message.scale, message.antialias, message.replaceText, message.force);
    // ── geändert: Parameter weiterreichen ──
    if (message?.type === 'count-quote-images') return countQuoteImages(message.minBytes, message.isRemove, message.excludedHashes);
    if (message?.type === 'undo-all') return Promise.resolve(undoAll());
    if (message?.type === 'get-image-data') {
      // FIX: gleiche Indizierung wie getImageList (gemischte Liste inkl. Placeholder)
      const images = message.category === 'quote' ? getQuoteImageElements() : getInlineImages();
      const img = images[message.index];
      if (!img || img.tagName !== 'IMG') return { success: false };
      return { success: true, dataUrl: img.getAttribute('src') };
    }
  });
  // ── Pre-Kalkulation: neue/ersetzte Bilder im Hintergrund vorberechnen ──────
  // Schreibt NUR in den Cache (Background-Skript) — keine DOM-Änderung.
  const precalcedPayloads = new Set();
  let precalcTimer = null;

  function schedulePrecalc() {
    if (precalcTimer) clearTimeout(precalcTimer);
    precalcTimer = setTimeout(runPrecalcPass, 500);
  }

  async function runPrecalcPass() {
    const imgs = [...getInlineImages(), ...getQuoteImages()];
    for (const img of imgs) {
      if (img.dataset.pngquantDone === '1' || img.dataset.pngquantSkipped === '1') continue;
      const src = img.getAttribute('src') || '';
      if (!isCompressibleDataUrl(src)) continue;
      const payload = getPayload(src);
      if (!payload || precalcedPayloads.has(payload)) continue;
      precalcedPayloads.add(payload);
      try {
        const bytes = await dataUrlToBytes(src);
        const format = getFormatFromDataUrl(src);
        const category = isQuoteImage(img) ? 'quote' : 'inline';
        const dims = getDisplaySize(img);
        browser.runtime.sendMessage({
          type: 'precalc-image', bytes, format, category,
          displayW: dims ? dims.w : 0, displayH: dims ? dims.h : 0,
          naturalW: img.naturalWidth || 0, naturalH: img.naturalHeight || 0,
        }).catch(() => {});
      } catch (e) { /* Pre-Kalkulation ist best effort */ }
    }
  }

  const precalcObserver = new MutationObserver((mutations) => {
    for (const m of mutations) {
      if (m.type === 'childList' && m.addedNodes.length > 0) { schedulePrecalc(); return; }
      if (m.type === 'attributes') { schedulePrecalc(); return; }
    }
  });
  precalcObserver.observe(document.documentElement, {
    childList: true, subtree: true, attributes: true, attributeFilter: ['src'],
  });
  // Initial-Pass für bereits vorhandene Bilder (z. B. Zitate beim Antworten)
  schedulePrecalc();

})();
