(function initTheme() {
  const stored = localStorage.getItem('imagelite-theme');
  if (stored) {
    document.documentElement.setAttribute('data-theme', stored);
  } else if (window.matchMedia('(prefers-color-scheme: light)').matches) {
    document.documentElement.setAttribute('data-theme', 'light');
  } else {
    document.documentElement.setAttribute('data-theme', 'dark');
  }
})();

document.getElementById('theme-toggle').addEventListener('click', () => {
  const current = document.documentElement.getAttribute('data-theme');
  const next = current === 'dark' ? 'light' : 'dark';
  document.documentElement.setAttribute('data-theme', next);
  localStorage.setItem('imagelite-theme', next);
});

const state = {
  images: [],
  settings: {
    format: 'image/webp',
    quality: 82,
    autoQuality: false,
    resizeMode: 'original',
    maxWidth: null,
    maxHeight: null,
    exactWidth: null,
    exactHeight: null,
    resizePercent: 100,
    maxBytes: null,
    filenamePattern: '{name}',
    removeMetadata: true,
    cropAspect: 'free',
    cropX: 50,
    cropY: 50,
    cropZoom: 1,
    locked: false,
    expanded: false
  }
};

const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
const imageList = document.getElementById('image-list');
const summaryFooter = document.getElementById('summary-footer');
const summaryText = document.getElementById('summary-text');
const settingsPanel = document.getElementById('settings-panel');
const formatSelect = document.getElementById('format-select');
const qualitySlider = document.getElementById('quality-slider');
const qualityValue = document.getElementById('quality-value');
const autoQualityCheck = document.getElementById('auto-quality');
const resizeModeSelect = document.getElementById('resize-mode');
const maxWidthInput = document.getElementById('max-width');
const maxHeightInput = document.getElementById('max-height');
const exactWidthInput = document.getElementById('exact-width');
const exactHeightInput = document.getElementById('exact-height');
const resizePercentInput = document.getElementById('resize-percent');
const maxBytesInput = document.getElementById('max-bytes');
const maxBytesUnitSelect = document.getElementById('max-bytes-unit');
const filenamePatternInput = document.getElementById('filename-pattern');
const removeMetadataCheck = document.getElementById('remove-metadata');
const lockToggle = document.getElementById('lock-toggle');
const expandToggle = document.getElementById('expand-toggle');
const downloadAllZipBtn = document.getElementById('download-all-zip');
const pasteClipboardBtn = document.getElementById('paste-clipboard');
const clearImagesBtn = document.getElementById('clear-images');
let reprocessTimer = null;
let isRestoring = false;
let activeCropDrag = null;
const isToolWindow = new URLSearchParams(window.location.search).get('window') === '1';
const isOverlay = new URLSearchParams(window.location.search).get('overlay') === '1';
const processQueue = new Set();

function openImageDb() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('imagelite-workbench', 1);
    request.onupgradeneeded = () => {
      const db = request.result;
      if (!db.objectStoreNames.contains('images')) {
        db.createObjectStore('images', { keyPath: 'id' });
      }
    };
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

async function withImageStore(mode, callback) {
  const db = await openImageDb();
  try {
    return await new Promise((resolve, reject) => {
      const tx = db.transaction('images', mode);
      const store = tx.objectStore('images');
      const result = callback(store);
      tx.oncomplete = () => resolve(result);
      tx.onerror = () => reject(tx.error);
      tx.onabort = () => reject(tx.error);
    });
  } finally {
    db.close();
  }
}

function saveImageRecord(imageData) {
  if (!imageData.originalFile) return Promise.resolve();
  return withImageStore('readwrite', (store) => {
    store.put({
      id: imageData.id,
      name: imageData.originalName,
      displayName: imageData.displayName,
      type: imageData.originalType,
      lastModified: imageData.originalFile.lastModified || Date.now(),
      settings: imageData.settings || null,
      enabled: imageData.enabled !== false,
      blob: imageData.originalFile
    });
  }).catch((error) => {
    console.warn('Failed to persist image:', error);
    showToast('Could not keep image after close');
  });
}

function deleteImageRecord(id) {
  return withImageStore('readwrite', (store) => store.delete(id)).catch(console.warn);
}

function updateImageRecord(imageData) {
  return saveImageRecord(imageData);
}

function clearImageRecords() {
  return withImageStore('readwrite', (store) => store.clear()).catch(console.warn);
}

function getStoredImageRecords() {
  return withImageStore('readonly', (store) => {
    const request = store.getAll();
    return new Promise((resolve, reject) => {
      request.onsuccess = () => resolve(request.result || []);
      request.onerror = () => reject(request.error);
    });
  }).catch((error) => {
    console.warn('Failed to restore images:', error);
    return [];
  });
}

function updateSettingsFromUI() {
  state.settings.format = formatSelect.value;
  state.settings.quality = parseInt(qualitySlider.value, 10);
  state.settings.autoQuality = autoQualityCheck.checked;
  state.settings.resizeMode = resizeModeSelect.value;
  state.settings.maxWidth = maxWidthInput.value ? parseInt(maxWidthInput.value, 10) : null;
  state.settings.maxHeight = maxHeightInput.value ? parseInt(maxHeightInput.value, 10) : null;
  state.settings.exactWidth = exactWidthInput.value ? parseInt(exactWidthInput.value, 10) : null;
  state.settings.exactHeight = exactHeightInput.value ? parseInt(exactHeightInput.value, 10) : null;
  state.settings.resizePercent = resizePercentInput.value ? parseInt(resizePercentInput.value, 10) : 100;
  state.settings.maxBytes = maxBytesInput.value ? Math.max(1, parseInt(maxBytesInput.value, 10) * (maxBytesUnitSelect.value === 'kb' ? 1024 : 1024 * 1024)) : null;
  state.settings.filenamePattern = filenamePatternInput.value.trim() || '{name}';
  state.settings.removeMetadata = removeMetadataCheck.checked;
  qualityValue.textContent = state.settings.quality;
}

