const domReady = e => { "loading" === document.readyState ? document.addEventListener("DOMContentLoaded", e) : e() }, retryInit = 30; let retry = retryInit, observer = null, isLoading = !1, urlRef = null, parentTargetRef = null; chrome.runtime.onMessage.addListener((e => { switch (e.cmd) { case "pasteKeywords": document.querySelector("#content-keywords-ui-textarea").focus(), document.execCommand("paste"), doUnique(); break; case "pasteTitle": document.querySelector('textarea[data-t="asset-title-content-tagger"]').focus(), document.execCommand("paste"); break; case "createBtn": retry = retryInit, domReady(start); break; case "localResult": handleLocalResult(e.data, e.searchType); break; case "localModeChanged": updateLocalButtons() } })); let titleField, titleBtnDiv, btnDiv = null; function createAdditionBtn() { const e = qSelect(".container-absolute.container-full.c-align"); if (!e) return; const t = document.createElement("button"); t.id = "easyKeywordNavBtn", t.classList.add("nav__item", "button", "button--action"), t.setAttribute("data-t", "sub_menu_easy_keyword"), t.innerText = i18n("extShortName"), t.addEventListener("click", openDrawer); const n = document.createElement("div"); n.style.textAlign = "center", n.append(t), e.append(n); } async function openDrawer() { chrome.runtime.sendMessage({ cmd: "openDrawerFromBackground", target: "background" }) }

function showNiceAlert(msg, type = "info") {
  const toast = document.createElement('div');
  toast.style.cssText = `
    position: fixed; top: 20px; right: 20px; z-index: 1000000;
    padding: 16px 24px; border-radius: 12px; color: #fff;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    font-size: 14px; font-weight: 600; box-shadow: 0 10px 30px rgba(0,0,0,0.3);
    backdrop-filter: blur(8px); transform: translateX(120%);
    transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    background: ${type === 'error' ? 'linear-gradient(135deg, #ff7675, #d63031)' : 'linear-gradient(135deg, #00b894, #00cec9)'};
  `;
  toast.textContent = msg;
  document.body.appendChild(toast);
  requestAnimationFrame(() => toast.style.transform = 'translateX(0)');
  setTimeout(() => {
    toast.style.transform = 'translateX(120%)';
    setTimeout(() => toast.remove(), 400);
  }, 4000);
}

async function start() {
  const e = window.location.pathname.split("/"), t = "uploads" === e[e.length - 1];
  if (!t) return setTimeout(start, 1000);

  // 1. Check if cards exist to show/hide navigation button
  const hasCards = document.querySelectorAll(".upload-tile__wrapper").length > 0;
  const navBtn = qSelect("#easyKeywordNavBtn");
  if (hasCards) {
    if (!navBtn) createAdditionBtn();
  } else {
    if (navBtn) navBtn.parentElement.remove(); // Remove the wrapper div too
  }

  // 2. Ensure Floating Bar exists on Uploads page
  if (!qSelect("#floatingControls")) {
    createLocalButtons(); // Call without containers if missing
  }

  // 3. Grid-specific buttons (if grid header exists)
  btnDiv = document.querySelector(".margin-bottom-xsmall.clear-fix.container-full");
  if (btnDiv) {
    createBtn();
  } else {
    setTimeout(start, 500);
  }
}
function checkMultipleSelect() { return document.querySelectorAll(".upload-tile__wrapper.active").length > 1 } function showBtn(e) { const t = qSelect("#btnContain"), n = qSelect("#titleBtnContain"); try { e ? (t.style.display = "grid", n.style.display = "grid") : (t.style.display = "none", n.style.display = "none") } catch (e) { return } } async function createBtn() {
  if (checkMultipleSelect()) return showBtn(!1); showBtn(!0); try { if (document.querySelector("#keyUniqueBtn")) return null } catch { return retry = retryInit, domReady(start) } const o = i18n("btnRemoveRep"); titleBtnDiv = qSelect(".mobile-tagger-details.visible"), btnDiv = document.querySelector(".margin-bottom-xsmall.clear-fix.container-full"); const i = document.createElement("div"); i.id = "btnContain"; const l = document.createElement("button"); l.id = "keyUniqueBtn", l.classList.add("keyBtn"), l.innerHTML = `<img class="extensionLogo" src="${chrome.runtime.getURL("images/eraser-solid.svg")}"><span>${o}</span>`, l.addEventListener("click", (() => { doUnique() })); const u = document.createElement("div"); u.id = "titleBtnContain", titleBtnDiv.prepend(u), i.prepend(l), btnDiv.prepend(i), checkMutations(), addOtherEvent(), addClickListenerToNode(); const m = document.querySelectorAll(".content-upload-menu .nav__link")[1]; m && (m.length <= 0 || m.addEventListener("click", handleSelectAllBtnClick));
  // === LOCAL MODE BUTTONS ===
  createLocalButtons(i, u)
}

// === LOCAL MODE UI ===
const ICONS = {
  main: `<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M12 8v8"></path><path d="M8 12h8"></path></svg>`,
  upload: `<svg viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>`,
  concept: `<svg viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>`,
  setup: `<svg viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>`,
  generate: `<svg viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>`,
  auto: `<svg viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v4"></path><path d="M12 18v4"></path><path d="M4.93 4.93l2.83 2.83"></path><path d="M16.24 16.24l2.83 2.83"></path><path d="M2 12h4"></path><path d="M18 12h4"></path><path d="M4.93 19.07l2.83-2.83"></path><path d="M16.24 7.76l2.83-2.83"></path></svg>`,
  check: `<svg viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>`,
  close: `<svg viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`
};

async function createLocalButtons(kwContainer, titleContainer) {
  document.querySelectorAll('.localKeyBtn,.localTitleBtn,.localSetupBtn, .floating-controls, #floatingTrigger').forEach(b => b.remove());

  const trigger = document.createElement("button");
  trigger.id = "floatingTrigger";
  trigger.className = "floating-trigger active";
  trigger.innerHTML = ICONS.close;
  document.body.appendChild(trigger);

  const floatingDiv = document.createElement("div");
  floatingDiv.id = "floatingControls";
  floatingDiv.className = "floating-controls";
  document.body.appendChild(floatingDiv);

  // Close button pinned to top-right of floating bar
  const closeBtn = document.createElement("button");
  closeBtn.id = "floatingCloseBtn";
  closeBtn.className = "floating-close-btn";
  closeBtn.innerHTML = ICONS.close;
  closeBtn.title = "Close";
  closeBtn.addEventListener("click", () => {
    floatingDiv.classList.add("collapsed");
    trigger.classList.remove("active");
    trigger.innerHTML = ICONS.main;
  });
  floatingDiv.appendChild(closeBtn);

  trigger.addEventListener("click", () => {
    floatingDiv.classList.toggle("collapsed");
    trigger.classList.toggle("active");
    trigger.innerHTML = floatingDiv.classList.contains("collapsed") ? ICONS.main : ICONS.close;
  });

  // Check if Smart Upload is enabled before adding the button
  chrome.storage.local.get({ localSettings: {} }, (data) => {
    const settings = data.localSettings || {};
    if (settings.enableSmartUpload) {
      const uploadBtn = document.createElement("button");
      uploadBtn.id = "localUploadBtn";
      uploadBtn.className = "keyBtn";
      uploadBtn.style.cssText = "background:linear-gradient(135deg,#0984e3,#74b9ff)!important;color:#fff;";
      uploadBtn.innerHTML = ICONS.upload;
      uploadBtn.title = "Smart Upload";
      uploadBtn.addEventListener("click", () => smartUpload());
      // Insert after trigger but before conceptBtn (which we haven't created yet in this async callback, so we use appendChild and order might change, let's fix that by inserting before a specific element)
      floatingDiv.insertBefore(uploadBtn, floatingDiv.children[1]); // Index 1 is right after trigger
    }
  });

  // Key Concept Button (replacing input)
  const conceptBtn = document.createElement("button");
  conceptBtn.id = "localKeyConceptBtn";
  conceptBtn.className = "keyBtn";
  conceptBtn.style.cssText = "background:rgba(255,255,255,0.1)!important;color:#fff;border:1px solid rgba(255,255,255,0.2)!important;";
  conceptBtn.innerHTML = `${ICONS.concept}<span>Key Concept</span>`;
  conceptBtn.addEventListener("click", () => showKeyConceptModal());
  floatingDiv.appendChild(conceptBtn);

  const setupBtn = document.createElement("button");
  setupBtn.id = "localSetupBtn";
  setupBtn.className = "keyBtn";
  setupBtn.style.cssText = "background:rgba(255,255,255,0.05)!important;color:#fff;";
  setupBtn.innerHTML = ICONS.setup;
  setupBtn.title = "Setup";
  setupBtn.addEventListener("click", () => chrome.runtime.sendMessage({ cmd: "openLocalSetup" }));
  floatingDiv.appendChild(setupBtn);

  const localKwBtn = document.createElement("button");
  localKwBtn.id = "localKwBtn_manual";
  localKwBtn.className = "keyBtn localKeyBtn";
  localKwBtn.style.cssText = "background:linear-gradient(135deg,#6c5ce7,#a29bfe)!important;";
  localKwBtn.innerHTML = `${ICONS.generate}<span>Keywords</span>`;
  localKwBtn.addEventListener("click", () => generateSelected("keywords", localKwBtn));

  const localTitleBtn = document.createElement("button");
  localTitleBtn.id = "localTitleBtn_manual";
  localTitleBtn.className = "titleBtn localTitleBtn";
  localTitleBtn.style.cssText = "background:linear-gradient(135deg,#6c5ce7,#a29bfe)!important;";
  localTitleBtn.innerHTML = `${ICONS.generate}<span>Title</span>`;
  localTitleBtn.addEventListener("click", () => generateSelected("title", localTitleBtn));

  if (kwContainer) kwContainer.appendChild(localKwBtn);
  if (titleContainer) titleContainer.appendChild(localTitleBtn);

  const genBtn = document.createElement("button");
  genBtn.id = "localGenBtn";
  genBtn.className = "keyBtn";
  genBtn.style.cssText = "background:#00b894!important;color:#fff;";
  genBtn.innerHTML = `${ICONS.generate}<span>Generate</span>`;
  genBtn.addEventListener("click", () => generateSelected("both", genBtn));
  floatingDiv.appendChild(genBtn);

  const autoBtn = document.createElement("button");
  autoBtn.id = "localAutoBtn";
  autoBtn.className = "keyBtn";
  autoBtn.style.cssText = "background:#e17055!important;color:#fff;";
  autoBtn.innerHTML = `${ICONS.auto}<span>Auto</span>`;
  autoBtn.addEventListener("click", () => startAutoProcess());
  floatingDiv.appendChild(autoBtn);



  const overlay = document.createElement("div");
  overlay.id = "generationOverlay";
  overlay.className = "generation-overlay";
  overlay.innerHTML = `
    <div class="overlay-thumb-container">
      <img id="overlayThumb" class="overlay-thumb" src="">
      <img class="overlay-robot" src="${chrome.runtime.getURL("images/robot ai.gif")}">
    </div>
    <div id="overlayStatus" class="overlay-status">Starting...</div>
    <div class="overlay-progress-dots"></div>
  `;
  document.body.appendChild(overlay);

  const statusSpan = document.createElement("span");
  statusSpan.id = "autoStatus";
  statusSpan.className = "auto-status";
  floatingDiv.appendChild(statusSpan);
}

function updateOverlay(imgSrc, status, active = true) {
  const overlay = document.getElementById("generationOverlay");
  const thumb = document.getElementById("overlayThumb");
  const statusEl = document.getElementById("overlayStatus");
  const statusSpan = document.getElementById("autoStatus");

  if (!overlay) return;

  if (active) {
    overlay.classList.add("active");
    if (imgSrc) thumb.src = imgSrc;
    if (status) {
      statusEl.textContent = status;
      if (statusSpan) statusSpan.textContent = status;
    }
  } else {
    overlay.classList.remove("active");
    if (thumb) thumb.src = "";
  }
}

async function showKeyConceptModal() {
  const settings = await localGet("localSettings", {});
  const currentConcept = settings.keyConcept || "";

  const existing = document.getElementById('keyConceptModal');
  if (existing) existing.remove();

  const modal = document.createElement('div');
  modal.id = 'keyConceptModal';
  modal.className = 'modal-overlay';
  modal.innerHTML = `
    <div class="modal-panel">
      <div class="modal-header">
        <span class="modal-icon">${ICONS.concept}</span>
        <h3>Key Concept Settings</h3>
      </div>
      <div class="modal-body">
        <p class="modal-desc">
          Key Concept membantu AI memahami fokus utama dari gambar Anda. 
          Misalnya: "indonesian food", "modern office", "nature sunset".
        </p>
        <div class="input-group">
          <label for="modalKeyConceptInput">Concept Keywords</label>
          <input type="text" id="modalKeyConceptInput" value="${currentConcept}" placeholder="Enter concept keywords...">
        </div>
      </div>
      <div class="modal-footer">
        <button class="modal-btn cancel" id="conceptCancel">Batal</button>
        <button class="modal-btn primary" id="conceptSave">Simpan Perubahan</button>
      </div>
    </div>
  `;

  document.body.appendChild(modal);

  const input = document.getElementById('modalKeyConceptInput');
  input.focus();

  const handleSave = async () => {
    const inputEl = document.getElementById('modalKeyConceptInput');
    const val = inputEl ? inputEl.value.trim() : "";
    console.log("[Concept] Saving value:", val);

    const currentSettings = await localGet("localSettings", {});
    currentSettings.keyConcept = val;
    await localSet("localSettings", currentSettings);

    modal.remove();
    showNiceAlert("Key Concept tersimpan: " + (val || "None"));
  };

  input.addEventListener('keydown', (e) => {
    if (e.key === 'Enter') {
      e.preventDefault();
      handleSave();
    }
  });

  document.getElementById('conceptCancel').addEventListener('click', () => modal.remove());
  document.getElementById('conceptSave').addEventListener('click', handleSave);

  modal.addEventListener('click', (e) => {
    if (e.target === modal) modal.remove();
  });
}


// === FILE TRACKING ===
// Database recording via Firebase Firestore

// Partial SHA-256 content hash — hashes file size + 64KB start, middle, and end chunks
// Extremely fast and memory efficient even for 1GB+ video files
async function computeContentHash(file) {
  const CHUNK_SIZE = 64 * 1024; // 64 KB
  const size = file.size;
  
  const chunks = [];

  if (size <= CHUNK_SIZE * 3) {
    // BACKWARD COMPATIBILITY: If file is small (< 192KB), just read the whole thing
    // This ensures previously uploaded small images still have the EXACT same hash.
    chunks.push(new Uint8Array(await file.arrayBuffer()));
  } else {
    // Include exact file size for large files to guarantee uniqueness
    chunks.push(new TextEncoder().encode(size.toString()));
    
    // 1. Start chunk
    chunks.push(new Uint8Array(await file.slice(0, CHUNK_SIZE).arrayBuffer()));
    
    // 2. Middle chunk
    const midPoint = Math.floor(size / 2);
    chunks.push(new Uint8Array(await file.slice(midPoint, midPoint + CHUNK_SIZE).arrayBuffer()));
    
    // 3. End chunk
    chunks.push(new Uint8Array(await file.slice(size - CHUNK_SIZE, size).arrayBuffer()));
  }
  
  // Combine chunks
  const totalLength = chunks.reduce((acc, curr) => acc + curr.byteLength, 0);
  const combinedBuffer = new Uint8Array(totalLength);
  let offset = 0;
  for (const chunk of chunks) {
    combinedBuffer.set(chunk, offset);
    offset += chunk.byteLength;
  }
  
  // Use .buffer to pass the underlying ArrayBuffer (safer for crypto API)
  const hashBuffer = await crypto.subtle.digest('SHA-256', combinedBuffer.buffer);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.slice(0, 16).map(b => b.toString(16).padStart(2, '0')).join('');
}

// Filename-based hash — used as fallback when file bytes aren't available (auto-process)
function shortHash(str) {
  // cyrb53-based hash — 53-bit precision
  let h1 = 0xdeadbeef, h2 = 0x41c6ce57;
  for (let i = 0; i < str.length; i++) {
    const ch = str.charCodeAt(i);
    h1 = Math.imul(h1 ^ ch, 2654435761);
    h2 = Math.imul(h2 ^ ch, 1597334677);
  }
  h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
  h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
  h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
  h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
  const full = 4294967296 * (2097151 & h2) + (h1 >>> 0);
  return full.toString(36);
}

// Legacy DJB2 hash — kept for backward compatibility with existing DB records
function legacyHash(str) {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    hash = (hash << 5) - hash + str.charCodeAt(i);
    hash |= 0;
  }
  return Math.abs(hash).toString(36);
}

// Save to DB by filename (writes under BOTH new and legacy hash for backward compat)
// Used by auto-process where we don't have file bytes
async function trackFile(originalName, extra = {}) {
  if (!originalName) return;
  const key = originalName.toLowerCase().trim();
  const hash = shortHash(key);
  const legacy = legacyHash(key);

  console.log(`[Track] Saving to DB (filename): ${originalName} -> new:${hash}, legacy:${legacy}`);

  // Write new hash
  const writeNew = new Promise((resolve) => {
    chrome.runtime.sendMessage(
      { cmd: 'recordDb', target: 'background', filename: originalName, hash: hash },
      (resp) => {
        if (chrome.runtime.lastError || (resp && !resp.success)) {
          const err = chrome.runtime.lastError?.message || resp?.error;
          console.warn('[Track] Error (new hash):', err);
          resolve(false);
        } else {
          resolve(resp?.success || false);
        }
      }
    );
  });

  // Also write legacy hash so old extension versions can still detect it
  let writeLegacy = Promise.resolve(true);
  if (legacy !== hash) {
    writeLegacy = new Promise((resolve) => {
      chrome.runtime.sendMessage(
        { cmd: 'recordDb', target: 'background', filename: originalName, hash: legacy },
        (resp) => {
          if (chrome.runtime.lastError) {
            console.warn('[Track] Error (legacy hash):', chrome.runtime.lastError.message);
            resolve(false);
          } else {
            resolve(resp?.success || false);
          }
        }
      );
    });
  }

  // Await both writes to ensure data is persisted before returning
  const [resultNew, resultLegacy] = await Promise.all([writeNew, writeLegacy]);
  if (!resultNew) {
    showNiceAlert("Gagal catat ke DB: file tracking error", "error");
  }
  return resultNew;
}

// Save to DB by content hash (SHA-256) — most accurate method
// Used by Smart Upload where we have the actual File objects
async function trackFileByHash(contentHash, filename) {
  if (!contentHash) return false;
  console.log(`[Track] Saving to DB (content): ${filename} -> sha:${contentHash}`);

  return new Promise((resolve) => {
    chrome.runtime.sendMessage(
      { cmd: 'recordDb', target: 'background', filename: filename, hash: `sha_${contentHash}` },
      (resp) => {
        if (chrome.runtime.lastError || (resp && !resp.success)) {
          console.warn('[Track] Error (content hash):', chrome.runtime.lastError?.message || resp?.error);
          resolve(false);
        } else {
          resolve(resp?.success || false);
        }
      }
    );
  });
}

// Check if file exists in DB (checks BOTH new and legacy hash)
// Used by auto-process where we only have the filename
async function isFileTracked(originalName) {
  if (!originalName) return false;
  const key = originalName.toLowerCase().trim();
  const newHash = shortHash(key);
  const legacy = legacyHash(key);

  // Check both hashes in a single batch call
  const allHashes = [newHash];
  if (legacy !== newHash) allHashes.push(legacy);

  const resp = await new Promise((resolve) => {
    chrome.runtime.sendMessage(
      { cmd: 'checkBatchDb', target: 'background', hashes: allHashes },
      (res) => resolve(res)
    );
  });

  return resp && resp.duplicates && resp.duplicates.length > 0;
}

function extractFileId(url) {
  if (!url) return null;
  const match = url.match(/220_F_(\d+_[a-zA-Z0-9]+)/);
  if (match) return match[1];
  const parts = url.split('/');
  return parts[parts.length - 1].replace(/\.[^/.]+$/, '') || null;
}

function getOriginalFilename() {
  const el = document.querySelector('[data-t="asset-sidebar-footer"] .text-sregular')
    || document.querySelector('[data-t="asset-sidebar-footer"]');
  if (!el) { console.warn('[Track] Footer element not found'); return null; }
  const text = el.textContent || '';
  // Match various Adobe Stock labels for the original filename
  const match = text.match(/Original name\(s\):\s*(.+?)$/i)
    || text.match(/Former name:\s*(.+?)$/i)
    || text.match(/name\(s\):\s*(.+?)$/i)
    || text.match(/Original name:\s*(.+?)$/i)
    || text.match(/File name:\s*(.+?)$/i);

  if (match && match[1]) {
    const name = match[1].trim().split('\n')[0]; // Take only the first line if multiple
    console.log('[Track] Found filename:', name);
    return name;
  }
  console.warn('[Track] Could not parse filename from:', text);
  return null;
}