function saveSettings() {
  chrome.storage.local.set({ imageliteSettings: state.settings });
}

function scheduleReprocess() {
  if (isRestoring || state.images.length === 0) return;
  clearTimeout(reprocessTimer);
  reprocessTimer = setTimeout(() => {
    reprocessAllImages();
  }, 240);
}

function handleSettingsInput() {
  updateSettingsFromUI();
  saveSettings();
  scheduleReprocess();
  syncResizeFields();
}

function syncResizeFields() {
  const mode = resizeModeSelect.value;
  const visibleMap = {
    original: [],
    maxWidth: ['maxWidth'],
    maxHeight: ['maxHeight'],
    exact: ['exactWidth', 'exactHeight'],
    percent: ['percent'],
    maxBytes: ['maxBytes']
  };
  const visible = new Set(visibleMap[mode] || []);

  document.querySelectorAll('[data-resize-field]').forEach((group) => {
    const key = group.dataset.resizeField;
    group.hidden = !visible.has(key);
  });
}

formatSelect.addEventListener('change', handleSettingsInput);
qualitySlider.addEventListener('input', handleSettingsInput);
autoQualityCheck.addEventListener('change', handleSettingsInput);
resizeModeSelect.addEventListener('change', handleSettingsInput);
maxWidthInput.addEventListener('input', handleSettingsInput);
maxHeightInput.addEventListener('input', handleSettingsInput);
exactWidthInput.addEventListener('input', handleSettingsInput);
exactHeightInput.addEventListener('input', handleSettingsInput);
resizePercentInput.addEventListener('input', handleSettingsInput);
maxBytesInput.addEventListener('input', handleSettingsInput);
maxBytesUnitSelect.addEventListener('change', handleSettingsInput);
filenamePatternInput.addEventListener('input', handleSettingsInput);
removeMetadataCheck.addEventListener('change', handleSettingsInput);

lockToggle.addEventListener('click', () => {
  if (isOverlay) {
    window.parent.postMessage({ source: 'imagelite', action: 'closeOverlay' }, '*');
    return;
  }

  if (!isToolWindow) {
    openToolWindow({ expanded: true });
    return;
  }
  state.settings.locked = !state.settings.locked;
  chrome.storage.local.set({ imageliteWindowState: { locked: state.settings.locked, expanded: state.settings.expanded } });
  applyWindowState();
});

expandToggle.addEventListener('click', async () => {
  if (isOverlay) {
    state.settings.expanded = !state.settings.expanded;
    applyWindowState();
    window.parent.postMessage({ source: 'imagelite', action: 'setOverlayExpanded', expanded: state.settings.expanded }, '*');
    return;
  }

  if (isToolWindow) {
    if (state.settings.expanded) {
      await returnToActionPopup();
      return;
    }
    state.settings.expanded = !state.settings.expanded;
    chrome.storage.local.set({ imageliteWindowState: { locked: state.settings.locked, expanded: state.settings.expanded } });
    applyWindowState();
    chrome.runtime.sendMessage({ action: 'resizeToolWindow', expanded: state.settings.expanded }).catch(() => {});
    return;
  }

  await openToolWindow({ expanded: true });
});

async function openToolWindow(options = {}) {
  const screenInfo = {
    availWidth: window.screen?.availWidth,
    availHeight: window.screen?.availHeight,
    availLeft: window.screen?.availLeft || 0,
    availTop: window.screen?.availTop || 0
  };

  try {
    const response = await chrome.runtime.sendMessage({ action: 'openToolWindow', options: { ...options, screen: screenInfo } });
    if (!response?.success) throw new Error(response?.error || 'Background window open failed');
    window.close();
  } catch (error) {
    const url = chrome.runtime.getURL('popup.html?window=1');
    window.open(url, 'ImageLite', 'width=940,height=780,resizable=yes');
    window.close();
  }
}

async function returnToActionPopup() {
  try {
    const response = await chrome.runtime.sendMessage({ action: 'returnToActionPopup' });
    if (response?.success) return;
    showToast('Click the ImageLite icon to return to popup mode');
  } catch (error) {
    showToast('Click the ImageLite icon to return to popup mode');
  }
}

function applySettingsToUI() {
  formatSelect.value = state.settings.format;
  qualitySlider.value = state.settings.quality;
  qualityValue.textContent = state.settings.quality;
  autoQualityCheck.checked = !!state.settings.autoQuality;
  resizeModeSelect.value = state.settings.resizeMode || 'original';
  maxWidthInput.value = state.settings.maxWidth || '';
  maxHeightInput.value = state.settings.maxHeight || '';
  exactWidthInput.value = state.settings.exactWidth || '';
  exactHeightInput.value = state.settings.exactHeight || '';
  resizePercentInput.value = state.settings.resizePercent || 100;
  if (state.settings.maxBytes) {
    if (state.settings.maxBytes % (1024 * 1024) === 0) {
      maxBytesUnitSelect.value = 'mb';
      maxBytesInput.value = state.settings.maxBytes / (1024 * 1024);
    } else {
      maxBytesUnitSelect.value = 'kb';
      maxBytesInput.value = Math.max(1, Math.round(state.settings.maxBytes / 1024));
    }
  } else {
    maxBytesInput.value = '';
  }
  filenamePatternInput.value = state.settings.filenamePattern || '{name}';
  removeMetadataCheck.checked = state.settings.removeMetadata;
  syncResizeFields();
  applyWindowState();
}