function getAdobeFileCount() {
  // Try several possible selectors for the count label
  const selectors = [
    '[data-t="content-filter-menu"] .CqADjq_spectrum-ActionButton-label',
    '[data-t="content-filter-menu"] [class*="label"]',
    '.uploader__status-text',
    '.upload-tiles-count',
    'button[data-t="upload-file-tab"] span'
  ];

  for (const sel of selectors) {
    const el = document.querySelector(sel);
    if (el) {
      const match = (el.textContent || '').match(/\((\d+)\)/) || (el.textContent || '').match(/(\d+)/);
      if (match) return parseInt(match[1]);
    }
  }

  // Fallback: count the actual tiles in the grid
  const tiles = document.querySelectorAll('.upload-tile__wrapper');
  if (tiles.length > 0) return tiles.length;

  return -1;
}


// === SMART UPLOAD SYSTEM ===
// 1. Open file picker  2. Record selected files  3. Check duplicates  4. Eliminate  5. Upload only new
async function smartUpload() {
  const statusSpan = document.getElementById('autoStatus');
  const uploadBtn = document.getElementById('localUploadBtn');

  // Switch to upload tab
  const tabBtn = document.querySelector('button[data-t="upload-file-tab"]');
  if (tabBtn) {
    tabBtn.click();
    await delay(800);
  }

  // Find or create a hidden file input to intercept selection
  let fileInput = document.getElementById('__smartUploadInput');
  if (!fileInput) {
    fileInput = document.createElement('input');
    fileInput.type = 'file';
    fileInput.id = '__smartUploadInput';
    fileInput.multiple = true;
    fileInput.accept = 'image/*,video/*,.eps,.ai,.svg';
    fileInput.style.display = 'none';
    document.body.appendChild(fileInput);
  }

  // Update button state
  if (uploadBtn) {
    uploadBtn.innerHTML = ICONS.upload;
    uploadBtn.style.cssText = "background:linear-gradient(135deg,#fdcb6e,#e17055)!important;color:#fff;";
  }
  if (statusSpan) statusSpan.textContent = 'Select files...';

  // Listen for file selection
  fileInput.onchange = async (e) => {
    const files = Array.from(e.target.files);
    if (!files.length) {
      resetUploadBtn();
      return;
    }
    await processSelectedFiles(files);
    fileInput.value = ''; // reset for next use
  };

  fileInput.click();

  // If user cancels the file picker
  window.addEventListener('focus', function onFocus() {
    setTimeout(() => {
      if (!fileInput.files || fileInput.files.length === 0) {
        resetUploadBtn();
      }
      window.removeEventListener('focus', onFocus);
    }, 500);
  }, { once: true });
}

function resetUploadBtn() {
  const uploadBtn = document.getElementById('localUploadBtn');
  const statusSpan = document.getElementById('autoStatus');
  if (uploadBtn) {
    uploadBtn.innerHTML = ICONS.upload;
    uploadBtn.style.cssText = "background:linear-gradient(135deg,#0984e3,#74b9ff)!important;color:#fff;";
  }
  if (statusSpan) statusSpan.textContent = '';
}

async function processSelectedFiles(files) {
  const statusSpan = document.getElementById('autoStatus');
  const uploadBtn = document.getElementById('localUploadBtn');

  if (uploadBtn) uploadBtn.innerHTML = ICONS.upload;
  if (statusSpan) statusSpan.textContent = `Hashing ${files.length} files...`;

  const fileNames = files.map(f => f.name);
  console.log('[SmartUpload] Selected files:', fileNames);

  // 1. Compute SHA-256 content hash for each file (accurate, content-based)
  const contentHashes = [];
  const hashToFile = {};  // contentHash -> file object
  const nameToHashes = {}; // lowercase name -> { content, filename, legacy }

  for (let i = 0; i < files.length; i++) {
    if (statusSpan) statusSpan.textContent = `Hashing file ${i + 1}/${files.length}...`;
    try {
      const ch = await computeContentHash(files[i]);
      contentHashes.push(ch);
      hashToFile[ch] = files[i];
      const key = files[i].name.toLowerCase().trim();
      nameToHashes[key] = {
        content: ch,
        filename: shortHash(key),
        legacy: legacyHash(key)
      };
    } catch (e) {
      console.warn('[SmartUpload] Hash error for', files[i].name, e);
      contentHashes.push(null);
    }
  }

  if (statusSpan) statusSpan.textContent = `Checking ${files.length} files for duplicates...`;

  // 2. Build combined hash list: content hashes + filename hashes (backward compat)
  const allHashes = [];
  const hashToName = {}; // any hash -> lowercase filename

  files.forEach((f, i) => {
    const key = f.name.toLowerCase().trim();
    const info = nameToHashes[key];
    if (!info) return;

    // Content hash (prefixed with sha_ to distinguish from filename hashes)
    const shaKey = `sha_${info.content}`;
    hashToName[shaKey] = key;
    if (!allHashes.includes(shaKey)) allHashes.push(shaKey);

    // Also check filename hashes (backward compat with old records)
    hashToName[info.filename] = key;
    if (!allHashes.includes(info.filename)) allHashes.push(info.filename);

    if (info.legacy !== info.filename) {
      hashToName[info.legacy] = key;
      if (!allHashes.includes(info.legacy)) allHashes.push(info.legacy);
    }
  });

  // 3. Check all hashes against DB in one batch call
  console.log('[SmartUpload] Sending checkBatchDb with', allHashes.length, 'hashes:', allHashes);
  const dbResp = await new Promise((resolve) => {
    chrome.runtime.sendMessage(
      { cmd: 'checkBatchDb', target: 'background', hashes: allHashes },
      (res) => {
        if (chrome.runtime.lastError) {
          console.error('[SmartUpload] checkBatchDb chrome error:', chrome.runtime.lastError.message);
          resolve({ success: false, error: chrome.runtime.lastError.message, duplicates: [], results: {} });
        } else {
          console.log('[SmartUpload] checkBatchDb response:', JSON.stringify(res));
          resolve(res);
        }
      }
    );
  });

  if (!dbResp || dbResp.error) {
    console.error('[SmartUpload] DB check failed:', dbResp);
    showNiceAlert("Gagal cek database: " + (dbResp?.error || 'No response'), "error");
    resetUploadBtn();
    return;
  }

  // 4. Collect duplicates (deduplicate by filename)
  let duplicates = [];
  let duplicateDetails = {};

  if (dbResp && dbResp.duplicates) {
    const seenNames = new Set();
    dbResp.duplicates.forEach(h => {
      const origName = hashToName[h];
      if (origName && !seenNames.has(origName)) {
        seenNames.add(origName);
        duplicates.push(origName);
        duplicateDetails[origName] = dbResp.results[h];
      }
    });
  }

  // 5. Separate new files vs duplicates
  const dupSet = new Set(duplicates);
  const newFiles = files.filter(f => !dupSet.has(f.name.toLowerCase().trim()));
  const dupFiles = files.filter(f => dupSet.has(f.name.toLowerCase().trim()));

  // Attach content hashes to newFiles for optimistic tracking
  newFiles.forEach(f => {
    const key = f.name.toLowerCase().trim();
    f._contentHash = nameToHashes[key]?.content || null;
  });

  console.log(`[SmartUpload] RESULT: ${newFiles.length} new, ${dupFiles.length} duplicates (checked ${allHashes.length} hashes in 1 batch)`);
  if (dupFiles.length > 0) {
    console.log('[SmartUpload] Duplicate files:', dupFiles.map(f => f.name));
  }
  if (newFiles.length > 0) {
    console.log('[SmartUpload] New files:', newFiles.map(f => `${f.name} (hash:${f._contentHash || 'none'})`));
  }

  // 6. Show summary toast
  showUploadToast(newFiles, dupFiles, duplicateDetails);
}