function loadSettings() {
  return new Promise((resolve) => {
    chrome.storage.local.get(['imageliteSettings', 'imageliteWindowState'], (result) => {
      if (result.imageliteSettings) {
        state.settings = { ...state.settings, ...result.imageliteSettings };
      }
      if (isToolWindow && result.imageliteWindowState) {
        state.settings = { ...state.settings, ...result.imageliteWindowState };
      } else if (!isToolWindow) {
        state.settings.locked = false;
        state.settings.expanded = false;
      }
      applySettingsToUI();
      resolve();
    });
  });
}

function applyWindowState() {
  const expanded = (isToolWindow || isOverlay) && !!state.settings.expanded;
  const locked = isOverlay || (isToolWindow ? state.settings.locked !== false : false);
  document.body.classList.toggle('is-expanded', expanded);
  document.body.classList.toggle('is-tool-window', isToolWindow);
  document.body.classList.toggle('is-overlay', isOverlay);
  document.body.classList.toggle('is-locked', locked);
  lockToggle.setAttribute('aria-pressed', String(!!state.settings.locked));
  lockToggle.disabled = false;
  expandToggle.setAttribute('aria-pressed', String(expanded));
  lockToggle.title = isOverlay ? 'Pinned in this tab' : (isToolWindow ? (state.settings.locked ? 'Unlock window state' : 'Lock window state') : 'Open pinned tab overlay');
  expandToggle.title = isOverlay ? (expanded ? 'Compact overlay' : 'Expand overlay') : (isToolWindow ? (expanded ? 'Return to popup' : 'Expand tool window') : 'Open tab overlay');
}

window.addEventListener('message', (event) => {
  if (!isOverlay || event.data?.source !== 'imagelite-host') return;
  if (event.data.action === 'overlayExpandedChanged') {
    state.settings.expanded = !!event.data.expanded;
    applyWindowState();
  }
});

dropZone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (event) => handleFiles(event.target.files));

dropZone.addEventListener('dragover', (event) => {
  event.preventDefault();
  dropZone.classList.add('drag-over');
});

dropZone.addEventListener('dragleave', () => {
  dropZone.classList.remove('drag-over');
});

dropZone.addEventListener('drop', (event) => {
  event.preventDefault();
  dropZone.classList.remove('drag-over');
  handleFiles(event.dataTransfer.files);
});

document.addEventListener('paste', async (event) => {
  const added = await addClipboardItems(event.clipboardData?.items);
  if (added) showToast('Image pasted from clipboard');
});

pasteClipboardBtn.addEventListener('click', async () => {
  try {
    const clipboardItems = await navigator.clipboard.read();
    let added = false;

    for (const item of clipboardItems) {
      for (const type of item.types) {
        if (!type.startsWith('image/')) continue;

        const blob = await item.getType(type);
        const ext = type.split('/')[1] || 'png';
        await addImage(new File([blob], `clipboard.${ext}`, { type }));
        added = true;
        break;
      }
      if (added) break;
    }

    showToast(added ? 'Image pasted from clipboard' : 'No image in clipboard');
  } catch (error) {
    showToast('Clipboard access denied');
  }
});

chrome.storage.onChanged.addListener((changes, areaName) => {
  if (areaName === 'local' && changes.pendingClipboardImage?.newValue) {
    loadPendingImage();
  }
  if (areaName === 'local' && (changes.imageliteSettings || (isToolWindow && changes.imageliteWindowState))) {
    loadSettings().then(renderAll);
  }
});

async function addClipboardItems(items) {
  if (!items) return false;

  let added = false;
  for (const item of items) {
    if (!item.type.startsWith('image/')) continue;

    const blob = item.getAsFile();
    if (!blob) continue;

    await addImage(blob);
    added = true;
  }
  return added;
}

async function loadPendingImage() {
  chrome.storage.local.get(['pendingClipboardImage', 'pendingClipboardType', 'pendingClipboardName'], async (result) => {
    if (!result.pendingClipboardImage) return;

    try {
      const response = await fetch(result.pendingClipboardImage);
      const blob = await response.blob();
      const type = result.pendingClipboardType || blob.type || 'image/png';
      const ext = type.split('/')[1] || 'png';
      const file = new File([blob], result.pendingClipboardName || `image.${ext}`, { type });
      await addImage(file);
      showToast('Image loaded');
    } catch (error) {
      showToast('Failed to load image');
    } finally {
      chrome.storage.local.remove(['pendingClipboardImage', 'pendingClipboardType', 'pendingClipboardName']);
    }
  });
}

async function handleFiles(files) {
  const incoming = Array.from(files || []);
  const accepted = [];
  const limits = getClientLimits();

  for (const file of incoming) {
    if (!file.type.startsWith('image/')) {
      showToast(`Skipped: ${file.name} is not an image`);
      continue;
    }
    if (state.images.length + accepted.length >= limits.maxFiles) {
      showToast(`Limit reached: ${limits.maxFiles} images for this device`);
      break;
    }
    const projectedBytes = getCurrentOriginalBytes() + accepted.reduce((sum, item) => sum + item.size, 0) + file.size;
    if (projectedBytes > limits.maxBytes) {
      showToast(`Limit reached: ${formatBytes(limits.maxBytes)} total for this device`);
      break;
    }
    accepted.push(file);
  }

  await addFilesInBatches(accepted);
  fileInput.value = '';
}

async function addFilesInBatches(files) {
  for (const file of files) {
    await addImage(file);
    await idlePause();
  }
}

function getClientLimits() {
  const memory = navigator.deviceMemory || 4;
  const cores = navigator.hardwareConcurrency || 4;
  if (memory <= 2 || cores <= 2) return { maxFiles: 12, maxBytes: 80 * 1024 * 1024 };
  if (memory <= 4 || cores <= 4) return { maxFiles: 28, maxBytes: 180 * 1024 * 1024 };
  return { maxFiles: 60, maxBytes: 420 * 1024 * 1024 };
}

function getCurrentOriginalBytes() {
  return state.images.reduce((sum, image) => sum + (image.originalSize || 0), 0);
}

function idlePause() {
  return new Promise((resolve) => {
    if ('requestIdleCallback' in window) {
      requestIdleCallback(resolve, { timeout: 120 });
    } else {
      setTimeout(resolve, 20);
    }
  });
}

async function addImage(file) {
  return addImageFromFile(file, { persist: true });
}

async function addImageFromFile(file, options = {}) {
  const imageData = {
    id: options.id || crypto.randomUUID(),
    originalFile: file,
    originalName: file.name,
    displayName: options.displayName || file.name,
    originalSize: file.size,
    originalType: file.type,
    enabled: options.enabled !== false,
    settings: options.settings || null,
    status: 'processing',
    outputBlob: null,
    outputSize: null,
    warnings: []
  };

  state.images.unshift(imageData);
  renderAll();
  if (options.persist !== false) {
    saveImageRecord(imageData);
  }

  await processImage(imageData);

  renderAll();
}

async function processImage(imageData) {
  if (imageData.enabled === false) {
    imageData.status = 'disabled';
    return;
  }
  imageData.status = 'processing';
  imageData.outputBlob = null;
  imageData.outputSize = null;
  imageData.error = null;
  imageData.warnings = [];
  if (!processQueue.has(imageData.id)) renderAll();

  try {
    const result = await ImageProcessor.process(imageData.originalFile, getImageSettings(imageData));
    imageData.outputBlob = result.blob;
    imageData.outputSize = result.blob.size;
    imageData.outputType = result.blob.type || getImageSettings(imageData).format;
    imageData.outputQuality = result.quality;
    imageData.dimensions = result.dimensions;
    imageData.originalDimensions = result.originalDimensions;
    imageData.warnings = result.warnings || [];
    imageData.status = 'ready';
  } catch (error) {
    imageData.status = 'failed';
    imageData.error = error.message;
    console.error('Processing failed:', error);
  }
}

async function reprocessAllImages() {
  const images = state.images.filter((image) => image.enabled !== false);
  images.forEach((image) => {
    image.status = 'processing';
    image.outputBlob = null;
    image.outputSize = null;
  });
  renderAll();
  for (const image of images) {
    await processImage(image);
  }
  renderAll();
}

async function restoreImages() {
  isRestoring = true;
  const records = await getStoredImageRecords();
  for (const record of records.sort((a, b) => (b.lastModified || 0) - (a.lastModified || 0))) {
    const file = new File([record.blob], record.name, {
      type: record.type || record.blob.type || 'image/png',
      lastModified: record.lastModified || Date.now()
    });
    await addImageFromFile(file, {
      id: record.id,
      displayName: record.displayName || record.name,
      settings: record.settings || null,
      enabled: record.enabled !== false,
      persist: false
    });
  }
  isRestoring = false;
}

function getImageSettings(imageData) {
  return { ...state.settings, ...(imageData.settings || {}) };
}

function getBaseName(filename) {
  const dotIndex = filename.lastIndexOf('.');
  return dotIndex > 0 ? filename.substring(0, dotIndex) : filename;
}

function getExtensionPart(filename) {
  const dotIndex = filename.lastIndexOf('.');
  return dotIndex > 0 ? filename.substring(dotIndex) : '';
}

function renameImage(imageData, nextName) {
  const clean = nextName.trim();
  if (!clean) return;
  imageData.displayName = clean;
  updateImageRecord(imageData);
  renderAll();
}

function setImageOverride(imageData, key, value) {
  imageData.settings = { ...getImageSettings(imageData), [key]: value };
  updateImageRecord(imageData);
  processImage(imageData).then(renderAll);
}

function setImageViewSetting(imageData, key, value) {
  imageData.settings = { ...getImageSettings(imageData), [key]: value };
  updateImageRecord(imageData);
  renderAll();
}

function processImageQuiet(imageData) {
  processQueue.add(imageData.id);
  return processImage(imageData).finally(() => {
    processQueue.delete(imageData.id);
    renderAll();
  });
}

const quietProcessTimers = new Map();

function scheduleProcessImageQuiet(imageData, delay = 180) {
  clearTimeout(quietProcessTimers.get(imageData.id));
  quietProcessTimers.set(imageData.id, setTimeout(() => {
    quietProcessTimers.delete(imageData.id);
    processImageQuiet(imageData);
  }, delay));
}

function getCropStyle(settings) {
  if (settings.cropAspect === 'free') {
    return 'object-fit: contain; object-position: center center;';
  }
  return `object-fit: cover; object-position: ${settings.cropX ?? 50}% ${settings.cropY ?? 50}%; transform: scale(${settings.cropZoom || 1});`;
}

function getPreviewUrl(img, originalUrl, outputUrl) {
  if (img.cropOpen) return originalUrl;
  return img.compareOriginal ? originalUrl : (outputUrl || originalUrl);
}