function showUploadToast(newFiles, dupFiles, dupDetails) {
  // Remove existing toast
  const existing = document.getElementById('smartUploadToast');
  if (existing) existing.remove();

  const toast = document.createElement('div');
  toast.id = 'smartUploadToast';
  toast.innerHTML = `
    <style>
      #smartUploadToast {
        position: fixed;
        top: 0; left: 0; right: 0; bottom: 0;
        background: rgba(0,0,0,0.7);
        backdrop-filter: blur(4px);
        z-index: 999999;
        display: flex;
        align-items: center;
        justify-content: center;
        animation: fadeInToast 0.3s ease;
      }
      @keyframes fadeInToast { from { opacity: 0; } to { opacity: 1; } }
      .sut-panel {
        background: #1a1a2e;
        border: 1px solid #3d3d5c;
        border-radius: 16px;
        width: 560px;
        max-height: 80vh;
        overflow: hidden;
        box-shadow: 0 20px 60px rgba(0,0,0,0.5);
        display: flex;
        flex-direction: column;
      }
      .sut-header {
        padding: 20px 24px;
        background: linear-gradient(135deg, #0984e3, #6c5ce7);
        color: #fff;
        display: flex;
        align-items: center;
        gap: 12px;
      }
      .sut-header h3 { margin: 0; font-size: 18px; font-weight: 600; }
      .sut-header .sut-summary {
        margin-left: auto;
        font-size: 13px;
        opacity: 0.9;
        background: rgba(255,255,255,0.15);
        padding: 4px 12px;
        border-radius: 20px;
      }
      .sut-body {
        padding: 16px 24px;
        overflow-y: auto;
        max-height: 50vh;
      }
      .sut-section-title {
        font-size: 13px;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 1px;
        margin: 12px 0 8px;
        display: flex;
        align-items: center;
        gap: 8px;
      }
      .sut-section-title.new { color: #00b894; }
      .sut-section-title.dup { color: #e17055; }
      .sut-file-list {
        list-style: none;
        margin: 0; padding: 0;
      }
      .sut-file-item {
        display: flex;
        align-items: center;
        gap: 10px;
        padding: 8px 12px;
        margin-bottom: 4px;
        border-radius: 8px;
        font-size: 13px;
        color: #dfe6e9;
        font-family: 'Consolas', monospace;
      }
      .sut-file-item.new {
        background: rgba(0,184,148,0.1);
        border: 1px solid rgba(0,184,148,0.2);
      }
      .sut-file-item.dup {
        background: rgba(225,112,85,0.1);
        border: 1px solid rgba(225,112,85,0.2);
        text-decoration: line-through;
        opacity: 0.7;
      }
      .sut-file-item .sut-icon { font-size: 16px; flex-shrink: 0; }
      .sut-file-item .sut-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
      .sut-file-item .sut-date { font-size: 11px; color: #b2bec3; flex-shrink: 0; }
      .sut-footer {
        padding: 16px 24px;
        border-top: 1px solid #3d3d5c;
        display: flex;
        gap: 10px;
        justify-content: flex-end;
        align-items: center;
      }
      .sut-footer .sut-info {
        flex: 1;
        font-size: 12px;
        color: #b2bec3;
      }
      .sut-btn {
        padding: 10px 24px;
        border-radius: 8px;
        border: none;
        font-size: 14px;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.2s;
      }
      .sut-btn:hover { transform: translateY(-1px); filter: brightness(1.1); }
      .sut-btn.primary { background: #00b894; color: #fff; }
      .sut-btn.cancel { background: transparent; border: 1px solid #3d3d5c; color: #b2bec3; }
      .sut-btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
      .sut-empty {
        text-align: center;
        padding: 20px;
        color: #e17055;
        font-size: 14px;
      }
    </style>
    <div class="sut-panel">
      <div class="sut-header">
        <span style="font-size:24px">📋</span>
        <h3>Smart Upload Summary</h3>
        <span class="sut-summary">${newFiles.length + dupFiles.length} file(s) selected</span>
      </div>
      <div class="sut-body">
        ${dupFiles.length > 0 ? `
          <div class="sut-section-title dup">🚫 Duplicates — akan diskip (${dupFiles.length})</div>
          <ul class="sut-file-list">
            ${dupFiles.map(f => {
    const key = f.name.toLowerCase().trim();
    const info = dupDetails[key]?.data;
    const dateStr = info?.lastSeen ? new Date(info.lastSeen).toLocaleDateString('id-ID') : '';
    return `<li class="sut-file-item dup">
                <span class="sut-icon">❌</span>
                <span class="sut-name">${f.name}</span>
                ${dateStr ? `<span class="sut-date">uploaded ${dateStr}</span>` : ''}
              </li>`;
  }).join('')}
          </ul>
        ` : ''}
        ${newFiles.length > 0 ? `
          <div class="sut-section-title new">✅ File baru — siap upload (${newFiles.length})</div>
          <ul class="sut-file-list">
            ${newFiles.map(f => `
              <li class="sut-file-item new">
                <span class="sut-icon">📄</span>
                <span class="sut-name">${f.name}</span>
                <span class="sut-date">${(f.size / 1024).toFixed(0)} KB</span>
              </li>
            `).join('')}
          </ul>
        ` : `
          <div class="sut-empty">⚠️ Semua file sudah pernah diupload!<br>Tidak ada file baru untuk diupload.</div>
        `}
      </div>
      <div class="sut-footer">
        <span class="sut-info">
          ${dupFiles.length > 0 ? `🚫 ${dupFiles.length} duplikat dieliminasi` : '✅ Tidak ada duplikat'}
          ${newFiles.length > 0 ? ` · 📤 ${newFiles.length} siap upload` : ''}
        </span>
        <button class="sut-btn cancel" id="sutCancelBtn">Batal</button>
        ${newFiles.length > 0 ? `<button class="sut-btn primary" id="sutUploadBtn">⬆ Upload ${newFiles.length} File</button>` : ''}
      </div>
    </div>
  `;

  document.body.appendChild(toast);

  // Close uploader panel helper
  function closeUploaderPanel() {
    const closeBtn = document.querySelector('button.uploader__close');
    if (closeBtn) closeBtn.click();
  }

  // Cancel button
  document.getElementById('sutCancelBtn').addEventListener('click', () => {
    toast.remove();
    resetUploadBtn();
    closeUploaderPanel();
  });

  // Click overlay to close
  toast.addEventListener('click', (e) => {
    if (e.target === toast) {
      toast.remove();
      resetUploadBtn();
      closeUploaderPanel();
    }
  });

  // Upload button
  const sutUploadBtn = document.getElementById('sutUploadBtn');
  if (sutUploadBtn) {
    sutUploadBtn.addEventListener('click', async () => {
      sutUploadBtn.disabled = true;
      sutUploadBtn.textContent = '⏳ Uploading...';

      // Create a DataTransfer with only the new files
      const dt = new DataTransfer();
      newFiles.forEach(f => dt.items.add(f));

      // Find the actual Adobe Stock file input
      let adobeInput = document.querySelector('.uploader__drop input[type="file"]')
        || document.querySelector('input[type="file"][accept]')
        || document.querySelector('input[type="file"]');

      if (!adobeInput) {
        // Click the upload tab to reveal the file input area
        const tabBtn = document.querySelector('button[data-t="upload-file-tab"]');
        if (tabBtn) {
          tabBtn.click();
          await delay(1000);
        }
        adobeInput = document.querySelector('.uploader__drop input[type="file"]')
          || document.querySelector('input[type="file"][accept]')
          || document.querySelector('input[type="file"]');
      }

      if (adobeInput) {
        // === OPTIMISTIC TRACKING: Track ALL files to DB IMMEDIATELY ===
        // Uses SHA-256 content hash (primary) + filename hash (backward compat)
        console.log(`[SmartUpload] Optimistic tracking: recording ${newFiles.length} files to DB...`);
        const statusSpan = document.getElementById('autoStatus');
        if (statusSpan) statusSpan.textContent = `📝 Recording ${newFiles.length} files to DB...`;

        let trackSuccess = 0;
        let trackFail = 0;
        for (const f of newFiles) {
          try {
            // Track by content hash (primary, accurate)
            if (f._contentHash) {
              console.log(`[SmartUpload] Tracking content hash: sha_${f._contentHash} for ${f.name}`);
              const ok1 = await trackFileByHash(f._contentHash, f.name);
              console.log(`[SmartUpload] Content hash track result:`, ok1);
              if (ok1) trackSuccess++; else trackFail++;
            } else {
              console.warn(`[SmartUpload] No content hash for ${f.name}, skipping content track`);
              trackFail++;
            }
            // Also track by filename hash (backward compat + auto-process cross-reference)
            await trackFile(f.name, { status: 'uploaded', source: 'smart_upload' });
          } catch (e) {
            console.warn('[SmartUpload] Track error for', f.name, e);
            trackFail++;
          }
        }
        console.log(`[SmartUpload] Optimistic tracking done: ${trackSuccess} content hashes OK, ${trackFail} issues`);

        // Now start the actual Adobe upload
        console.log(`[SmartUpload] Starting upload of ${newFiles.length} files.`);

        // Assign filtered files and dispatch change event to start Adobe's upload
        adobeInput.files = dt.files;
        adobeInput.dispatchEvent(new Event('change', { bubbles: true }));
        adobeInput.dispatchEvent(new Event('input', { bubbles: true }));

        // Also try dropping files on the drop zone
        const dropZone = document.querySelector('.uploader__drop') || document.querySelector('[data-t="upload-file-tab"]')?.closest('.uploader');
        if (dropZone) {
          const dropEvent = new DragEvent('drop', { bubbles: true, dataTransfer: dt });
          dropZone.dispatchEvent(dropEvent);
        }

        toast.remove();
        resetUploadBtn();

        if (statusSpan) {
          if (trackFail > 0) {
            statusSpan.textContent = `⚠️ ${trackSuccess} tracked, ${trackFail} failed. Upload started.`;
          } else {
            statusSpan.textContent = `✅ ${trackSuccess} files tracked & upload started!`;
          }
          setTimeout(() => { if (statusSpan) statusSpan.textContent = ''; }, 8000);
        }

      } else {
        // Fallback: just open the browse dialog
        alert(`File input not found. Will open file picker.`);
        toast.remove();
        resetUploadBtn();
        const browseBtn = document.querySelector('.uploader__drop .hide-on-small-and-down button')
          || document.querySelector('._9-Xiq_spectrum-Link');
        if (browseBtn) browseBtn.click();
      }
    });
  }
}




async function updateLocalButtons() {
  const kwC = document.getElementById("btnContain");
  const titleC = document.getElementById("titleBtnContain");
  if (kwC && titleC) {
    document.querySelectorAll('.localKeyBtn,.localTitleBtn,.localSetupBtn').forEach(b => b.remove());
    createLocalButtons(kwC, titleC);
  }
}

// === GENERATE SELECTED ===
let generating = false;

function stopGenerate() {
  generating = false;
  const genBtn = document.getElementById("localGenBtn");
  const statusSpan = document.getElementById("autoStatus");
  const floatingDiv = document.getElementById("floatingControls");
  toggleGridBlur(false);
  if (genBtn) {
    genBtn.innerHTML = `${ICONS.generate}<span>Generate</span>`;
    genBtn.style.cssText = "background:#00b894!important;color:#fff;";
    genBtn.disabled = false;
  }
  if (statusSpan) statusSpan.textContent = "Stopped";
  updateOverlay(null, null, false);
  if (floatingDiv) floatingDiv.classList.remove("running");
  setTimeout(() => { const s = document.getElementById("autoStatus"); if (s) s.textContent = ""; }, 3000);
}