function renderAll() {
  if (state.images.length > 0) {
    settingsPanel.style.display = 'block';
    summaryFooter.style.display = 'flex';
    clearImagesBtn.hidden = false;
    dropZone.querySelector('.drop-zone__content').innerHTML = `
      <svg class="drop-zone__icon" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
      <p class="drop-zone__title">Add more images</p>
    `;
  } else {
    settingsPanel.style.display = 'block';
    summaryFooter.style.display = 'none';
    clearImagesBtn.hidden = true;
    dropZone.querySelector('.drop-zone__content').innerHTML = `
      <svg class="drop-zone__icon" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
      <p class="drop-zone__title">Drop images here</p>
      <p class="drop-zone__sub">or click to browse</p>
      <p class="drop-zone__formats">PNG, JPG, JPEG, WebP, AVIF</p>
    `;
  }

  renderImageList();
  updateSummary();
}

function renderImageList() {
  imageList.innerHTML = state.images.map((img) => {
    const originalUrl = URL.createObjectURL(img.originalFile);
    const outputUrl = img.outputBlob ? URL.createObjectURL(img.outputBlob) : null;
    const savedPercent = img.outputSize ? Number(((1 - img.outputSize / img.originalSize) * 100).toFixed(1)) : null;
    const savedPositive = savedPercent !== null && savedPercent > 0;
    const settings = getImageSettings(img);
    const filename = getOutputFilename(img);
    const isDisabled = img.enabled === false;

    return `
      <article class="image-card ${img.status === 'processing' ? 'image-card--processing' : ''} ${isDisabled ? 'image-card--disabled' : ''}" data-id="${img.id}">
        <div class="image-card__preview ${img.cropOpen ? 'image-card__preview--crop' : ''}" data-crop-stage="${img.id}">
          <img src="${getPreviewUrl(img, originalUrl, outputUrl)}" alt="${escapeHtml(img.displayName)}" class="image-card__preview-img" data-preview="${img.id}" style="${getCropStyle(settings)}">
          ${img.status === 'ready' ? `
            <span class="image-card__preview-badge">${getFormatLabel(img.outputType || settings.format)}</span>
            <div class="image-card__preview-tools">
              <button class="image-card__tool ${img.cropOpen ? 'is-active' : ''}" type="button" data-crop-toggle="${img.id}" title="Crop">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 2v14a2 2 0 0 0 2 2h14"/><path d="M18 22V8a2 2 0 0 0-2-2H2"/></svg>
              </button>
              <button class="image-card__tool ${img.compareOriginal ? 'is-active' : ''}" type="button" data-compare-toggle="${img.id}" title="Show original">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3a9 9 0 1 0 0 18"/><path d="M12 3a9 9 0 0 1 0 18"/></svg>
              </button>
            </div>
            ${img.compareOriginal ? '<span class="image-card__ghost-label">Original</span>' : ''}
          ` : ''}
          ${img.status === 'processing' ? '<span class="image-card__preview-badge">Processing</span>' : ''}
          ${isDisabled ? '<span class="image-card__preview-badge">Disabled</span>' : ''}
          ${img.cropOpen ? `
            <div class="image-card__crop-panel">
              ${['free', '1:1', '4:3', '4:5', '16:9'].map((aspect) => `
                <button class="image-card__aspect ${settings.cropAspect === aspect ? 'is-active' : ''}" type="button" data-crop-aspect="${img.id}" data-aspect="${aspect}">${aspect === 'free' ? 'Free' : aspect}</button>
              `).join('')}
              <button class="image-card__aspect image-card__aspect--icon" type="button" data-crop-zoom="${img.id}" data-zoom-dir="-1" title="Zoom out">-</button>
              <button class="image-card__aspect image-card__aspect--icon" type="button" data-crop-zoom="${img.id}" data-zoom-dir="1" title="Zoom in">+</button>
            </div>
          ` : ''}
        </div>
        <div class="image-card__body">
          <button class="image-card__name" data-rename="${img.id}" type="button" title="Rename">${escapeHtml(getBaseName(img.displayName || img.originalName))}</button>
          <div class="image-card__controls">
            <label class="image-card__toggle"><input type="checkbox" data-enabled="${img.id}" ${!isDisabled ? 'checked' : ''}> Active</label>
            <select class="image-card__select" data-format="${img.id}" ${isDisabled ? 'disabled' : ''}>
              <option value="image/webp" ${settings.format === 'image/webp' ? 'selected' : ''}>WebP</option>
              <option value="image/jpeg" ${settings.format === 'image/jpeg' ? 'selected' : ''}>JPEG</option>
              <option value="image/png" ${settings.format === 'image/png' ? 'selected' : ''}>PNG</option>
              <option value="image/avif" ${settings.format === 'image/avif' ? 'selected' : ''}>AVIF</option>
            </select>
            <label class="image-card__quality">Q <input type="number" min="1" max="100" value="${settings.quality}" data-quality="${img.id}" ${isDisabled ? 'disabled' : ''}></label>
          </div>
          ${img.status === 'failed' ? `<div class="image-card__warning">${escapeHtml(img.error || 'Processing failed')}</div>` : ''}
          ${img.status === 'ready' ? `
            <div class="image-card__stats">
              <div class="image-card__stat"><strong>${formatBytes(img.originalSize)}</strong>Original ${img.originalDimensions ? `${img.originalDimensions.w}x${img.originalDimensions.h}` : ''}</div>
              <div class="image-card__stat"><strong>${formatBytes(img.outputSize)}</strong><span class="image-card__saved ${!savedPositive ? 'image-card__saved--negative' : ''}">${savedPositive ? `Saved ${savedPercent}%` : `Larger by ${Math.abs(savedPercent)}%`}</span></div>
            </div>
            ${img.outputQuality ? `<div class="image-card__warning image-card__warning--info">Quality ${img.outputQuality}%${settings.autoQuality ? ' · optimized' : ''}</div>` : ''}
            ${img.warnings.map((warning) => `<div class="image-card__warning">${escapeHtml(warning)}</div>`).join('')}
            <div class="image-card__actions">
              <button class="image-card__btn image-card__btn--primary" data-download="${img.id}" type="button" title="Download">
                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
                Download
              </button>
              <button class="image-card__btn" data-copy="${img.id}" type="button" title="Copy image">
                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
                Copy
              </button>
              <button class="image-card__btn" data-copy-html="${img.id}" type="button">HTML</button>
              <button class="image-card__btn" data-copy-filename="${img.id}" type="button">Name</button>
              <button class="image-card__btn" data-remove="${img.id}" type="button" title="Remove">
                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
              </button>
            </div>
          ` : ''}
        </div>
      </article>
    `;
  }).join('');

  attachCardListeners();
}