async function generateSelected(mode = "both", triggerBtn = null) {
  // Toggle stop
  if (generating) return stopGenerate();

  const titleField = document.querySelector('textarea[data-t="asset-title-content-tagger"]');
  if (!titleField) return alert("Select a thumbnail first.");

  generating = true;
  const genBtn = triggerBtn || document.getElementById("localGenBtn");
  const statusSpan = document.getElementById("autoStatus");
  const floatingDiv = document.getElementById("floatingControls");

  if (genBtn) {
    genBtn._origHTML = genBtn.innerHTML;
    genBtn._origStyle = genBtn.style.cssText;
    genBtn.innerHTML = `<span>Stop</span>`;
    genBtn.style.cssText = "background:#d63031!important;color:#fff;";
  }
  if (floatingDiv) floatingDiv.classList.add("running");
  updateOverlay(null, "Starting...", true);
  toggleGridBlur(true);

  try {
    // Capture image
    updateOverlay(null, "Capturing...");
    let imageBase64 = null;
    try {
      const activeImg = document.querySelector(".upload-tile__wrapper.active img");
      if (activeImg && activeImg.src) {
        imageBase64 = await imgToBase64(activeImg.src);
        updateOverlay(activeImg.src, "Capturing...");
      }
    } catch (e) { console.warn("[Gen] img fail:", e); }

    if (!generating) return;

    // Optimized path for BOTH
    if (mode === "both") {
      let attempts = 0;
      let combinedResult;
      let isValid = false;
      const settings = await new Promise(r => chrome.storage.local.get({ localSettings: {} }, d => r(d.localSettings || {})));

      while (attempts < 3 && !isValid && generating) {
        attempts++;
        updateOverlay(null, `AI Processing (Try ${attempts})...`);

        combinedResult = await new Promise((resolve, reject) => {
          chrome.runtime.sendMessage(
            { cmd: "localProcess", target: "background", searchType: "localAutoCombined", imageBase64 },
            (resp) => {
              if (chrome.runtime.lastError) reject(new Error(chrome.runtime.lastError.message));
              else resolve(resp);
            }
          );
        });

        if (combinedResult && combinedResult.success) {
          isValid = validateMetadata(combinedResult);
          if (!isValid) console.warn(`[Gen] Validation failed on attempt ${attempts}, retrying...`);
        } else {
          break;
        }
      }

      if (combinedResult && combinedResult.success && generating) {
        console.log("[Generate] Hasil Gen (Combined):", combinedResult);
        // Fill Title
        const cleanTitle = smartTruncateTitle(combinedResult.title, 200);
        titleField.focus();
        document.execCommand("selectAll", false, "");
        document.execCommand("insertText", false, cleanTitle);

        await delay(300);

        // Fill Keywords
        const kwField = document.querySelector("#content-keywords-ui-textarea");
        if (kwField) {
          kwField.focus();
          document.execCommand("selectAll", false, "");
          document.execCommand("insertText", false, combinedResult.keywords);
          await delay(300);
          const maxKw = settings.maxKeywords || 35;
          await cleanAndEnforceKeywords(maxKw);
        }

        // Apply fixes & auto-fill
        applyTitleFixes(titleField, settings);
        await delay(200);
        applyAutoFillSettings(settings, combinedResult);

        if (statusSpan) statusSpan.textContent = "Selesai ✓";
        updateOverlay(null, "Selesai ✓");
        setTimeout(() => updateOverlay(null, null, false), 1500);

        // Auto Save after single gen
        setTimeout(() => clickSaveButton(), 500);
      } else {
        const err = combinedResult?.error || "Validation failed after 3 attempts";
        if (statusSpan) statusSpan.textContent = "Fail: " + err;
        showNiceAlert("Gagal: " + err);
      }
    } else {
      // Individual modes (Title or Keywords only)
      if (mode === "title") {
        updateOverlay(null, "Title...");
        const settings = await new Promise(r => chrome.storage.local.get({ localSettings: {} }, d => r(d.localSettings || {})));
        const titleResult = await new Promise((resolve, reject) => {
          chrome.runtime.sendMessage(
            { cmd: "localProcess", target: "background", searchType: "localAutoTitle", imageBase64 },
            (resp) => {
              if (chrome.runtime.lastError) return reject(new Error(chrome.runtime.lastError.message));
              resolve(resp);
            }
          );
        });

        if (titleResult && titleResult.success && generating) {
          console.log("[Generate] Hasil Gen (Title):", titleResult);
          const cleanTitle = smartTruncateTitle(titleResult.result, 200);
          titleField.focus();
          document.execCommand("selectAll", false, "");
          document.execCommand("insertText", false, cleanTitle);
          if (statusSpan) statusSpan.textContent = "Title ✓";
          updateOverlay(null, "Title ✓");
        } else {
          const err = titleResult?.error || "?";
          showNiceAlert("Gagal Title: " + err);
        }
      }

      if (mode === "keywords") {
        updateOverlay(null, "Keywords...");
        const settings = await new Promise(r => chrome.storage.local.get({ localSettings: {} }, d => r(d.localSettings || {})));
        const kwResult = await new Promise((resolve, reject) => {
          chrome.runtime.sendMessage(
            { cmd: "localProcess", target: "background", searchType: "localAi", imageBase64, concept: settings.keyConcept },
            (resp) => {
              if (chrome.runtime.lastError) return reject(new Error(chrome.runtime.lastError.message));
              resolve(resp);
            }
          );
        });

        if (kwResult && kwResult.success && generating) {
          console.log("[Generate] Hasil Gen (Keywords):", kwResult);
          const kwField = document.querySelector("#content-keywords-ui-textarea");
          if (kwField) {
            kwField.focus();
            document.execCommand("selectAll", false, "");
            document.execCommand("insertText", false, kwResult.result);
            await delay(300);
            await cleanAndEnforceKeywords();
          }
          if (statusSpan) statusSpan.textContent = "Keywords ✓";
          updateOverlay(null, "Keywords ✓");
        } else {
          const err = kwResult?.error || "?";
          showNiceAlert("Gagal Keywords: " + err);
        }
      }
    }

    // Mark completed (UI only, recording is now handled by Smart Upload)
    const activeTile = document.querySelector(".upload-tile__wrapper.active");
    if (activeTile) {
      const pt = activeTile.parentElement?.parentElement;
      if (pt) pt.classList.add("completedThumbnail");
    }

  } catch (e) {
    console.error("[Gen] Error:", e);
    if (statusSpan) statusSpan.textContent = "Error: " + e.message;
    showNiceAlert("Error sistem: " + e.message);
  } finally {
    generating = false;
    toggleGridBlur(false);
    updateOverlay(null, null, false);
    chrome.runtime.sendMessage({ cmd: "closeOffscreen", target: "background" });

    if (genBtn) {
      genBtn.innerHTML = genBtn._origHTML || `${ICONS.generate}<span>Generate</span>`;
      genBtn.style.cssText = genBtn._origStyle || "background:#00b894!important;color:#fff;";
      genBtn.disabled = false;
    }
    if (floatingDiv) floatingDiv.classList.remove("running");
    setTimeout(() => { const s = document.getElementById("autoStatus"); if (s) s.textContent = ""; }, 4000);
  }
}

// === AUTO PROCESS ===
let autoRunning = false;


function updateAutoPanel(current, total, status) {
  const statusSpan = document.getElementById("autoStatus");
  const floatingDiv = document.getElementById("floatingControls");
  if (statusSpan) {
    statusSpan.textContent = `${status} (${current}/${total})`;
    if (floatingDiv) floatingDiv.classList.add("running");
  }
}

function stopAutoProcess() {
  autoRunning = false;
  toggleGridBlur(false);
  const statusSpan = document.getElementById("autoStatus");
  const floatingDiv = document.getElementById("floatingControls");
  if (statusSpan) statusSpan.textContent = "Stopped";
  updateOverlay(null, null, false);
  if (floatingDiv) floatingDiv.classList.remove("running");
  const btn = document.getElementById("localAutoBtn");
  if (btn) btn.innerHTML = `${ICONS.auto}<span>Auto</span>`;
  setTimeout(() => { if (statusSpan) statusSpan.textContent = ""; }, 3000);
}


function toggleGridBlur(blur) {
  const grid = document.querySelector('div[data-t="assets-content-grid"]');
  if (grid) {
    if (blur) grid.classList.add('blur-grid');
    else grid.classList.remove('blur-grid');
  }
}


async function startAutoProcess() {
  if (autoRunning) return stopAutoProcess();

  const tiles = document.querySelectorAll(".upload-tile__wrapper");
  if (!tiles || tiles.length === 0) return alert("No thumbnails found!");

  autoRunning = true;
  toggleGridBlur(true);
  const btn = document.getElementById("localAutoBtn");
  if (btn) btn.innerHTML = `<span>Stop</span>`;
  const floatingDiv = document.getElementById("floatingControls");
  if (floatingDiv) floatingDiv.classList.add("running");


  try {
    const total = tiles.length;
    let allDone = false;
    let maxPasses = 3;
    let pass = 0;

    while (!allDone && pass < maxPasses && autoRunning) {
      pass++;

      for (let i = 0; i < total; i++) {
        if (!autoRunning) break;

        const tile = tiles[i];
        const parentTile = tile.parentElement?.parentElement;

        // Skip already completed in this session (local CSS class)
        if (parentTile && parentTile.classList.contains("completedThumbnail")) {
          updateOverlay(null, `Skipped (${i + 1}/${total})`);
          continue;
        }

        const container = tile.closest('.container-relative') || parentTile;

        // Fast check: Skip if already filled (detected via green icon)
        const isFilled = container && container.querySelector('.green.apple-text');
        if (isFilled) {
          console.log(`[Auto] Skipping already filled tile (green icon) ${i + 1}`);
          if (parentTile) parentTile.classList.add("completedThumbnail");
          updateOverlay(null, `Already filled (${i + 1}/${total})`);
          continue;
        }

        // Click thumbnail to select it and read filename
        updateOverlay(null, `Selecting... (${i + 1}/${total})`);
        tile.click();
        await delay(1200);

        // === ANTI-DUPLICATE CHECK REMOVED ===
        // We no longer check duplicates during generation because files are already tracked to DB during upload.
        // If we check here, all newly uploaded files would be falsely skipped.

        if (!autoRunning) break;

        // Wait for title field to be ready
        let titleField = null;
        for (let w = 0; w < 10; w++) {
          titleField = document.querySelector('textarea[data-t="asset-title-content-tagger"]');
          if (titleField) break;
          await delay(500);
        }
        if (!titleField || !autoRunning) continue;

        // Get image
        updateOverlay(null, `Capturing... (${i + 1}/${total})`);

        let imageBase64 = null;
        try {
          const activeImg = document.querySelector(".upload-tile__wrapper.active img");
          if (activeImg && activeImg.src) {
            imageBase64 = await imgToBase64(activeImg.src);
            updateOverlay(activeImg.src, `Capturing... (${i + 1}/${total})`);
          }
        } catch (e) { console.warn("[Auto] Image capture failed:", e); }

        if (!autoRunning) break;

        // Fetch settings early so we can use minTitleLength and apply them later
        const settings = await new Promise(r => chrome.storage.local.get({ localSettings: {} }, d => r(d.localSettings || {})));

        let attempts = 0;
        let combinedResult;
        let isValid = false;

        while (attempts < 3 && !isValid && autoRunning) {
          attempts++;
          updateOverlay(null, `AI Processing (Try ${attempts})... (${i + 1}/${total})`);

          combinedResult = await new Promise((resolve, reject) => {
            chrome.runtime.sendMessage(
              { cmd: "localProcess", target: "background", searchType: "localAutoCombined", imageBase64 },
              (resp) => {
                if (chrome.runtime.lastError) reject(new Error(chrome.runtime.lastError.message));
                else resolve(resp);
              }
            );
          });

          if (combinedResult && combinedResult.success) {
            isValid = validateMetadata(combinedResult);
            if (!isValid) console.warn(`[Auto] Validation failed on attempt ${attempts}, retrying...`);
          } else {
            break; // Error from AI, don't retry loop
          }
        }

        if (combinedResult && combinedResult.success && autoRunning) {
          // Fill Title
          const cleanTitle = smartTruncateTitle(combinedResult.title, 200);
          titleField.focus();
          document.execCommand("selectAll", false, "");
          document.execCommand("insertText", false, cleanTitle);
          await localSet("imgName", cleanTitle);
          updateOverlay(null, `Title OK (${i + 1}/${total})`);

          await delay(300);

          // Fill Keywords
          const kwField = document.querySelector("#content-keywords-ui-textarea");
          if (kwField && autoRunning) {
            kwField.focus();
            document.execCommand("selectAll", false, "");
            document.execCommand("insertText", false, combinedResult.keywords);
            await delay(300);
            const maxKw = settings.maxKeywords || 35;
            await cleanAndEnforceKeywords(maxKw);
            updateOverlay(null, `Keywords OK (${i + 1}/${total})`);
          }

          // Apply title prefix/suffix
          applyTitleFixes(titleField, settings);

          // Apply auto-fill settings (passing AI result for category/filetype)
          await delay(300);
          applyAutoFillSettings(settings, combinedResult);
          updateOverlay(null, `Filling... (${i + 1}/${total})`);
        } else if (combinedResult && !combinedResult.success) {
          showNiceAlert("Auto Fill Error: " + (combinedResult?.error || "Unknown"));
          autoRunning = false;
        }

        if (!autoRunning) break;

        // Mark as completed and record to DB
        if (parentTile) parentTile.classList.add("completedThumbnail");
        const originalName = getOriginalFilename();
        if (originalName) {
          await trackFile(originalName, { status: 'auto_filled' });
        }
        updateOverlay(null, `Selesai (${i + 1}/${total})`);

        // Auto Save after each item in Auto mode
        clickSaveButton();

        await delay(1000);
      } // end for

      if (!autoRunning) break;

      // Final Check & Verification
      updateOverlay(null, "Verifying...");
      await delay(2500);

      const submitBtn = document.querySelector('button[data-t="submit-moderation-button"]');
      let readyCount = 0;
      if (submitBtn && submitBtn.textContent.includes("Submit")) {
        const match = submitBtn.textContent.match(/\d+/);
        if (match) readyCount = parseInt(match[0], 10);
      }

      if (readyCount >= total || !submitBtn) {
        allDone = true;
      } else {
        updateOverlay(null, `Checking status...`);
        let foundEmpty = false;
        for (let i = 0; i < total; i++) {
          if (!autoRunning) break;
          const tile = tiles[i];
          const container = tile.closest('.container-relative') || tile.parentElement?.parentElement;
          // If it's still red, we need another pass
          if (container && container.querySelector('.red.pastel-text')) {
            const pt = tile.parentElement?.parentElement;
            if (pt && pt.classList.contains("completedThumbnail")) {
              pt.classList.remove("completedThumbnail");
              foundEmpty = true;
            }
          }
        }
        if (!foundEmpty) allDone = true;
      }
    } // end while
  } finally {
    // Finished or Stopped
    autoRunning = false;
    toggleGridBlur(false);
    updateOverlay(null, null, false);
    chrome.runtime.sendMessage({ cmd: "closeOffscreen", target: "background" });

    const floatingDiv = document.getElementById("floatingControls");
    if (floatingDiv) floatingDiv.classList.remove("running");

    const btn = document.getElementById("localAutoBtn");
    if (btn) {
      btn.innerHTML = `${ICONS.auto}<span>Auto</span>`;
      btn.style.cssText = "background:#e17055!important;color:#fff;";
      btn.disabled = false;
    }

    // Final Auto-save
    await delay(500);
    const saved = clickSaveButton();

    const finalReadyCount = document.querySelectorAll('.green.apple-text').length;
    const finalTotal = (typeof tiles !== 'undefined' && tiles.length) ? tiles.length : 0;

    if (saved) {
      updateAutoPanel(finalReadyCount, finalTotal, "✅ Tersimpan!");
      console.log('[Auto] Save work clicked. Ready:', finalReadyCount);
    } else {
      updateAutoPanel(finalReadyCount, finalTotal, "Selesai");
    }

    setTimeout(() => {
      const statusSpan = document.getElementById("autoStatus");
      if (statusSpan) statusSpan.textContent = "";
    }, 5000);
  }
}

function clickSaveButton() {
  // Adobe Stock Save button selectors
  const selectors = [
    'button[data-t="save-work"]',
    'button[data-t="save"]',
    'button[data-t="asset-sidebar-save-button"]',
    '.panel button.save-button',
    '.asset-sidebar-header-save-button'
  ];

  for (const sel of selectors) {
    const btn = document.querySelector(sel);
    if (btn && !btn.disabled && btn.offsetParent !== null) { // Must be visible
      btn.click();
      console.log("[Content] Clicked Save button via:", sel);
      return true;
    }
  }

  // Fallback: search by text for buttons that might not have data-t
  const buttons = Array.from(document.querySelectorAll('button:not([disabled])'));
  const saveBtn = buttons.find(b => {
    const text = b.innerText.toLowerCase();
    return (text.includes('save') || text.includes('simpan')) && b.offsetParent !== null;
  });

  if (saveBtn) {
    saveBtn.click();
    console.log("[Content] Clicked Save button via text");
    return true;
  }

  return false;
}




// === AUTO-FILL HELPERS ===

// Banned word lists for keyword quality enforcement
const BANNED_KEYWORDS_EXACT = new Set([
  // Meta words
  'stock photo', 'image', 'picture', 'wallpaper', 'illustration', 'vector',
  'clipart', 'icon', 'logo', 'graphic', 'background image', 'screenshot',
  'stock', 'photo', 'foto', 'gambar',
  // Standalone conjunctions/prepositions EN
  'and', 'or', 'but', 'the', 'a', 'an', 'in', 'on', 'at', 'to', 'for',
  'of', 'with', 'by', 'from', 'into', 'through', 'is', 'are', 'was', 'were',
  // Standalone conjunctions/prepositions ID
  'dan', 'atau', 'di', 'ke', 'dari', 'untuk', 'yang', 'dengan', 'pada',
  // Filler adjectives
  'beautiful', 'amazing', 'stunning', 'wonderful', 'nice', 'good', 'great',
  'best', 'perfect', 'awesome', 'lovely', 'pretty', 'gorgeous', 'magnificent',
  'fantastic', 'incredible', 'extraordinary', 'remarkable',
]);

const BANNED_KEYWORDS_PARTIAL = [
  // Camera/brand terms
  'canon', 'nikon', 'sony', 'dslr', 'mirrorless', 'iphone', 'samsung',
  'apple', 'google', 'microsoft', 'f/1.', 'f/2.', '85mm', '50mm', '35mm',
  'bokeh effect', 'hdr',
];

// Synonym groups — keep max 2 from each group
const SYNONYM_GROUPS = [
  ['dog', 'canine', 'pup', 'puppy', 'pooch', 'hound'],
  ['cat', 'feline', 'kitten', 'kitty'],
  ['happy', 'joyful', 'cheerful', 'glad', 'delighted', 'elated', 'blissful'],
  ['sad', 'unhappy', 'sorrowful', 'melancholy', 'gloomy'],
  ['house', 'home', 'residence', 'dwelling', 'abode'],
  ['kid', 'child', 'youngster', 'toddler', 'infant'],
  ['old', 'elderly', 'aged', 'senior', 'mature'],
  ['big', 'large', 'huge', 'enormous', 'massive', 'giant', 'gigantic'],
  ['small', 'tiny', 'little', 'miniature', 'mini', 'petite'],
  ['fast', 'quick', 'rapid', 'swift', 'speedy'],
  ['forest', 'woods', 'woodland', 'jungle', 'rainforest'],
  ['ocean', 'sea', 'marine', 'maritime'],
  ['road', 'street', 'highway', 'path', 'pathway'],
  ['shop', 'store', 'boutique', 'outlet'],
  ['rock', 'stone', 'boulder', 'pebble'],
  ['lake', 'pond', 'reservoir'],
  ['walk', 'stroll', 'amble', 'saunter'],
];

function isKeywordBanned(tag) {
  if (BANNED_KEYWORDS_EXACT.has(tag)) return true;
  for (const banned of BANNED_KEYWORDS_PARTIAL) {
    if (tag === banned || tag.includes(banned)) return true;
  }
  return false;
}

function dedupSynonyms(tags, maxPerGroup = 2) {
  const result = [];
  const groupCounts = new Map();
  for (const tag of tags) {
    let foundGroup = -1;
    for (let gi = 0; gi < SYNONYM_GROUPS.length; gi++) {
      if (SYNONYM_GROUPS[gi].some(syn => tag === syn || tag.includes(syn))) {
        foundGroup = gi;
        break;
      }
    }
    if (foundGroup === -1) {
      result.push(tag);
    } else {
      const currentCount = groupCounts.get(foundGroup) || 0;
      if (currentCount < maxPerGroup) {
        result.push(tag);
        groupCounts.set(foundGroup, currentCount + 1);
      } else {
        console.log(`[Keywords] 🚫 Synonym dedup: removed "${tag}"`);
      }
    }
  }
  return result;
}