function attachCardListeners() {
  document.querySelectorAll('[data-download]').forEach((btn) => {
    btn.addEventListener('click', (event) => {
      event.stopPropagation();
      const img = state.images.find((image) => image.id === btn.dataset.download);
      if (!img?.outputBlob) return;

      downloadBlob(img.outputBlob, getOutputFilename(img));
      showToast('Downloaded');
    });
  });

  document.querySelectorAll('[data-copy]').forEach((btn) => {
    btn.addEventListener('click', async (event) => {
      event.stopPropagation();
      const img = state.images.find((image) => image.id === btn.dataset.copy);
      if (!img?.outputBlob) return;

      try {
        await navigator.clipboard.write([new ClipboardItem({ [img.outputBlob.type]: img.outputBlob })]);
        showToast('Image copied');
      } catch (error) {
        showToast('Failed to copy image');
      }
    });
  });

  document.querySelectorAll('[data-copy-html]').forEach((btn) => {
    btn.addEventListener('click', async (event) => {
      event.stopPropagation();
      const img = state.images.find((image) => image.id === btn.dataset.copyHtml);
      if (!img?.dimensions) return;

      const filename = getOutputFilename(img);
      const html = `<img src="/assets/${filename}" width="${img.dimensions.w}" height="${img.dimensions.h}" alt="">`;

      try {
        await navigator.clipboard.writeText(html);
        showToast('HTML copied');
      } catch (error) {
        showToast('Failed to copy HTML');
      }
    });
  });

  document.querySelectorAll('[data-copy-filename]').forEach((btn) => {
    btn.addEventListener('click', async (event) => {
      event.stopPropagation();
      const img = state.images.find((image) => image.id === btn.dataset.copyFilename);
      if (!img) return;
      try {
        await navigator.clipboard.writeText(getOutputFilename(img));
        showToast('Filename copied');
      } catch (error) {
        showToast('Failed to copy filename');
      }
    });
  });

  document.querySelectorAll('[data-remove]').forEach((btn) => {
    btn.addEventListener('click', (event) => {
      event.stopPropagation();
      state.images = state.images.filter((image) => image.id !== btn.dataset.remove);
      deleteImageRecord(btn.dataset.remove);
      renderAll();
    });
  });

  document.querySelectorAll('[data-rename]').forEach((btn) => {
    btn.addEventListener('click', (event) => {
      event.stopPropagation();
      const img = state.images.find((image) => image.id === btn.dataset.rename);
      if (!img) return;
      const nextName = prompt('Rename image', getBaseName(img.displayName || img.originalName));
      if (nextName !== null) renameImage(img, nextName);
    });
  });

  document.querySelectorAll('[data-enabled]').forEach((input) => {
    input.addEventListener('change', (event) => {
      const img = state.images.find((image) => image.id === input.dataset.enabled);
      if (!img) return;
      img.enabled = input.checked;
      updateImageRecord(img);
      if (img.enabled) processImage(img).then(renderAll);
      else renderAll();
    });
  });

  document.querySelectorAll('[data-format]').forEach((select) => {
    select.addEventListener('change', () => {
      const img = state.images.find((image) => image.id === select.dataset.format);
      if (img) setImageOverride(img, 'format', select.value);
    });
  });

  document.querySelectorAll('[data-quality]').forEach((input) => {
    input.addEventListener('change', () => {
      const img = state.images.find((image) => image.id === input.dataset.quality);
      if (img) setImageOverride(img, 'quality', Math.max(1, Math.min(100, parseInt(input.value, 10) || 82)));
    });
  });

  document.querySelectorAll('[data-crop-toggle]').forEach((btn) => {
    btn.addEventListener('click', (event) => {
      event.stopPropagation();
      const img = state.images.find((image) => image.id === btn.dataset.cropToggle);
      if (!img) return;
      img.cropOpen = !img.cropOpen;
      renderAll();
    });
  });

  document.querySelectorAll('[data-compare-toggle]').forEach((btn) => {
    btn.addEventListener('click', (event) => {
      event.stopPropagation();
      const img = state.images.find((image) => image.id === btn.dataset.compareToggle);
      if (!img) return;
      img.compareOriginal = !img.compareOriginal;
      renderAll();
    });
  });

  document.querySelectorAll('[data-crop-aspect]').forEach((btn) => {
    btn.addEventListener('click', (event) => {
      event.stopPropagation();
      const img = state.images.find((image) => image.id === btn.dataset.cropAspect);
      if (!img) return;
      img.cropOpen = true;
      img.settings = { ...getImageSettings(img), cropAspect: btn.dataset.aspect || 'free', cropX: 50, cropY: 50, cropZoom: 1 };
      updateImageRecord(img);
      const panel = btn.closest('.image-card__crop-panel');
      panel?.querySelectorAll('.image-card__aspect').forEach((item) => item.classList.remove('is-active'));
      btn.classList.add('is-active');
      const preview = document.querySelector(`[data-crop-stage="${img.id}"] [data-preview]`);
      if (preview) preview.setAttribute('style', getCropStyle(getImageSettings(img)));
      processImageQuiet(img);
    });
  });

  document.querySelectorAll('[data-crop-zoom]').forEach((btn) => {
    btn.addEventListener('click', (event) => {
      event.stopPropagation();
      const img = state.images.find((image) => image.id === btn.dataset.cropZoom);
      if (!img) return;
      const settings = getImageSettings(img);
      const dir = Number(btn.dataset.zoomDir) || 1;
      const nextZoom = clamp(Number(((settings.cropZoom || 1) + dir * 0.08).toFixed(2)), 1, 2.5);
      img.settings = { ...settings, cropZoom: nextZoom };
      updateImageRecord(img);
      const preview = document.querySelector(`[data-crop-stage="${img.id}"] [data-preview]`);
      if (preview) preview.setAttribute('style', getCropStyle(getImageSettings(img)));
      scheduleProcessImageQuiet(img);
    });
  });

  document.querySelectorAll('[data-crop-stage]').forEach((stage) => {
    stage.addEventListener('pointerdown', (event) => {
      const img = state.images.find((image) => image.id === stage.dataset.cropStage);
      if (!img?.cropOpen) return;
      const settings = getImageSettings(img);
      if (settings.cropAspect === 'free') return;
      event.preventDefault();
      stage.setPointerCapture(event.pointerId);
      activeCropDrag = {
        id: img.id,
        pointerId: event.pointerId,
        startX: event.clientX,
        startY: event.clientY,
        cropX: settings.cropX ?? 50,
        cropY: settings.cropY ?? 50,
        rect: stage.getBoundingClientRect()
      };
      stage.classList.add('is-dragging');
    });

    stage.addEventListener('pointermove', (event) => {
      if (!activeCropDrag || activeCropDrag.pointerId !== event.pointerId) return;
      const img = state.images.find((image) => image.id === activeCropDrag.id);
      if (!img) return;
      const nextX = clamp(activeCropDrag.cropX - ((event.clientX - activeCropDrag.startX) / activeCropDrag.rect.width) * 100, 0, 100);
      const nextY = clamp(activeCropDrag.cropY - ((event.clientY - activeCropDrag.startY) / activeCropDrag.rect.height) * 100, 0, 100);
      img.settings = { ...getImageSettings(img), cropX: Number(nextX.toFixed(2)), cropY: Number(nextY.toFixed(2)) };
      const preview = stage.querySelector('[data-preview]');
      if (preview) preview.setAttribute('style', getCropStyle(getImageSettings(img)));
    });

    stage.addEventListener('pointerup', (event) => {
      if (!activeCropDrag || activeCropDrag.pointerId !== event.pointerId) return;
      const img = state.images.find((image) => image.id === activeCropDrag.id);
      activeCropDrag = null;
      stage.classList.remove('is-dragging');
      if (img) {
        updateImageRecord(img);
        scheduleProcessImageQuiet(img, 80);
      }
    });

    stage.addEventListener('wheel', (event) => {
      const img = state.images.find((image) => image.id === stage.dataset.cropStage);
      if (!img?.cropOpen) return;
      const settings = getImageSettings(img);
      if (settings.cropAspect === 'free') return;
      event.preventDefault();
      const nextZoom = clamp(Number(((settings.cropZoom || 1) + (event.deltaY > 0 ? -0.06 : 0.06)).toFixed(2)), 1, 2.5);
      img.settings = { ...settings, cropZoom: nextZoom };
      updateImageRecord(img);
      const preview = stage.querySelector('[data-preview]');
      if (preview) preview.setAttribute('style', getCropStyle(getImageSettings(img)));
      scheduleProcessImageQuiet(img);
    }, { passive: false });

    stage.addEventListener('pointercancel', () => {
      activeCropDrag = null;
      stage.classList.remove('is-dragging');
    });
  });
}

function clamp(value, min, max) {
  return Math.max(min, Math.min(max, value));
}

function updateSummary() {
  const activeImages = state.images.filter((image) => image.enabled !== false);
  const readyImages = activeImages.filter((image) => image.status === 'ready');
  if (state.images.length === 0) {
    summaryFooter.style.display = 'none';
    return;
  }

  const totalOriginal = activeImages.reduce((sum, image) => sum + image.originalSize, 0);
  const totalOutput = readyImages.reduce((sum, image) => sum + (image.outputSize || image.originalSize), 0);
  const processingOriginal = activeImages.filter((image) => image.status === 'processing').reduce((sum, image) => sum + image.originalSize, 0);
  const displayOutput = totalOutput + processingOriginal;
  const savedPercent = totalOriginal > 0 ? Number(((1 - displayOutput / totalOriginal) * 100).toFixed(1)) : 0;
  const savedPositive = savedPercent > 0;

  summaryText.innerHTML = `
    ${activeImages.length}/${state.images.length} image${state.images.length !== 1 ? 's' : ''}
    · ${formatBytes(totalOriginal)} to ${formatBytes(displayOutput)}
    · <span class="${savedPositive ? 'is-positive' : 'is-negative'}">${savedPositive ? `Saved ${savedPercent}%` : `Larger by ${Math.abs(savedPercent)}%`}</span>
  `;
  summaryFooter.style.display = 'flex';
  downloadAllZipBtn.disabled = readyImages.length === 0;
}