// Unified keyword cleanup: clean raw text, dedup, filter banned, enforce max count (hard cap 50)
async function cleanAndEnforceKeywords(maxCount) {
  const kwField = document.querySelector("#content-keywords-ui-textarea");
  if (!kwField) return;

  // If no maxCount passed, read from settings
  if (!maxCount) {
    const settings = await new Promise(r => chrome.storage.local.get({ localSettings: {} }, d => r(d.localSettings || {})));
    maxCount = settings.maxKeywords || 35;
  }
  // Hard cap at 50 (Adobe Stock limit)
  maxCount = Math.min(Number(maxCount) || 50, 50);

  let raw = kwField.value;
  // Normalize separators: semicolons → commas
  raw = raw.replace(/;/g, ',');
  // Clean: remove quotes, dots, newlines, numbering like "1. " or "1) "
  raw = raw.replace(/"/g, '').replace(/\./g, '').replace(/\n/g, ',').replace(/^\d+[\)\.]?\s*/gm, '');

  // Split, trim, lowercase, remove empties
  let tags = raw.split(',').map(t => t.trim().toLowerCase()).filter(t => t.length > 0);
  const beforeCount = tags.length;

  // 1. Deduplicate
  tags = [...new Set(tags)];

  // 2. Remove banned keywords
  const bannedRemoved = [];
  tags = tags.filter(t => {
    if (isKeywordBanned(t)) { bannedRemoved.push(t); return false; }
    return true;
  });
  if (bannedRemoved.length > 0) {
    console.log(`[Keywords] 🚫 Banned removed (${bannedRemoved.length}):`, bannedRemoved.join(', '));
  }

  // 3. Remove too short (< 2 chars) or too long (> 40 chars)
  tags = tags.filter(t => {
    if (t.length < 2) { console.log(`[Keywords] 🚫 Too short: "${t}"`); return false; }
    if (t.length > 40) { console.log(`[Keywords] 🚫 Too long: "${t}"`); return false; }
    return true;
  });

  // 4. Synonym dedup (keep max 2 per group)
  tags = dedupSynonyms(tags, 2);

  // 5. Enforce count
  if (tags.length > maxCount) {
    console.log(`[Keywords] Trimmed from ${tags.length} → ${maxCount}`);
    tags = tags.slice(0, maxCount);
  }

  const result = tags.join(', ');
  kwField.focus();
  document.execCommand('selectAll', false, '');
  document.execCommand('insertText', false, result);
  console.log(`[Keywords] ✅ Pipeline: ${beforeCount} raw → ${tags.length} final (max: ${maxCount})`);
}

// Legacy alias for backward compatibility
function enforceKeywordCount(maxCount) {
  cleanAndEnforceKeywords(maxCount);
}

function smartTruncateTitle(raw, maxLen = 200) {
  // 1. Clean special characters
  let title = raw.replace(/"/g, '').replace(/\./g, '').replace(/:/g, '').replace(/\n/g, ' ').trim();

  // 2. If within limit, return as-is
  if (title.length <= maxLen) return title;

  // 3. Cut at word boundary
  title = title.substring(0, maxLen);
  const lastSpace = title.lastIndexOf(' ');
  if (lastSpace > maxLen * 0.7) {
    title = title.substring(0, lastSpace);
  }

  // 4. Remove trailing incomplete words (prepositions, articles, conjunctions)
  const trailingJunk = /\s+(for|the|a|an|in|on|at|to|of|by|with|and|or|but|is|are|was|were|that|this|from|into|as|its|their|your|our|my|his|her|which|who|whom|whose|when|where|how|if|then|than|so|yet|nor|not|very|just|also|about|over|under|between|through|during|after|before|while|since|until|up|down|out|off|no)$/i;
  while (trailingJunk.test(title)) {
    title = title.replace(trailingJunk, '');
  }

  return title.trim();
}

function applyTitleFixes(titleField, settings) {
  if (!titleField) return;
  let title = titleField.value;

  // Enforce max title length with smart truncation
  const maxLen = 200;
  if (title.length > maxLen) {
    title = smartTruncateTitle(title, maxLen);
  }

  titleField.focus();
  document.execCommand('selectAll', false, '');
  document.execCommand('insertText', false, title);
}

function applyAutoFillSettings(settings, aiResult = null) {
  try {
    // Auto "No Release"
    if (settings.defaultNo !== false) {
      const noRelBtn = document.querySelector('input[data-t="has-release-no"]');
      if (noRelBtn) noRelBtn.click();
    }

    // Auto AI Checkbox & Fictional People/Property
    if (settings.defaultAi !== false) {
      const aiCheckbox = document.querySelector("#content-tagger-generative-ai-checkbox");
      if (aiCheckbox && !aiCheckbox.checked) {
        aiCheckbox.click();
      }
      
      // Beri sedikit jeda agar DOM React Adobe Stock sempat merender sub-checkbox
      setTimeout(() => {
        const fictionalCheckbox = document.querySelector("#content-tagger-generative-ai-property-release-checkbox");
        if (fictionalCheckbox && !fictionalCheckbox.checked) {
          fictionalCheckbox.click();
        }
      }, 100);
    }

    // Auto File Type
    if (settings.autoFileType !== false) {
      // Cari elemen <select> yang memiliki opsi "Photos" dan "Illustrations"
      const selects = Array.from(document.querySelectorAll('select'));
      const fileTypeSelect = selects.find(s => {
        const options = Array.from(s.options).map(o => o.text.toLowerCase());
        return options.includes('photos') && options.includes('illustrations');
      });

      if (fileTypeSelect && !fileTypeSelect.disabled) {
        if (aiResult && aiResult.fileType) {
          const ft = aiResult.fileType.toLowerCase();
          let targetText = 'photos';
          if (ft.includes('illustration')) targetText = 'illustrations';
          
          // Cari opsi yang sesuai dan set valuenya
          const targetOption = Array.from(fileTypeSelect.options).find(o => o.text.toLowerCase().includes(targetText));
          if (targetOption) {
            fileTypeSelect.value = targetOption.value;
            fileTypeSelect.dispatchEvent(new Event("change", { bubbles: true }));
          }
        }
      }
    }

    // Auto Category
    if (settings.autoCategory !== false) {
      const catSelect = document.querySelector('select[name="category"]');
      if (catSelect) {
        const catMap = {
          "animals": "10001", "buildings and architecture": "10092", "business": "10162",
          "drinks": "10209", "the environment": "10235", "states of mind": "10255",
          "food": "10283", "graphic resources": "10432", "hobbies and leisure": "10486",
          "industry": "10556", "landscapes": "10584", "lifestyle": "10631",
          "people": "10683", "plants and flowers": "10733", "culture and religion": "10778",
          "science": "10797", "social issues": "10834", "sports": "10868",
          "technology": "10927", "transport": "10958", "travel": "10988"
        };

        let detectedCatId = null;

        // 1. Check AI result first
        if (aiResult && aiResult.category) {
          const aiCat = aiResult.category.toLowerCase().trim();
          for (const [name, id] of Object.entries(catMap)) {
            if (aiCat.includes(name)) {
              detectedCatId = id;
              break;
            }
          }
        }

        // 2. Fallback to keyword detection
        if (!detectedCatId) {
          const kwField = document.querySelector("#content-keywords-ui-textarea");
          if (kwField) {
            const kw = kwField.value.toLowerCase();
            const kwDetect = {
              "10001": ["animal", "dog", "cat", "bird", "fish", "horse", "pet", "wildlife", "puppy", "kitten", "zoo"],
              "10092": ["building", "architecture", "house", "city", "skyline", "tower", "bridge", "office", "skyscraper", "facade"],
              "10162": ["business", "office", "meeting", "corporate", "team", "work", "professional", "suit", "handshake", "conference"],
              "10283": ["food", "meal", "dish", "cooking", "recipe", "kitchen", "chef", "restaurant", "delicious", "ingredient"],
              "10209": ["drink", "coffee", "tea", "wine", "beer", "juice", "cocktail", "beverage", "water", "cup"],
              "10584": ["landscape", "mountain", "forest", "river", "lake", "valley", "field", "countryside", "horizon", "scenery", "nature", "sunset"],
              "10631": ["lifestyle", "home", "family", "relax", "morning", "routine", "wellness", "self care", "cozy", "comfort"],
              "10683": ["people", "person", "man", "woman", "child", "portrait", "face", "smile", "couple", "group", "model"],
              "10733": ["flower", "plant", "garden", "bloom", "petal", "leaf", "tree", "botanical", "floral", "rose", "tulip"],
              "10868": ["sport", "fitness", "gym", "training", "exercise", "athlete", "running", "football", "basketball", "soccer"],
              "10927": ["technology", "computer", "phone", "digital", "software", "data", "screen", "laptop", "smartphone", "code", "ai"],
              "10958": ["car", "vehicle", "transport", "road", "drive", "truck", "bus", "train", "motorcycle", "airplane"],
              "10988": ["travel", "tourism", "vacation", "adventure", "explore", "trip", "destination", "beach", "hotel", "tourist"],
              "10235": ["environment", "nature", "ecology", "green", "climate", "earth", "pollution", "recycle", "sustainability"]
            };
            for (const [catId, keywords] of Object.entries(kwDetect)) {
              if (keywords.some(k => kw.includes(k))) {
                detectedCatId = catId;
                break;
              }
            }
          }
        }

        if (detectedCatId) {
          catSelect.value = detectedCatId;
          catSelect.dispatchEvent(new Event("change", { bubbles: true }));
        }
      }
    }
  } catch (e) { console.warn("[Auto] Auto-fill error:", e); }
}

// === VALIDATION HELPERS ===
function validateMetadata(data) {
  if (!data) return false;

  // 1. Check Title
  if (!data.title || data.title.length < 10) {
    console.warn("[Validation] Title too short or empty");
    return false;
  }

  // 2. Check Keywords
  const kws = (data.keywords || "").split(",").map(k => k.trim()).filter(Boolean);
  if (kws.length === 0) {
    console.warn("[Validation] Keywords empty");
    return false;
  }
  if (kws.length > 50) {
    console.warn("[Validation] Too many keywords:", kws.length);
    return false;
  }

  // 3. Check Category & File Type (if present)
  if (!data.category) {
    console.warn("[Validation] Category missing");
    return false;
  }
  if (!data.fileType) {
    console.warn("[Validation] File Type missing");
    return false;
  }

  const validCategories = [
    "Animals", "Buildings and Architecture", "Business", "Drinks", "The Environment",
    "States of Mind", "Food", "Graphic Resources", "Hobbies and Leisure", "Industry",
    "Landscape", "Lifestyle", "People", "Plants and Flowers", "Culture and Religion",
    "Science", "Social Issues", "Sports", "Technology", "Transport", "Travel"
  ];

  const isCatValid = validCategories.some(vc => data.category.toLowerCase().includes(vc.toLowerCase()));
  if (!isCatValid) {
    console.warn("[Validation] Invalid category:", data.category);
    return false;
  }

  const isFTValid = ["Photo", "Illustration"].some(vft => data.fileType.toLowerCase().includes(vft.toLowerCase()));
  if (!isFTValid) {
    console.warn("[Validation] Invalid file type:", data.fileType);
    return false;
  }

  console.log("[Validation] Metadata passed!");
  return true;
}



async function localBtnClick(searchType) {
  let titleField = document.querySelector('textarea[data-t="asset-title-content-tagger"]');
  if (!titleField) return;

  let titleSuggest = "", keySuggest = "";
  if (searchType === "localTitle") {
    try { document.body.querySelectorAll(".tabs.menu__tabs button").forEach(e => { titleSuggest += `${e.innerText}, `; }); } catch { }
    try { document.body.querySelectorAll('div[data-t="content-tagger-keywords-tag-group"] .keyword-tag-content > span').forEach(e => { keySuggest += `${e.innerText}, `; }); } catch { }
  } else {
    if (0 === titleField.value.replace(" ", "").length) return alert(i18n("pleaseProvideTitle"));
  }

  await localSet("imgName", titleField.value);
  await localSet("titleSuggest", titleSuggest);
  await localSet("keySuggest", keySuggest);
  await localSet("searchType", searchType);

  // Capture image as base64 directly from the page (avoids CORS in service worker)
  let imageBase64 = null;
  if (searchType !== "localTitle") {
    try {
      const activeImg = document.querySelector(".upload-tile__wrapper.active img");
      if (activeImg && activeImg.src) {
        imageBase64 = await imgToBase64(activeImg.src);
      }
    } catch (e) { console.warn("[Local] Image capture failed:", e); }
  }

  // Show loading state
  const btn = searchType === "localTitle" ? document.getElementById("localTitleBtn") : document.getElementById("localAiBtn");
  const origHTML = btn ? btn.innerHTML : "";
  if (btn) { btn.innerHTML = "<span>⏳ Processing...</span>"; btn.disabled = true; }

  try {
    const response = await new Promise((resolve, reject) => {
      chrome.runtime.sendMessage(
        { cmd: "localProcess", target: "background", searchType, imageBase64 },
        (resp) => {
          if (chrome.runtime.lastError) reject(new Error(chrome.runtime.lastError.message));
          else resolve(resp);
        }
      );
    });

    if (response && response.success) {
      handleLocalResult(response.result, searchType);
    } else {
      showNiceAlert("Local AI Error: " + (response?.error || "Unknown error"));
    }
  } catch (e) {
    showNiceAlert("Local AI Error: " + e.message);
  } finally {
    if (btn) { btn.innerHTML = origHTML; btn.disabled = false; }
  }
}

// Convert image to base64 using canvas (works even with cross-origin images already rendered on page)
function imgToBase64(imgSrc) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = "anonymous";
    img.onload = () => {
      try {
        const canvas = document.createElement("canvas");
        canvas.width = img.naturalWidth;
        canvas.height = img.naturalHeight;
        const ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0);
        resolve(canvas.toDataURL("image/jpeg", 0.8));
      } catch (e) {
        // If canvas is tainted, try fetching the image directly
        fetch(imgSrc)
          .then(r => r.blob())
          .then(blob => {
            const reader = new FileReader();
            reader.onloadend = () => resolve(reader.result);
            reader.onerror = () => reject(e);
            reader.readAsDataURL(blob);
          })
          .catch(() => reject(e));
      }
    };
    img.onerror = () => reject(new Error("Image load failed"));
    img.src = imgSrc;
  });
}

function handleLocalResult(result, searchType) {
  if (!result) return;
  if (searchType === "localTitle") {
    const titleField = document.querySelector('textarea[data-t="asset-title-content-tagger"]');
    if (titleField) {
      titleField.focus();
      document.execCommand("selectAll", false, "");
      document.execCommand("insertText", false, result.replace(/"/g, "").replace(/\./g, ""));
    }
  } else {
    const kwField = document.querySelector("#content-keywords-ui-textarea");
    if (kwField) {
      kwField.focus();
      document.execCommand("selectAll", false, "");
      document.execCommand("insertText", false, result);
      // Clean, dedup, enforce count from settings (async, slight delay for DOM update)
      setTimeout(() => cleanAndEnforceKeywords(), 200);
    }
  }
}

// --- Nice Alert ---
function showNiceAlert(message) {
  const div = document.createElement('div');
  div.style.cssText = "position:fixed;top:20px;left:50%;transform:translateX(-50%) translateY(-100px);z-index:9999999;padding:12px 24px;border-radius:12px;box-shadow:0 10px 30px rgba(0,0,0,0.5);color:#fff;font-family:sans-serif;font-size:14px;font-weight:600;background:rgba(231, 76, 60, 0.95);backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,0.1);transition:all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);";
  div.innerHTML = `⚠️ &nbsp;${message}`;
  document.body.appendChild(div);

  // Animate in
  setTimeout(() => { div.style.transform = 'translateX(-50%) translateY(0)'; }, 50);

  // Animate out
  setTimeout(() => {
    div.style.transform = 'translateX(-50%) translateY(-100px)';
    div.style.opacity = '0';
    setTimeout(() => div.remove(), 400);
  }, 6000);
}

function handleSelectAllBtnClick() { return setTimeout(createBtn, 200) } async function checkMutations() { if (observer) try { observer.disconnect() } catch (e) { } const e = document.querySelector("div[data-t=assets-content-grid]"); observer = new MutationObserver((e => { for (const t of e) "childList" === t.type && debounce(1e3, addClickListenerToNode) })), observer.observe(e, { childList: !0 }) } function addClickListenerToNode() { document.querySelectorAll(".upload-tile__wrapper").forEach((e => { e.addEventListener("click", handleNodeClick) })) } async function delay(e) { return new Promise((t => { setTimeout((() => t(null)), e) })) } function isOneActive() { return Array.from(document.querySelectorAll(".upload-tile__wrapper")).filter((e => e.parentElement.parentElement.classList.contains("uploadingThumbnail"))).length >= 1 } async function handleNodeClick(e) { const t = e.currentTarget; if (!t) return; try { if (!document.querySelector("#keyUniqueBtn")) throw new Error("") } catch { return retry = retryInit, domReady(start) } if (await delay(100), checkMultipleSelect()) return void showBtn(!1); if (showBtn(!0), isLoading) return; const n = t.querySelector("img").src, a = t.parentElement.parentElement, o = document.querySelector('textarea[data-t="asset-title-content-tagger"]'), i = document.querySelector("#content-keywords-ui-textarea"); a == document.querySelectorAll(".upload-tile")[0] && "" != i.value.trim() || "" != o.value.trim() && "" != i.value.trim() || a.classList.contains("loadingThumbnail") || a.classList.contains("uploadingThumbnail") || (a.classList.contains("completedThumbnail") ? chrome.runtime.sendMessage({ cmd: "fillResult", target: "background", data: n }) : (a.classList.contains("failThumbnail") && (a.classList.remove("failThumbnail"), chrome.runtime.sendMessage({ cmd: "fillResult", target: "background", data: n })), await localGet("advanceMode") && (isOneActive() || (isLoading = !0, urlRef = n, parentTargetRef = a, a.classList.add("loadingThumbnail"), debounce(1e3, (() => uploadImg())))))) } async function uploadImg() { isLoading = !1, parentTargetRef.classList.remove("loadingThumbnail"), urlRef && await chrome.runtime.sendMessage({ cmd: "uploadImg", target: "background", data: urlRef }) } function loadPopup(e) { const t = document.createElement("iframe"); t.src = chrome.runtime.getURL("popup.html"), t.id = "extensionIframe", e.append(t) } async function btnClick(e) { let t = document.querySelector('textarea[data-t="asset-title-content-tagger"]'), n = "", a = ""; switch (e) { case "title": case "titleBard": t.focus(), document.execCommand("selectAll", !1, ""), document.execCommand("insertHTML", !1, "&nbsp;"), document.execCommand("selectAll", !1, ""), document.execCommand("delete", !1, ""); try { document.body.querySelectorAll(".tabs.menu__tabs button").forEach((e => { n += `${e.innerText}, ` })) } catch { return null } try { document.body.querySelectorAll('div[data-t="content-tagger-keywords-tag-group"] .keyword-tag-content > span').forEach((e => { a += `${e.innerText}, ` })) } catch { return null } break; case "ai": case "aiBard": case "original": if (0 === t.value.replace(" ", "").length) return alert(i18n("pleaseProvideTitle")) }await localSet("imgName", t.value), await localSet("titleSuggest", n), await localSet("keySuggest", a), await localSet("searchType", e), chrome.runtime.sendMessage({ cmd: "open", keyword: truncate(t.value), type: e }) } setTimeout((() => domReady(start)), 1e3); const zoomStyle = document.createElement("style"); zoomStyle.id = "zoomStyle", document.head.append(zoomStyle); let zoomInCss = '\ndiv[data-t="content-tagger-keywords-area"] {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100vw;\n  height: 100vh;\n  background: #f5f5f5;\n  z-index: 99999;\n  padding:50px !important;\n  overflow-y: scroll !important;\n}\n\n.margin-left-medium {\n  margin-left: -10px;\n}\n\n.margin-bottom-xsmall {\n    margin:0 30px !important;\n}\n\n#js-keywords-input-items-wrapper {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: normal;\n  margin: 30px 0;\n  max-height:80vh;\n  flex-direction:column;\n  overflow: auto !important;\n}\n\n.container-table {\n  display: flex !important;\n  flex-direction: column;\n  flex-direction: row;\n}\n\ndiv[data-t="content-keywords-input-item"],\ninput[data-t="content-keyword"] {\n  width: 180px;\n}\n\n.container-table.padding-top-xsmall.padding-bottom-xsmall {\n  width: 200px !important;\n  position: relative;\n  justify-content: flex-end;\n}\n\n.container-table-cell.keywords-input__label {\n  position: relative;\n  width: 30px;\n  display: flex;\n  align-content: center;\n  flex-wrap: wrap;\n}\n\n.container-table-cell [type="text"] {\n  font-size: 20px;\n  height: 40px;\n  padding: 0 20px;\n  width: 200px;\n}\n\n.keywords-input__label__number {\n  background: white !important;\n  width: 30px;\n}\n\n/*button[data-t="content-keywords-input-actions-item-icon-grab"],\nbutton[data-t="content-keywords-input-actions-item-icon-up"] {\n   display: none;\n}*/\n\nbutton[data-t="content-keywords-input-actions-item-icon-up"] {\n   display: none;\n}\n\ndiv[data-t="content-keywords-input-actions-item-actions"] {\n  position: absolute;\n  right: 5px;\n  top: 15px;\n}\n'; for (let e = 1; e <= 60; e++)zoomInCss += `#js-keywords-input-items-wrapper .keywords-input__item:nth-child(${e}) { z-index: ${61 - e}; }\n`; function expandKeyword() { const e = qSelect('button[data-t="keywords-remaining"]') ?? null; e && e.click() } function addOtherEvent() { const e = qSelect('input[data-t="content-keywords-ui-switch"]'); e.addEventListener("change", (() => { const t = qSelect("#btnContain"); e.checked ? (t.style.display = "none", zoomStyle.textContent = zoomInCss, setTimeout(expandKeyword, 100)) : (t.style.display = "grid", zoomStyle.innerText = "") })), document.querySelector('button[data-t="asset-sidebar-delete-asset-button"]').addEventListener("click", (() => setTimeout((() => { document.querySelector('button[data-t="delete-all-assets-confirmation-CTA"]').addEventListener("click", (() => setTimeout(start, 500))) }), 200))), document.querySelectorAll(".container-inline-block").forEach((e => { e.addEventListener("click", (() => { setTimeout(createBtn, 200) })) })) } function truncate(e, t = 50) { if (e.length <= 50) return e; let n = e.split(" "); return n.pop(), n = n.join(" "), n } function doUnique() { const e = document.querySelector("#content-keywords-ui-textarea"); e.focus(); let t = !1; e.value.indexOf(";") >= 0 && (e.value = e.value.replaceAll(";", ","), t = !0); const n = e.value.split(","); if (0 === n.length) return null; const a = new Set; for (value of n) a.add(value.trim()); const o = Array.from(a); if (JSON.stringify(n) === JSON.stringify(o) && !1 === t) return null; const i = o.join(", "); e.value = "", document.execCommand("insertText", !1, i) }