downloadAllZipBtn.addEventListener('click', async () => {
  const readyImages = state.images.filter((image) => image.enabled !== false && image.status === 'ready' && image.outputBlob);
  if (readyImages.length === 0) {
    showToast('No optimized images');
    return;
  }

  try {
    const files = await Promise.all(readyImages.map(async (img, index) => ({
      name: getOutputFilename(img, index + 1),
      blob: img.outputBlob
    })));
    const content = await createZipBlob(files);
    downloadBlob(content, 'imagelite-images.zip');
    showToast('ZIP downloaded');
  } catch (error) {
    showToast('Failed to create ZIP');
    console.error(error);
  }
});

clearImagesBtn.addEventListener('click', async () => {
  state.images = [];
  await clearImageRecords();
  renderAll();
  showToast('Cleared');
});

function getOutputFilename(imageData, index = 1) {
  const settings = getImageSettings(imageData);
  const source = imageData.displayName || imageData.originalName;
  const baseName = getBaseName(source);
  const pattern = settings.filenamePattern || '{name}';
  const safeBase = pattern
    .replaceAll('{name}', baseName)
    .replaceAll('{original}', getBaseName(imageData.originalName))
    .replaceAll('{n}', String(index))
    .replace(/[\\/:*?"<>|]/g, '-')
    .trim() || baseName;
  return `${safeBase}${getFormatExtension(imageData.outputType || settings.format)}`;
}

function getFormatLabel(mimeType) {
  return {
    'image/webp': 'WebP',
    'image/jpeg': 'JPEG',
    'image/png': 'PNG',
    'image/avif': 'AVIF'
  }[mimeType] || mimeType;
}

function getFormatExtension(mimeType) {
  return {
    'image/webp': '.webp',
    'image/jpeg': '.jpg',
    'image/png': '.png',
    'image/avif': '.avif'
  }[mimeType] || '.png';
}

const crcTable = (() => {
  const table = [];
  for (let n = 0; n < 256; n++) {
    let c = n;
    for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
    table[n] = c >>> 0;
  }
  return table;
})();

function crc32(bytes) {
  let c = 0xffffffff;
  for (const b of bytes) c = crcTable[(c ^ b) & 255] ^ (c >>> 8);
  return (c ^ 0xffffffff) >>> 0;
}

function u16(value) {
  const buffer = new ArrayBuffer(2);
  new DataView(buffer).setUint16(0, value, true);
  return new Uint8Array(buffer);
}

function u32(value) {
  const buffer = new ArrayBuffer(4);
  new DataView(buffer).setUint32(0, value >>> 0, true);
  return new Uint8Array(buffer);
}

function dosDateTime(date = new Date()) {
  const year = Math.max(1980, date.getFullYear());
  return {
    time: (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2),
    date: ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate()
  };
}

async function createZipBlob(files) {
  const encoder = new TextEncoder();
  const chunks = [];
  const central = [];
  let offset = 0;

  for (const file of files) {
    const data = new Uint8Array(await file.blob.arrayBuffer());
    const name = encoder.encode(file.name);
    const crc = crc32(data);
    const dt = dosDateTime();
    const local = concatBytes([
      u32(0x04034b50), u16(20), u16(0), u16(0), u16(dt.time), u16(dt.date),
      u32(crc), u32(data.length), u32(data.length), u16(name.length), u16(0), name
    ]);
    chunks.push(local, data);
    central.push(concatBytes([
      u32(0x02014b50), u16(20), u16(20), u16(0), u16(0), u16(dt.time), u16(dt.date),
      u32(crc), u32(data.length), u32(data.length), u16(name.length), u16(0), u16(0),
      u16(0), u16(0), u32(0), u32(offset), name
    ]));
    offset += local.length + data.length;
  }

  const centralDir = concatBytes(central);
  const end = concatBytes([
    u32(0x06054b50), u16(0), u16(0), u16(files.length), u16(files.length),
    u32(centralDir.length), u32(offset), u16(0)
  ]);

  return new Blob([...chunks, centralDir, end], { type: 'application/zip' });
}

function concatBytes(parts) {
  const length = parts.reduce((sum, part) => sum + part.length, 0);
  const out = new Uint8Array(length);
  let offset = 0;
  for (const part of parts) {
    out.set(part, offset);
    offset += part.length;
  }
  return out;
}

function formatBytes(bytes) {
  if (bytes === 0) return '0 B';

  const k = 1024;
  const sizes = ['B', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  const val = parseFloat((bytes / Math.pow(k, i)).toFixed(1));
  return `${val} ${sizes[i]}`;
}

function downloadBlob(blob, filename) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}

function showToast(message) {
  let toast = document.querySelector('.toast');
  if (!toast) {
    toast = document.createElement('div');
    toast.className = 'toast';
    document.body.appendChild(toast);
  }

  toast.textContent = message;
  toast.classList.add('is-visible');
  clearTimeout(toast._timeout);
  toast._timeout = setTimeout(() => toast.classList.remove('is-visible'), 2000);
}

function escapeHtml(value) {
  const div = document.createElement('div');
  div.textContent = value;
  return div.innerHTML;
}

chrome.action.setBadgeText({ text: '' });
(async function init() {
  await loadSettings();
  await restoreImages();
  loadPendingImage();
  renderAll();
})();
