/*! Kori Collectibles v2.3.0 - FULL app.js with variant pill + PB/MB overlays (PRE/BLK/WHT) */
(function(){
  'use strict';
  const REST = window.KORI_REST || '/wp-json/kori/v2/';
  const AJAX = window.KORI_AJAX || (window.ajaxurl || '/wp-admin/admin-ajax.php');

  // ------- helpers -------
  const $ = (s, c=document)=>c.querySelector(s);
  const $$ = (s, c=document)=>Array.from(c.querySelectorAll(s));
  const rest = p => fetch(REST+p,{credentials:'same-origin'}).then(r=>{if(!r.ok) throw 0; return r.json()});
  const ajax = (action, data={}) => {
    const body = new URLSearchParams({action, ...data}).toString();
    return fetch(AJAX,{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/x-www-form-urlencoded'},body}).then(r=>r.json());
  };
  const svgPH = 'data:image/svg+xml;base64,'+btoa(`<svg xmlns="http://www.w3.org/2000/svg" width="800" height="400"><rect width="100%" height="100%" fill="#0e141a"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="16" fill="#81909f">No image</text></svg>`);
  function imgOf(row){
    try{
      if (row.local_image_large) return row.local_image_large;
      if (row.local_image_small) return row.local_image_small;
      if (row.images_small) return row.images_small;
      if (row.images_large) return row.images_large;
      if (row.image_small) return row.image_small;
      if (row.image) return row.image;
      if (row.images){
        if (typeof row.images === 'string') {
          try { const o = JSON.parse(row.images); return o.large || o.small || ''; } catch(e) {}
        } else { return row.images.large || row.images.small || ''; }
      }
    }catch(_){}
    return '';
  }
  function logoOf(s){ return (s.local_logo||s.logo||'') || ''; }

	// ------- Variant overlay helpers -------
	const SHOW_NORMAL_BADGE = false; // set true to show "Normal" pills too
	const OVERLAY_SETS = new Set(['PRE','BLK','WHT','ZSV10PT5','RSV10PT5','SV8PT5']); // Prismatic Evolutions, Black Bolt, White Flare, and modern sets with PB/MB variants

	function stripVariantFromName(name){
	  const n = String(name||''); const i = n.indexOf(' – ');
	  return i>0 ? n.slice(0,i) : n;
	}
	function getVariantLabel(row){
	  if (row && row.variant_label) return row.variant_label;
	  const m = / – (.+)$/.exec(row && row.name || '');
	  return m ? m[1] : '';
	}
	function getVariantKey(row){
	  if (row && row.variant_key) return String(row.variant_key).toUpperCase();
	  const lbl = (getVariantLabel(row)||'').toLowerCase();
	  
	  if (/master\s*ball/.test(lbl)) return 'MB';
	  if (/poke\s*ball|pok[eé]ball|pokeball/.test(lbl)) return 'PB';
	  if (/reverse/.test(lbl)) return 'RH';
	  if (/holo/.test(lbl)) return 'H';
	  
	  return '';
	}
	function getVariantIndex(c, vKey) {
	  // Get the variants array for this card
	  const variants = c.variants || [];
	  if (!Array.isArray(variants)) return -1;
	  
	  // Find the index of the variant with the matching key
	  for (let i = 0; i < variants.length; i++) {
	    if (variants[i].key === vKey) {
	      return i;
	    }
	  }
	  
	  // If not found, try to match by label (fallback)
	  const vLabel = getVariantLabel(c);
	  for (let i = 0; i < variants.length; i++) {
	    if (variants[i].label === vLabel) {
	      return i;
	    }
	  }
	  
	  return -1;
	}
	function variantPillHTML(label, key){
	  const lbl = (label||'').trim();
	  if (!lbl) return '';
	  if (!SHOW_NORMAL_BADGE && /^normal$/i.test(lbl)) return '';
	  const cls = (key||'').toUpperCase() ? ` var-${(key||'').toUpperCase()}` : '';
	  return `<div class="variant-pill${cls}">${lbl}</div>`;
	}

	// Determine base URL of this script to resolve overlays/ folder
	function scriptBaseUrl(){
	  try{
		 const scripts = Array.from(document.getElementsByTagName('script'));
		 const hit = scripts.find(sc=>/app\.js(\?|$)/.test(sc.src)) || scripts[scripts.length-1];
		 if (!hit || !hit.src) return '';
		 return hit.src.replace(/\/[^\/?#]*(\?[^#]*)?$/, '/');
	  }catch(_){}
	  return '';
	}
	const BASE_URL = scriptBaseUrl();

	// File maps: generic (BLK/WHT) vs prismatic (PRE)
	const OVERLAY_FILES = {
	  generic: {
		 PB: 'overlays/pokeball-overlay.png',
		 MB: 'overlays/masterball-overlay.png'
	  },
	  prismatic: {
		 PB: 'overlays/prismatic-pokeball-overlay.png',
		 MB: 'overlays/prismatic-masterball-overlay.png'
	  }
	};

	// Build the overlay <img> (single img with fallback)
	function variantImageOverlayHTML(setCode, key){
	  const code = String(setCode||'').toUpperCase();
	  const k    = String(key||'').toUpperCase();
	  
	  if (!OVERLAY_SETS.has(code)) return '';            // only supported sets
	  
	  // Determine overlay family - PRE and SV8PT5 use prismatic, everything else uses generic
	  const prismaticSets = ['PRE', 'SV8PT5'];
	  const family = prismaticSets.includes(code) ? 'prismatic' : 'generic';
	  const rel    = (OVERLAY_FILES[family]||{})[k];     // PB/MB only
	  
	  if (!rel) return '';

	  const primary = BASE_URL ? (BASE_URL + rel) : rel;
	  const fallback = BASE_URL ? (BASE_URL + '../' + rel) : ('../' + rel);

	  // one img; if primary 404s, swap to fallback; if that fails, hide
	  return `<img class="variant-overlay" src="${primary}"
					  onerror="if(this.dataset.f){ this.style.display='none'; } else { this.dataset.f=1; this.src='${fallback}'; }">`;
	}


  // ------- Mount root & Shadow DOM -------
  function findHost(){
    return document.querySelector('#app') ||
           document.querySelector('#kori-v2-app') ||
           document.querySelector('#primary .entry-content') ||
           document.querySelector('#primary') ||
           document.querySelector('.entry-content') ||
           document.body;
  }
  function clearHost(host){
    try{
      if (host && host !== document.body){
        host.innerHTML = '';
      }
    }catch(_){}
  }
  function mountRoot(){
    const host = findHost();
    if (!host) return null;
    clearHost(host);

    const root = document.createElement('div');
    root.id = 'kori-v2-root';
    host.appendChild(root);
    const shadow = root.attachShadow({mode:'open'});

    // Styles inside shadow
    const style = document.createElement('style');
    style.textContent = `
      :host{all:initial}
      *,*:before,*:after{box-sizing:border-box}
      :host, .app{color:#e9f2f7;font:14px/1.5 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif}
      .wrap{max-width:1100px;margin:0 auto;padding:12px}
      .top{display:flex;align-items:center;justify-content:space-between;margin:4px 0 14px 0}
      .brand{font-weight:800;font-size:18px}
      .brand small{color:#78b7ff;margin-left:6px}
      .pills{display:flex;gap:8px}
      .pill{display:inline-block;padding:8px 12px;border-radius:10px;background:#0f1a22;border:1px solid rgba(255,255,255,.14);color:#e9f2f7;text-decoration:none}
      .pill.active{background:#19323d;border-color:#2a4457}
      .stage{background:#0c1218;border:1px solid rgba(255,255,255,.10);border-radius:16px;padding:16px}
      .row{display:flex;gap:10px}
      .row>*{flex:1}
      .btn{display:inline-block;padding:10px 14px;border-radius:12px;background:#155e75;color:#fff;border:1px solid transparent;cursor:pointer;text-decoration:none}
      .btn.small{padding:8px 12px}
      .btn.alt{background:#23424e}
      .btn[disabled]{opacity:.55;cursor:not-allowed}
      .inp{width:100%;padding:10px 12px;border-radius:12px;border:1px solid rgba(255,255,255,.14);background:#101820;color:#e9f2f7}
      .muted{color:#9aa6b2}
      .center{text-align:center}
      .grid{display:grid;gap:12px}
      .sets{grid-template-columns:repeat(4,1fr)}
      .cards{grid-template-columns:repeat(5,1fr)}
      @media(max-width:1100px){.cards{grid-template-columns:repeat(4,1fr)}}
      @media(max-width:900px){.cards{grid-template-columns:repeat(3,1fr)}}
      @media(max-width:700px){.cards{grid-template-columns:repeat(2,1fr)} .sets{grid-template-columns:1fr}}
      .tile{background:#0f161d;border:1px solid rgba(255,255,255,.10);border-radius:12px;padding:10px;display:flex;flex-direction:column;gap:10px;box-shadow:0 1px 0 rgba(0,0,0,.25), 0 0 0 1px rgba(255,255,255,.04) inset}
      .tileHead{display:flex;align-items:center;gap:8px}
      .dot{width:10px;height:10px;border-radius:3px;background:#11202c;border:1px solid rgba(255,255,255,.12)}
      .sym{
        display:inline-block;
        height:22px;          /* consistent height */
        width:auto;
        max-width:44px;
        object-fit:contain;
        vertical-align:middle;
        margin-right:8px;
        border-radius:3px;
        flex:0 0 auto;
      }
      .code{margin-left:6px;opacity:.85;font-weight:700;font-size:12px}
      .imgWrap{background:#101820;border-radius:8px;height:120px;display:flex;align-items:center;justify-content:center;overflow:hidden}
      .logo{width:100%;height:100%;object-fit:contain}
      .cardImgWrap{position:relative;background:#101820;border-radius:8px;aspect-ratio:2.5/3.5;display:flex;align-items:center;justify-content:center;overflow:hidden}
      .cardImg{width:100%;height:100%;object-fit:contain}
      .badge{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);padding:6px 10px;border-radius:10px;background:rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.22);font-weight:800;backdrop-filter: blur(2px)}
      .badge.small{font-size:12px;padding:4px 8px;border-radius:8px}
      /* Variant overlay pill (bottom center) */
      .variant-pill{
        position:absolute;
        left:50%;
        bottom:10px;
        transform:translateX(-50%);
        padding:6px 12px;
        border-radius:999px;
        background:#ffffff;
        color:#0c1218;
        font-weight:700;
        font-size:12px;
        border:1px solid rgba(0,0,0,.25);
        box-shadow:0 2px 10px rgba(0,0,0,.35);
        white-space:nowrap;
      }
      .variant-pill.var-PB{
        background:linear-gradient(#fff,#d32f2f);
        color:#fff;
        border-color:rgba(0,0,0,.35);
      }
      .variant-pill.var-MB{
        background:linear-gradient(#ec9aff,#8e24aa);
        color:#fff;
        border-color:rgba(0,0,0,.35);
      }
      /* Image overlays (PB/MB art) */
      .variant-overlay{
        position:absolute;inset:0;width:100%;height:100%;
        object-fit:contain;pointer-events:none;
        filter:drop-shadow(0 2px 6px rgba(0,0,0,.35));
      }
      .tileFoot{display:flex;align-items:center;justify-content:space-between}
      .mt{margin-top:12px}
      .toast{position:fixed;right:16px;bottom:16px;background:#0f161d;border:1px solid rgba(255,255,255,.16);border-radius:10px;padding:10px 12px;box-shadow:0 8px 24px rgba(0,0,0,.4)}
      /* modal */
      .overlay{position:fixed;inset:0;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;z-index:9999}
      .modal{width:520px;max-width:95vw;background:#0f161d;color:#fff;border:1px solid rgba(255,255,255,.12);border-radius:14px;padding:16px}
      .modal h4{margin:0 0 10px 0;font-size:18px}
      .modal .actions{display:flex;justify-content:space-between;gap:8px;margin-top:16px}
      /* Era dividers */
      .era-divider{display:flex;align-items:center;margin:24px 0 16px 0;gap:16px}
      .era-line{flex:1;height:1px;background:linear-gradient(90deg,transparent,rgba(255,255,255,.2),transparent)}
      .era-title{font-size:14px;font-weight:600;color:#78b7ff;text-transform:uppercase;letter-spacing:1px;white-space:nowrap;padding:0 8px}
      .era-sets{margin-bottom:32px}
      
      /* Price Check Styles */
      .price-comparison{margin-top:8px;padding:8px;background:#0f161d;border-radius:6px;border:1px solid rgba(255,255,255,0.1)}
      .price-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;font-size:12px}
      .price-row:last-child{margin-bottom:0}
      .price-label{color:#9aa6b2}
      .price-value{color:#fff;font-weight:600}
      .price-row.price-higher .price-value{color:#ef4444}
      .price-row.price-lower .price-value{color:#10b981}
      
      /* Mobile responsive grid - only for Price Check */
      .pricecheck-grid{grid-template-columns:repeat(4,1fr)}
      @media (max-width: 768px) {
        .pricecheck-grid{grid-template-columns:repeat(2,1fr)}
        .pricecheck-grid .tile{padding:16px}
        .pricecheck-grid .cardImgWrap{margin-bottom:12px}
      }
      @media (max-width: 480px) {
        .pricecheck-grid .tile{padding:12px}
        .pricecheck-grid .cardImgWrap{margin-bottom:8px}
      }
      
      /* Theme Toggle Styles */
      .top-right{display:flex;align-items:center;gap:16px}
      .theme-toggle{
        background:#0f1a22;border:1px solid rgba(255,255,255,.14);border-radius:10px;
        padding:8px 12px;cursor:pointer;color:#e9f2f7;font-size:16px;
        transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;
        min-width:40px;height:40px;
      }
      .theme-toggle:hover{background:#19323d;border-color:#2a4457}
      .theme-icon{transition:transform 0.2s ease}
      
      /* Light Mode Styles */
      .app.light-mode{
        background:#f8fafc;color:#1e293b;
      }
      .app.light-mode body{
        background:#f8fafc;
      }
      .app.light-mode html{
        background:#f8fafc;
      }
      .app.light-mode{
        background:#f8fafc !important;
      }
      .app.light-mode .wrap{
        background:#f8fafc;
      }
      .app.light-mode .top{
        background:#f8fafc;
      }
      .app.light-mode .stage{
        background:#ffffff;border:1px solid rgba(0,0,0,.1);
      }
      .app.light-mode .tile{
        background:#ffffff;border:1px solid rgba(0,0,0,.1);box-shadow:0 1px 3px rgba(0,0,0,.1);
      }
      .app.light-mode .pill{
        background:#f1f5f9;border:1px solid rgba(0,0,0,.14);color:#475569;
      }
      .app.light-mode .pill.active{
        background:#e2e8f0;border-color:#cbd5e1;
      }
      .app.light-mode .btn{
        background:#3b82f6;color:#ffffff;
      }
      .app.light-mode .btn.alt{
        background:#64748b;
      }
      .app.light-mode .inp{
        background:#ffffff;border:1px solid rgba(0,0,0,.14);color:#1e293b;
      }
      .app.light-mode .modal{
        background:#ffffff !important;color:#1e293b !important;border:1px solid rgba(0,0,0,.12) !important;
      }
      .app.light-mode .modal h4{
        color:#1e293b !important;
      }
      .app.light-mode .modal label{
        color:#64748b !important;
      }
      .app.light-mode .modal .muted{
        color:#64748b !important;
      }
      .app.light-mode .modal input{
        background:#ffffff !important;border:1px solid rgba(0,0,0,.14) !important;color:#1e293b !important;
      }
      /* Modal light mode overrides */
      .app.light-mode .overlay .modal div[style*="background:#1a2332"]{
        background:#f1f5f9 !important;border:1px solid rgba(0,0,0,.1) !important;color:#1e293b !important;
      }
      .app.light-mode .overlay .modal div[style*="background:#0f161d"]{
        background:#f8fafc !important;border:1px solid rgba(0,0,0,.1) !important;color:#1e293b !important;
      }
      .app.light-mode .overlay #stockInfo{
        background:#f8fafc !important;border:1px solid rgba(0,0,0,.1) !important;color:#1e293b !important;
      }
      .app.light-mode .overlay .modal div[style*="color:#9aa6b2"]{
        color:#64748b !important;
      }
      .app.light-mode .overlay .modal div[style*="color:#78b7ff"]{
        color:#3b82f6 !important;
      }
      .app.light-mode .overlay .modal div[style*="color:#f59e0b"]{
        color:#d97706 !important;
      }
      .app.light-mode .overlay input[style*="background:#1a2332"]{
        background:#f1f5f9 !important;color:#64748b !important;
      }
      .app.light-mode .overlay a[style*="background:#f59e0b"]{
        background:#f59e0b !important;color:#ffffff !important;
      }
      .app.light-mode .theme-toggle{
        background:#f1f5f9;border:1px solid rgba(0,0,0,.14);color:#475569;
      }
      .app.light-mode .theme-toggle:hover{
        background:#e2e8f0;border-color:#cbd5e1;
      }
      .app.light-mode .brand{
        color:#1e293b;
      }
      .app.light-mode .brand small{
        color:#3b82f6;
      }
      .app.light-mode .muted{
        color:#64748b;
      }
      .app.light-mode .dot{
        background:#e2e8f0;border:1px solid rgba(0,0,0,.12);
      }
      .app.light-mode .imgWrap{
        background:#f8fafc;
      }
      .app.light-mode .cardImgWrap{
        background:#f8fafc;
      }
      .app.light-mode .code{
        color:#64748b;
      }
      .app.light-mode .badge{
        background:rgba(255,255,255,.9);color:#1e293b;border:1px solid rgba(0,0,0,.22);
      }
      .app.light-mode .variant-pill{
        background:#ffffff;color:#1e293b;border:1px solid rgba(0,0,0,.25);
      }
      .app.light-mode .variant-pill.var-PB{
        background:linear-gradient(#fff,#dc2626);color:#fff;
      }
      .app.light-mode .variant-pill.var-MB{
        background:linear-gradient(#ec9aff,#7c3aed);color:#fff;
      }
      .app.light-mode .overlay{
        background:rgba(0,0,0,.4);
      }
      .app.light-mode .toast{
        background:#ffffff;border:1px solid rgba(0,0,0,.16);color:#1e293b;
      }
      .app.light-mode .era-divider .era-title{
        color:#3b82f6;
      }
      .app.light-mode .era-line{
        background:linear-gradient(90deg,transparent,rgba(0,0,0,.2),transparent);
      }
      .app.light-mode select{
        background:#ffffff;border:1px solid rgba(0,0,0,.14);color:#1e293b;
      }
      .app.light-mode .price-row{
        color:#1e293b;
      }
      .app.light-mode .price-label{
        color:#64748b;
      }
      .app.light-mode .price-value{
        color:#1e293b;
      }
      .app.light-mode .price-row.price-higher .price-value{
        color:#dc2626;
      }
      .app.light-mode .price-row.price-lower .price-value{
        color:#059669;
      }
      .app.light-mode .price-comparison{
        background:#f8fafc;border:1px solid rgba(0,0,0,.1);
      }
      .app.light-mode #backToSets{
        background:#64748b !important;color:#ffffff !important;border-color:#64748b !important;
      }
      .app.light-mode .filter-sort-container{
        background:#f8fafc !important;border:1px solid rgba(0,0,0,.08) !important;color:#1e293b !important;
      }
      .app.light-mode .filter-sort-container *{
        color:#1e293b !important;
      }
      .app.light-mode #kori-v2-app{
        background:#f8fafc !important;
      }
      .app.light-mode #searchVariantFilter{
        background:#ffffff !important;border:1px solid rgba(0,0,0,.14) !important;color:#1e293b !important;
      }
      .app.light-mode #searchSortSelect{
        background:#ffffff !important;border:1px solid rgba(0,0,0,.14) !important;color:#1e293b !important;
      }
      .app.light-mode #variantFilter{
        background:#ffffff !important;border:1px solid rgba(0,0,0,.14) !important;color:#1e293b !important;
      }
      .app.light-mode #sortSelect{
        background:#ffffff !important;border:1px solid rgba(0,0,0,.14) !important;color:#1e293b !important;
      }
      
      /* Hide brand banner on mobile to save space */
      @media (max-width: 768px) {
        .brand{display:none}
        .top{padding:12px 16px}
        .top-right{gap:12px}
      }
      
      /* Skeleton loading animation */
      @keyframes skeleton-pulse {
        0%, 100% { opacity: 0.3; }
        50% { opacity: 0.6; }
      }
      .skeleton-loading {
        animation: skeleton-pulse 1.5s ease-in-out infinite;
      }
      
      /* Optimized image loading */
      .logo, .sym {
        transition: opacity 0.2s ease;
      }
      .logo[loading="lazy"], .sym[loading="lazy"] {
        opacity: 0;
      }
      .logo[loading="lazy"].loaded, .sym[loading="lazy"].loaded {
        opacity: 1;
      }
    `;
    shadow.appendChild(style);

    const app = document.createElement('div');
    app.className = 'app';
    app.innerHTML = `<div class="wrap">
      <div class="top">
        <div class="brand">Kori Collectibles <small>v2</small></div>
        <div class="top-right">
          <div class="pills">
            <a class="pill" data-nav="search" href="/koricollectiblesv2/search/">Search</a>
            <a class="pill" data-nav="sets" href="/koricollectiblesv2/sets/">Sets</a>
            <a class="pill" data-nav="pricecheck" href="/koricollectiblesv2/pricecheck/">Price Check</a>
            <a class="pill" data-nav="dashboard" href="/koricollectiblesv2/dashboard/">Dashboard</a>
          </div>
          <button id="themeToggle" class="theme-toggle" title="Toggle dark/light mode">
            <span class="theme-icon">🌙</span>
          </button>
        </div>
      </div>
      <div class="stage" id="stage"></div>
    </div>`;
    shadow.appendChild(app);
    
    // Initialize theme toggle functionality
    const themeToggle = shadow.getElementById('themeToggle');
    const themeIcon = themeToggle.querySelector('.theme-icon');
    const appElement = shadow.querySelector('.app');
    
    // Load saved theme or default to dark
    const savedTheme = localStorage.getItem('kori-theme') || 'dark';
    appElement.classList.toggle('light-mode', savedTheme === 'light');
    themeIcon.textContent = savedTheme === 'light' ? '☀️' : '🌙';
    
    // Set initial body, html, and app background
    document.body.style.backgroundColor = savedTheme === 'light' ? '#f8fafc' : '#0c1218';
    document.documentElement.style.backgroundColor = savedTheme === 'light' ? '#f8fafc' : '#0c1218';
    const koriApp = document.getElementById('kori-v2-app');
    if (koriApp) koriApp.style.backgroundColor = savedTheme === 'light' ? '#f8fafc' : '#0c1218';
    
    // Theme toggle click handler
    themeToggle.addEventListener('click', () => {
      const isLight = appElement.classList.contains('light-mode');
      const newTheme = isLight ? 'dark' : 'light';
      
      appElement.classList.toggle('light-mode');
      themeIcon.textContent = newTheme === 'light' ? '☀️' : '🌙';
      localStorage.setItem('kori-theme', newTheme);
      
      // Also set body, html, and app background for better coverage
      document.body.style.backgroundColor = newTheme === 'light' ? '#f8fafc' : '#0c1218';
      document.documentElement.style.backgroundColor = newTheme === 'light' ? '#f8fafc' : '#0c1218';
      const koriApp = document.getElementById('kori-v2-app');
      if (koriApp) koriApp.style.backgroundColor = newTheme === 'light' ? '#f8fafc' : '#0c1218';
    });
    
    return {root, shadow, stage: shadow.getElementById('stage')};
  }

	function sizeSymbols(shadow){
	  shadow.querySelectorAll('.tile .tileHead').forEach(head=>{
		const img = head.querySelector('img.sym');
		const txt = head.querySelector('div');
		if (!img || !txt) return;
		const lh = parseFloat(getComputedStyle(txt).lineHeight || '0');
		if (lh > 0) {
		  img.style.height = lh + 'px';
		  img.style.width  = 'auto';
		}
	  });
	}

  // ------- Views -------
  function setActive(shadow, where){
    shadow.querySelectorAll('.pill').forEach(a=>a.classList.toggle('active', a.dataset.nav===where));
  }

  // Cache for sets data with longer expiration
  let setsDataCache = null;
  const SETS_CACHE_DURATION = 30 * 60 * 1000; // 30 minutes

  // Helper function to format timestamp for tooltip
  function formatPriceTimestamp(timestamp) {
    if (!timestamp || timestamp === 'null' || timestamp === 'undefined') return 'Never updated';
    
    try {
      const date = new Date(timestamp);
      if (isNaN(date.getTime())) return 'Never updated';
      
      const now = new Date();
      const diffMs = now - date;
      const diffMins = Math.floor(diffMs / 60000);
      const diffHours = Math.floor(diffMs / 3600000);
      const diffDays = Math.floor(diffMs / 86400000);
      
      if (diffMins < 1) return 'Just now';
      if (diffMins < 60) return `${diffMins}m ago`;
      if (diffHours < 24) return `${diffHours}h ago`;
      if (diffDays < 7) return `${diffDays}d ago`;
      
      return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
    } catch (error) {
      console.warn('Error formatting timestamp:', timestamp, error);
      return 'Never updated';
    }
  }

  // Add tooltip styles to shadow DOM
  function addTooltipStyles(shadow) {
    if (!shadow.getElementById('tooltip-styles')) {
      const style = document.createElement('style');
      style.id = 'tooltip-styles';
      style.textContent = `
        .price-tooltip {
          position: relative;
          display: inline-block;
          cursor: help;
        }
        
        .price-tooltip .tooltip-content {
          visibility: hidden;
          width: 200px;
          background-color: #1a1a1a;
          color: #fff;
          text-align: center;
          border-radius: 6px;
          padding: 8px 12px;
          position: absolute;
          z-index: 1000;
          bottom: 125%;
          left: 50%;
          margin-left: -100px;
          opacity: 0;
          transition: opacity 0.3s;
          font-size: 12px;
          border: 1px solid #333;
          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
          pointer-events: none;
        }
        
        .price-tooltip .tooltip-content::after {
          content: "";
          position: absolute;
          top: 100%;
          left: 50%;
          margin-left: -5px;
          border-width: 5px;
          border-style: solid;
          border-color: #1a1a1a transparent transparent transparent;
        }
        
        .price-tooltip:hover .tooltip-content {
          visibility: visible;
          opacity: 1;
        }
        
        .light .price-tooltip .tooltip-content {
          background-color: #f8f9fa;
          color: #212529;
          border-color: #dee2e6;
        }
        
        .light .price-tooltip .tooltip-content::after {
          border-color: #f8f9fa transparent transparent transparent;
        }
      `;
      shadow.appendChild(style);
    }
  }

  // Optimized function to render sets data
  function renderSetsData(stage, container, sets, shadow = null) {
    // Cache the processed data
    stage.__koriSetsData = {
      sets: sets,
      timestamp: Date.now()
    };

    // Build lookup maps for faster access
    const totals = {}; const logos = {}; const symbols = {}; const codes = {}; const names = {};
    sets.forEach(s=>{
      totals[s.set_id]  = s.printed_total||s.total||0;
      logos[s.set_id]   = (s.local_logo||s.logo||'') || '';
      symbols[s.set_id] = (s.local_symbol||s.symbol||'') || '';
      codes[s.set_id]   = (s.code||'').toUpperCase();
      names[s.set_id]   = s.name || '';
    });
    stage.__koriSetTotals  = totals;
    stage.__koriSetLogos   = logos;
    stage.__koriSetSymbols = symbols;
    stage.__koriSetCodes   = codes;
    stage.__koriSetNames   = names;

    // Group sets by era (optimized)
    const setsByEra = {};
    sets.forEach(s => {
      const era = s.era || 'Unknown';
      if (!setsByEra[era]) setsByEra[era] = [];
      setsByEra[era].push(s);
    });

    // Era order (most recent first)
    const eraOrder = [
      'Scarlet & Violet', 'Sword & Shield', 'Sun & Moon', 'XY', 'Black & White',
      'Diamond & Pearl', 'HeartGold & SoulSilver', 'EX', 'e-Card', 'Neo', 'Gym', 'Base', 'Unknown'
    ];

    // Build HTML with lazy loading and skeleton screens
    let html = '';
    eraOrder.forEach(era => {
      if (setsByEra[era] && setsByEra[era].length > 0) {
        html += `<div class="era-divider">
          <div class="era-line"></div>
          <div class="era-title">${era} Era</div>
          <div class="era-line"></div>
        </div>`;
        
        html += `<div class="grid sets era-sets" data-era="${era}">`;
        html += setsByEra[era].map(s=>{
          const src = (s.local_logo||s.logo||'') || svgPH;
          const sym = (s.local_symbol||s.symbol||'') || '';
          return `<div class="tile">
            <div class="tileHead">
              ${sym ? `<img class="sym" src="${sym}" alt="" loading="lazy">` : `<span class="dot"></span>`}
              <div>${s.name||''} <span class="code">${s.code||''}</span></div>
            </div>
            <div class="imgWrap">
              <img class="logo" src="${src}" alt="${s.name||''}" loading="lazy" onerror="this.src='${svgPH}'">
            </div>
            <div class="tileFoot">
              <div class="muted">${s.release_date||''}</div>
              <button class="btn small" data-set-id="${s.set_id}">View cards</button>
            </div>
          </div>`;
        }).join('');
        html += '</div>';
      }
    });

    // Use document fragment for better performance
    const fragment = document.createDocumentFragment();
    const tempDiv = document.createElement('div');
    tempDiv.innerHTML = html;
    while (tempDiv.firstChild) {
      fragment.appendChild(tempDiv.firstChild);
    }
    container.appendChild(fragment);

    // Optimize image loading with intersection observer
    const images = container.querySelectorAll('img[loading="lazy"]');
    if ('IntersectionObserver' in window) {
      const imageObserver = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            const img = entry.target;
            img.classList.add('loaded');
            observer.unobserve(img);
          }
        });
      }, { rootMargin: '50px' });
      
      images.forEach(img => imageObserver.observe(img));
    } else {
      // Fallback for older browsers
      images.forEach(img => img.classList.add('loaded'));
    }

    // Remove existing event listeners to prevent duplicates
    container.removeEventListener('click', container._koriSetsClickHandler);
    
    // Add click event listener (event delegation)
    container._koriSetsClickHandler = (ev) => {
      console.log('Container click event triggered', ev.target);
      const b = ev.target.closest('[data-set-id]'); 
      if(!b) {
        console.log('No data-set-id found on clicked element');
        return;
      }
      console.log('Found set ID button:', b.dataset.setId);
      ev.preventDefault(); 
      
      // Use the shadow reference passed to this function
      if (shadow && stage) {
        viewCards(shadow, stage, b.dataset.setId);
      } else {
        console.error('Shadow or stage reference not available');
      }
    };
    
    container.addEventListener('click', container._koriSetsClickHandler);
  }

  function viewSets(shadow, stage){
    setActive(shadow,'sets');
    
    // Show loading state with skeleton screens
    stage.innerHTML = `
      <div id="setsContainer">
        <div style="text-align:center;padding:20px;color:#9aa6b2">
          <div>Loading sets...</div>
        </div>
        <div class="grid sets skeleton-loading">
          ${Array(12).fill().map(() => `
            <div class="tile">
              <div class="tileHead">
                <span class="dot"></span>
                <div>Loading...</div>
              </div>
              <div class="imgWrap" style="background:#1a1a1a"></div>
              <div class="tileFoot">
                <div class="muted">...</div>
                <button class="btn small" disabled>Loading...</button>
              </div>
            </div>
          `).join('')}
        </div>
      </div>
    `;
    const container = shadow.getElementById('setsContainer');
    
    // Check global cache first (immediate render, no loading state)
    if (setsDataCache && Date.now() - setsDataCache.timestamp < SETS_CACHE_DURATION) {
      container.innerHTML = ''; // Clear any loading state
      renderSetsData(stage, container, setsDataCache.sets, shadow);
      return;
    }
    
    // Check stage cache (immediate render, no loading state)
    if (stage.__koriSetsData && Date.now() - stage.__koriSetsData.timestamp < SETS_CACHE_DURATION) {
      container.innerHTML = ''; // Clear any loading state
      renderSetsData(stage, container, stage.__koriSetsData.sets, shadow);
      return;
    }
    
    // Fetch data with progress indication
    rest('sets/local/full').then(sets=>{
      // Store in global cache
      setsDataCache = {
        sets: sets,
        timestamp: Date.now()
      };
      
      // Clear loading state and render
      container.innerHTML = '';
      renderSetsData(stage, container, sets, shadow);
      
    }).catch(()=> {
      container.innerHTML = `<div class="center muted" style="grid-column:1/-1;padding:40px">Failed to load sets</div>`;
    });
  }


  // Cache for variant definitions
  let variantDefinitionsCache = null;
  
  // Function to get variant definitions (cached)
  async function getVariantDefinitions() {
    if (variantDefinitionsCache) return variantDefinitionsCache;
    
    try {
      const REST_BASE = (window.KORI_REST||'/wp-json/kori/v2/');
      const response = await fetch(REST_BASE + 'variants/defs', { credentials: 'same-origin' });
      if (response.ok) {
        variantDefinitionsCache = await response.json();
        return variantDefinitionsCache;
      }
    } catch (e) {
      console.warn('Could not fetch variant definitions:', e);
    }
    return [];
  }
  
  // Function to build eBay search URL for a card variant
  function buildEbayUrl(card, variantDefs = [], setInfo = {}) {
    const vLabel = getVariantLabel(card);
    const vKey = getVariantKey(card);
    
    // Clean the card name (remove variant suffix)
    const cleanCardName = stripVariantFromName(card.name) || card.name || '';
    
    // Build card number in format 001/086 using set info
    const cardNumber = card.card_number || '';
    const setTotal = setInfo.total || setInfo.printed_total || '';
    const setName = setInfo.name || '';
    let cardNumberFormatted = '';
    
    // Check if this is a promo card by looking at set name or set ID
    const isPromo = setName.toLowerCase().includes('promos') || 
                   (card.set_id && card.set_id.toLowerCase().includes('promo'));
    
    if (cardNumber && setTotal) {
      // Check if this is a TG or GG card
      const tgMatch = cardNumber.match(/^(TG)(\d+)$/i);
      const ggMatch = cardNumber.match(/^(GG)(\d+)$/i);
      
      if (tgMatch) {
        // Format as TG01/TG30 (no extra padding for gallery cards)
        const tgNumber = tgMatch[2].padStart(2, '0');
        const tgTotal = setTotal.padStart(2, '0');
        cardNumberFormatted = `TG${tgNumber}/TG${tgTotal}`;
      } else if (ggMatch) {
        // Format as GG01/GG70 (no extra padding for gallery cards)
        const ggNumber = ggMatch[2].padStart(2, '0');
        const ggTotal = setTotal.padStart(2, '0');
        cardNumberFormatted = `GG${ggNumber}/GG${ggTotal}`;
      } else if (isPromo) {
        // Format promo cards with set prefix (SVP001, SWSH001, etc.)
        const paddedNumber = cardNumber.padStart(3, '0');
        const setPrefix = getPromoSetPrefix(setName, card.set_id);
        cardNumberFormatted = `${setPrefix}${paddedNumber}`;
      } else {
        // Regular card formatting
        const paddedNumber = cardNumber.padStart(3, '0');
        const paddedTotal = setTotal.padStart(3, '0');
        cardNumberFormatted = `${paddedNumber}/${paddedTotal}`;
      }
    } else if (cardNumber) {
      if (isPromo) {
        const setPrefix = getPromoSetPrefix(setName, card.set_id);
        cardNumberFormatted = `${setPrefix}${cardNumber.padStart(3, '0')}`;
      } else {
        cardNumberFormatted = cardNumber.padStart(3, '0');
      }
    }
    
    // Helper function to get promo set prefix
    function getPromoSetPrefix(setName, setId) {
      const name = (setName || '').toLowerCase();
      const id = (setId || '').toLowerCase();
      
      if (name.includes('scarlet') || name.includes('violet') || id.includes('sv')) return 'SVP';
      if (name.includes('sword') || name.includes('shield') || id.includes('swsh')) return 'SWSH';
      if (name.includes('sun') || name.includes('moon') || id.includes('sm')) return 'SM';
      if (name.includes('xy') || id.includes('xy')) return 'XY';
      if (name.includes('black') || name.includes('white') || id.includes('bw')) return 'BW';
      if (name.includes('diamond') || name.includes('pearl') || id.includes('dp')) return 'DP';
      if (name.includes('heartgold') || name.includes('soulsilver') || id.includes('hgss')) return 'HGSS';
      
      return 'PROMO'; // fallback
    }
    
    // Get set name from set info and clean it for promo cards
    let cleanSetName = setInfo.name || '';
    
    // Remove "Promos" from set name for promo cards to avoid redundancy
    if (cleanSetName && cleanSetName.toLowerCase().includes('promos')) {
      cleanSetName = cleanSetName.replace(/\s+Promos\s*$/i, '').trim();
    }
    
    // Get variant query from cached definitions
    let variantQuery = '';
    // Handle empty variant names as "Normal"
    const lookupVariantName = vLabel || 'Normal';
    const variant = variantDefs.find(v => 
      v.name === lookupVariantName || 
      v.name.toLowerCase() === lookupVariantName.toLowerCase()
    );
    if (variant && variant.query) {
      variantQuery = variant.query;
    }
    
    // Build search terms (same order as Python scraper: card name, card number, set name, variant name, variant query)
    const searchTerms = [];
    searchTerms.push(cleanCardName);
    if (cardNumberFormatted) searchTerms.push(cardNumberFormatted);
    if (cleanSetName) searchTerms.push(cleanSetName);
    if (vLabel && vLabel.toLowerCase() !== 'normal') searchTerms.push(vLabel);
    if (variantQuery) searchTerms.push(variantQuery);
    
    // Join and encode
    const query = searchTerms.join(' ');
    const encodedQuery = encodeURIComponent(query);
    
    // Return eBay sold listings URL (UK only)
    return `https://www.ebay.co.uk/sch/i.html?_nkw=${encodedQuery}&_sacat=0&LH_Sold=1&LH_Complete=1&_sop=13&LH_PrefLoc=1`;
  }

  function viewCards(shadow, stage, setId){
    console.log('viewCards called with setId:', setId);
    console.log('Available cached data:', {
      totals: stage.__koriSetTotals,
      logos: stage.__koriSetLogos,
      symbols: stage.__koriSetSymbols,
      codes: stage.__koriSetCodes
    });
    
    const totals = stage.__koriSetTotals||{}; const logos = stage.__koriSetLogos||{}; const symbols = stage.__koriSetSymbols||{}; const codes = stage.__koriSetCodes||{};
    const total = totals[setId]||0; const padLen = String(total||0).length||3;
    const logo = logos[setId]||''; const sym = symbols[setId]||''; const setCode = setId;
    
    // Add filter and sort dropdowns above the cards grid (variant filter will be populated dynamically)
    const filterSortDropdowns = `
      <div class="filter-sort-container" style="display:flex;gap:16px;margin-bottom:16px;padding:12px;background:#0f161d;border:1px solid rgba(255,255,255,0.08);border-radius:12px;">
        <div style="display:flex;align-items:center;gap:8px;">
          <div style="font-weight:600;color:#eaf1f5;">Filter:</div>
          <select id="variantFilter" style="padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,0.14);background:#101820;color:#eaf1f5;font-size:14px;">
            <option value="all">All Variants</option>
          </select>
        </div>
        <div style="display:flex;align-items:center;gap:8px;">
          <div style="font-weight:600;color:#eaf1f5;">Sort by:</div>
          <select id="sortSelect" style="padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,0.14);background:#101820;color:#eaf1f5;font-size:14px;">
            <option value="card_id">Card ID</option>
            <option value="price_high">Highest Price</option>
            <option value="price_low">Lowest Price</option>
          </select>
        </div>
      </div>
    `;
    
    // Add back button to return to sets view (top left)
    const backButton = `
      <div style="display:flex;align-items:center;margin-bottom:16px">
        <button id="backToSets" class="btn" style="background:#6b7280;border-color:#6b7280">
          ← Back to Sets
        </button>
      </div>
    `;
    
    stage.innerHTML = `${logo?`<div class="center" style="margin-bottom:10px"><img src="${logo}" style="max-width:360px;max-height:120px;object-fit:contain"></div>`:''}${backButton}${filterSortDropdowns}<div class="grid cards" id="cardsGrid"></div>`;
    const grid = shadow.getElementById('cardsGrid');
    const sortSelect = shadow.getElementById('sortSelect');
    const variantFilter = shadow.getElementById('variantFilter');
    const backToSetsBtn = shadow.getElementById('backToSets');
    
    // Add back button event listener
    backToSetsBtn?.addEventListener('click', (e) => {
      e.preventDefault();
      // Clear any loading state and go back to sets view
      stage.innerHTML = '';
      viewSets(shadow, stage);
    });
    
    // Store cards data for sorting
    let allCards = [];
    
    // Function to populate variant filter dropdown with only variants that exist in the cards
    function populateVariantFilter(cards) {
      const uniqueVariants = new Set();
      cards.forEach(card => {
        const variantLabel = getVariantLabel(card);
        if (variantLabel) {
          uniqueVariants.add(variantLabel);
        }
      });
      
      // Clear existing options except "All Variants"
      variantFilter.innerHTML = '<option value="all">All Variants</option>';
      
      // Add options for each unique variant found
      Array.from(uniqueVariants).sort().forEach(variant => {
        const option = document.createElement('option');
        option.value = variant;
        option.textContent = variant;
        variantFilter.appendChild(option);
      });
    }
    
    // Function to filter cards by variant
    function filterCardsByVariant(cards, variantFilter) {
      if (variantFilter === 'all') return cards;
      return cards.filter(card => {
        const variantLabel = getVariantLabel(card);
        return variantLabel === variantFilter;
      });
    }
    
    // Function to render cards with current filter and sort
    function renderCards(cards, variantDefs, setInfo) {
      if(!cards||!cards.length){ 
        grid.innerHTML='<div class="center muted" style="grid-column:1/-1">No cards found</div>'; 
        return; 
      }
      
      // Apply variant filter
      const filteredCards = filterCardsByVariant(cards, variantFilter.value);
      
      if(!filteredCards.length){ 
        grid.innerHTML='<div class="center muted" style="grid-column:1/-1">No cards found for selected variant</div>'; 
        return; 
      }
      
      grid.innerHTML = filteredCards.map(c=>{
	      const img = imgOf(c);
	      const vLabel = getVariantLabel(c);
	      const vKey   = getVariantKey(c);
	      
	      
	      const imgTag = `
		      <div class="cardImgWrap">
		        ${img ? `<img class="cardImg" src="${img}" loading="lazy" alt="${stripVariantFromName(c.name)||''}">`
		              : `<img class="cardImg" src="${svgPH}" alt="">`}
            ${OVERLAY_SETS.has(setCode.toUpperCase()) ? variantImageOverlayHTML(setCode, vKey) : ''}
		        ${variantPillHTML(vLabel, vKey)}
		      </div>`;
	      const padded = String(c.card_number||'').padStart(padLen,'0');
	      const meta = `${c.set_name||''} ${padded}${total?'/'+total:''}`.trim();
	      const sku = `${setCode}-${String(c.card_number||'').padStart(3,'0')}${vKey ? '-' + vKey : ''}`;
	      
	      // Get price for this variant
	      let priceDisplay = '';
	      if (c.prices && Array.isArray(c.prices)) {
	        // Find the price for this variant based on variant index
	        const variantIndex = getVariantIndex(c, vKey);
	        if (variantIndex >= 0 && c.prices[variantIndex] > 0) {
	          const price = c.prices[variantIndex].toFixed(2);
	          const timestamp = c.price_updated_at || '';
	          const formattedTime = formatPriceTimestamp(timestamp);
	          priceDisplay = `
	            <div class="price-tooltip" style="text-align:center;margin:8px 0">
	              <div style="font-weight:600;color:#4ade80;cursor:help">£${price}</div>
	              <div class="tooltip-content">
	                Price updated: ${formattedTime}
	              </div>
	            </div>
	          `;
	        }
	      }
	      
	      // Build eBay URL for debug button using set info
	      const ebayUrl = buildEbayUrl(c, variantDefs, setInfo);
	      
	      // Prepare card data for modal
	      const cardData = {
	        card_id: c.card_id,
	        sku: sku,
	        name: c.name,
	        variant: vLabel,
	        table_price: c.prices && Array.isArray(c.prices) ? c.prices[getVariantIndex(c, vKey)] || 0 : 0
	      };
	      
	      return `<div class="tile">
		      <div class="tileHead">
		        ${sym ? `<img class="sym" src="${sym}" alt="">` : `<span class="dot"></span>`}
		        <div>${stripVariantFromName(c.name)||''}</div>
		      </div>
		      ${imgTag}
		      <div class="muted" style="display:flex;justify-content:space-between;align-items:center">
		        <span>${meta}</span>
		        <span style="font-weight:600;color:#78b7ff">${sku}</span>
		      </div>
		      ${priceDisplay}
		      <div class="row" style="gap:8px">
		        <a href="#" class="btn manage" data-card-id="${c.card_id||''}" data-card-data="${JSON.stringify(cardData).replace(/"/g, '&quot;')}" style="text-align:center;font-size:12px;padding:6px 8px">Manage</a>
		        <a href="${ebayUrl}" target="_blank" class="btn" style="background:#f59e0b;color:#fff;font-size:12px;padding:6px 8px;text-align:center">Debug</a>
		      </div>
	      </div>`;
	    }).join('');
    }
    
    // Function to sort cards based on selected option
    function sortCards(cards, sortBy) {
      const sortedCards = [...cards];
      
      switch(sortBy) {
        case 'card_id':
          return sortedCards.sort((a, b) => {
            const aNum = parseInt(a.card_number) || 0;
            const bNum = parseInt(b.card_number) || 0;
            return aNum - bNum;
          });
          
        case 'price_high':
          return sortedCards.sort((a, b) => {
            const aPrice = getVariantPrice(a);
            const bPrice = getVariantPrice(b);
            return bPrice - aPrice;
          });
          
        case 'price_low':
          return sortedCards.sort((a, b) => {
            const aPrice = getVariantPrice(a);
            const bPrice = getVariantPrice(b);
            return aPrice - bPrice;
          });
          
        default:
          return sortedCards;
      }
    }
    
    // Helper function to get the price for this specific variant
    function getVariantPrice(card) {
      if (!card.prices || !Array.isArray(card.prices)) return 0;
      const variantIndex = getVariantIndex(card, getVariantKey(card));
      return variantIndex >= 0 && card.prices[variantIndex] > 0 ? card.prices[variantIndex] : 0;
    }
    
    // Load variant definitions and cards, then get set info from the cached data
    console.log('Fetching cards for set:', setId);
    Promise.all([
      getVariantDefinitions(),
      rest('sets/local/'+encodeURIComponent(setId)+'/cards')
    ]).then(async ([variantDefs, cards]) => {
      console.log('Cards loaded successfully:', cards?.length || 0, 'cards');
      // Get set info from the cached sets data, with fallback to fetch if not available
      let setInfo = {
        name: stage.__koriSetNames?.[setId] || '',
        total: stage.__koriSetTotals?.[setId] || 0,
        printed_total: stage.__koriSetTotals?.[setId] || 0
      };
      
      // If cached data is not available, fetch it
      if (!setInfo.name || !setInfo.total) {
        console.log('Set info not cached, fetching from API...');
        try {
          const allSets = await rest('sets/local/full');
          const setData = allSets.find(s => s.set_id === setId);
          if (setData) {
            setInfo = {
              name: setData.name || '',
              total: setData.printed_total || setData.total || 0,
              printed_total: setData.printed_total || setData.total || 0
            };
            // Update the logo and symbol from the fetched data
            logo = setData.local_logo || setData.logo || '';
            sym = setData.local_symbol || setData.symbol || '';
          }
        } catch (error) {
          console.error('Failed to fetch set data:', error);
        }
      }
      
      // Store all cards for sorting
      allCards = cards || [];
      
      // Populate variant filter with only variants that exist in this set
      populateVariantFilter(allCards);
      
      // Initial render with default filter and sort
      renderCards(sortCards(allCards, 'card_id'), variantDefs, setInfo);
      
      // Add event listeners for both dropdowns
      sortSelect.addEventListener('change', (e) => {
        const sortBy = e.target.value;
        renderCards(sortCards(allCards, sortBy), variantDefs, setInfo);
      });
      
      variantFilter.addEventListener('change', (e) => {
        const sortBy = sortSelect.value;
        renderCards(sortCards(allCards, sortBy), variantDefs, setInfo);
      });
      
    }).catch(err => {
      console.error('Error loading cards:', err);
      console.error('Error details:', {
        message: err.message,
        stack: err.stack,
        setId: setId
      });
      grid.innerHTML = `<div class="center muted" style="grid-column:1/-1">
        Failed to load cards<br>
        <small style="color:#666;margin-top:8px;display:block">
          Set ID: ${setId}<br>
          Error: ${err.message || 'Unknown error'}
        </small>
      </div>`;
    });
  }


  // Build (and cache) a set_id -> symbol map for Search view
  async function ensureSymbolsMap(shadow, stage){
    if (stage.__koriSetSymbols && stage.__koriSetCodes) return stage.__koriSetSymbols;
    try{
      const sets = await rest('sets/local/full');
      const map = {}; const codes = {};
      sets.forEach(s=>{ if (s.set_id) { map[s.set_id] = (s.local_symbol||s.symbol||'') || ''; codes[s.set_id] = (s.code||'').toUpperCase(); } });
      stage.__koriSetSymbols = map;
      stage.__koriSetCodes   = codes;
      return map;
    }catch(_){
      stage.__koriSetSymbols = {};
      stage.__koriSetCodes   = {};
      return {};
    }
  }

  function viewSearch(shadow, stage){
    setActive(shadow,'search');
    
    // Add filter and sort dropdowns to search page (variant filter will be populated dynamically)
    const searchFilterSortDropdowns = `
      <div class="filter-sort-container" style="display:flex;gap:16px;margin-bottom:16px;padding:12px;background:#0f161d;border:1px solid rgba(255,255,255,0.08);border-radius:12px;">
        <div style="display:flex;align-items:center;gap:8px;">
          <div style="font-weight:600;color:#eaf1f5;">Filter:</div>
          <select id="searchVariantFilter" style="padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,0.14);background:#101820;color:#eaf1f5;font-size:14px;">
            <option value="all">All Variants</option>
          </select>
        </div>
        <div style="display:flex;align-items:center;gap:8px;">
          <div style="font-weight:600;color:#eaf1f5;">Sort by:</div>
          <select id="searchSortSelect" style="padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,0.14);background:#101820;color:#eaf1f5;font-size:14px;">
            <option value="card_id">Card ID</option>
            <option value="price_high">Highest Price</option>
            <option value="price_low">Lowest Price</option>
          </select>
        </div>
      </div>
    `;
    
    stage.innerHTML = `<div class="row"><input id="q" class="inp" placeholder="Search any card name or number…"><button id="go" class="btn">SEARCH</button></div>${searchFilterSortDropdowns}<div class="grid cards mt" id="results"></div>`;
    const q = shadow.getElementById('q'); const go = shadow.getElementById('go'); const res = shadow.getElementById('results');
    const searchSortSelect = shadow.getElementById('searchSortSelect');
    const searchVariantFilter = shadow.getElementById('searchVariantFilter');

    // Store search results for sorting
    let searchResults = [];
    
    // Function to populate search variant filter dropdown with only variants that exist in the search results
    function populateSearchVariantFilter(cards) {
      const uniqueVariants = new Set();
      cards.forEach(card => {
        const variantLabel = getVariantLabel(card);
        if (variantLabel) {
          uniqueVariants.add(variantLabel);
        }
      });
      
      // Clear existing options except "All Variants"
      searchVariantFilter.innerHTML = '<option value="all">All Variants</option>';
      
      // Add options for each unique variant found
      Array.from(uniqueVariants).sort().forEach(variant => {
        const option = document.createElement('option');
        option.value = variant;
        option.textContent = variant;
        searchVariantFilter.appendChild(option);
      });
    }
    
    // Function to filter search results by variant
    function filterSearchResultsByVariant(cards, variantFilter) {
      if (variantFilter === 'all') return cards;
      return cards.filter(card => {
        const variantLabel = getVariantLabel(card);
        return variantLabel === variantFilter;
      });
    }
    
    // Function to render search results with current filter and sort
    function renderSearchResults(cards, variantDefs) {
      if(!cards||!cards.length){ 
        res.innerHTML='<div class="center muted" style="grid-column:1/-1">No results</div>'; 
        return; 
      }
      
      // Apply variant filter
      const filteredCards = filterSearchResultsByVariant(cards, searchVariantFilter.value);
      
      if(!filteredCards.length){ 
        res.innerHTML='<div class="center muted" style="grid-column:1/-1">No results for selected variant</div>'; 
        return; 
      }
      
      res.innerHTML = filteredCards.map(c=>{
		      const img = imgOf(c);
		      const vLabel = getVariantLabel(c);
		      const vKey   = getVariantKey(c);
          const setCode = c.set_id;
		      const imgTag = `
			      <div class="cardImgWrap">
			        ${img ? `<img class="cardImg" src="${img}" loading="lazy" alt="${stripVariantFromName(c.name)||''}">`
			              : `<img class="cardImg" src="${svgPH}" alt="">`}
              ${OVERLAY_SETS.has(setCode.toUpperCase()) ? variantImageOverlayHTML(setCode, vKey) : ''}
			        ${variantPillHTML(vLabel, vKey)}
			      </div>`;
		      const total = parseInt(c.set_total,10)||0; const padLen = String(total||0).length||3;
		      const padded = String(c.card_number||'').padStart(padLen,'0');
		      const meta = `${c.set_name||''} ${padded}${total?'/'+total:''}`.trim();
		      const sym = (stage.__koriSetSymbols||{})[c.set_id] || '';
		      const sku = `${setCode}-${String(c.card_number||'').padStart(3,'0')}${vKey ? '-' + vKey : ''}`;
		      
		      // Get price for this variant
		      let priceDisplay = '';
		      if (c.prices && Array.isArray(c.prices)) {
		        // Find the price for this variant based on variant index
		        const variantIndex = getVariantIndex(c, vKey);
		        if (variantIndex >= 0 && c.prices[variantIndex] > 0) {
		          const price = c.prices[variantIndex].toFixed(2);
		          const timestamp = c.price_updated_at || '';
		          const formattedTime = formatPriceTimestamp(timestamp);
		          priceDisplay = `
		            <div class="price-tooltip" style="text-align:center;margin:8px 0">
		              <div style="font-weight:600;color:#4ade80;cursor:help">£${price}</div>
		              <div class="tooltip-content">
		                Price updated: ${formattedTime}
		              </div>
		            </div>
		          `;
		        }
		      }
		      
		      // Build set info for debug button
		      const setInfo = {
		        name: c.set_name || '',
		        total: c.set_total || c.printed_total || 0,
		        printed_total: c.set_total || c.printed_total || 0
		      };
		      
		      // Build eBay URL for debug button
		      const ebayUrl = buildEbayUrl(c, variantDefs, setInfo);
		      
		      // Prepare card data for modal
		      const cardData = {
		        card_id: c.card_id,
		        sku: sku,
		        name: c.name,
		        variant: vLabel,
		        table_price: c.prices && Array.isArray(c.prices) ? c.prices[getVariantIndex(c, vKey)] || 0 : 0
		      };
		      
		      return `<div class="tile">
			      <div class="tileHead">
			        ${sym ? `<img class="sym" src="${sym}" alt="">` : `<span class="dot"></span>`}
			        <div>${stripVariantFromName(c.name)||''}</div>
			      </div>
			      ${imgTag}
			      <div class="muted" style="display:flex;justify-content:space-between;align-items:center">
			        <span>${meta}</span>
			        <span style="font-weight:600;color:#78b7ff">${sku}</span>
			      </div>
			      ${priceDisplay}
			      <div class="row" style="gap:8px">
			        <a href="#" class="btn manage" data-card-id="${c.card_id||''}" data-card-data="${JSON.stringify(cardData).replace(/"/g, '&quot;')}" style="text-align:center;font-size:12px;padding:6px 8px">Manage</a>
			        <a href="${ebayUrl}" target="_blank" class="btn" style="background:#f59e0b;color:#fff;font-size:12px;padding:6px 8px;text-align:center">Debug</a>
			      </div>
		      </div>`;
	      }).join('');
    }
    
    // Function to sort search results based on selected option
    function sortSearchResults(cards, sortBy) {
      const sortedCards = [...cards];
      
      switch(sortBy) {
        case 'card_id':
          return sortedCards.sort((a, b) => {
            const aNum = parseInt(a.card_number) || 0;
            const bNum = parseInt(b.card_number) || 0;
            return aNum - bNum;
          });
          
        case 'price_high':
          return sortedCards.sort((a, b) => {
            const aPrice = getVariantPrice(a);
            const bPrice = getVariantPrice(b);
            return bPrice - aPrice;
          });
          
        case 'price_low':
          return sortedCards.sort((a, b) => {
            const aPrice = getVariantPrice(a);
            const bPrice = getVariantPrice(b);
            return aPrice - bPrice;
          });
          
        default:
          return sortedCards;
      }
    }
    
    // Helper function to get the price for this specific variant (reuse from sets view)
    function getVariantPrice(card) {
      if (!card.prices || !Array.isArray(card.prices)) return 0;
      const variantIndex = getVariantIndex(card, getVariantKey(card));
      return variantIndex >= 0 && card.prices[variantIndex] > 0 ? card.prices[variantIndex] : 0;
    }

    const run = async ()=>{
      const s=(q.value||'').trim();
      if (s.length<2){ res.innerHTML=''; return; }
      res.innerHTML='<div class="center muted" style="grid-column:1/-1">Searching…</div>';

      await ensureSymbolsMap(shadow, stage);

      rest('cards/search-local?query='+encodeURIComponent(s)+'&expandVariants=1').then(async rows=>{
        if(!rows||!rows.length){ 
          res.innerHTML='<div class="center muted" style="grid-column:1/-1">No results</div>'; 
          return; 
        }
        
        // Get variant definitions for debug buttons
        const variantDefs = await getVariantDefinitions();
        
        // Store search results for sorting
        searchResults = rows || [];
        
        // Populate variant filter with only variants that exist in the search results
        populateSearchVariantFilter(searchResults);
        
        // Initial render with default filter and sort
        renderSearchResults(sortSearchResults(searchResults, 'card_id'), variantDefs);
        
        // Add event listeners for both dropdowns
        searchSortSelect.addEventListener('change', (e) => {
          const sortBy = e.target.value;
          renderSearchResults(sortSearchResults(searchResults, sortBy), variantDefs);
        });
        
        searchVariantFilter.addEventListener('change', (e) => {
          const sortBy = searchSortSelect.value;
          renderSearchResults(sortSearchResults(searchResults, sortBy), variantDefs);
        });

      }).catch(()=>res.innerHTML='<div class="center" style="grid-column:1/-1;color:#f99">Search failed</div>');
    };

    go.addEventListener('click', run);
    q.addEventListener('keypress', e=>{ if(e.key==='Enter') run(); });
  }



  function showToast(shadow, msg){
    const t = document.createElement('div'); t.className='toast'; t.textContent = msg || '';
    shadow.appendChild(t);
    setTimeout(()=>{t.remove();}, 1800);
  }

    // ------- Modal (inside Shadow DOM) -------
    function openModal(shadow, card_id, cardData){
      const overlay = document.createElement('div');
      overlay.className = 'overlay';
      // Prepare data
      const variantLabel = String(cardData?.variant || 'Normal');
      const tablePrice = cardData?.table_price || (cardData?.prices ? Object.values(cardData.prices)[0] : 0);
      
      // Check if light mode is active (inside shadow DOM)
      const isLightMode = shadow.querySelector('.app')?.classList.contains('light-mode');
      
      overlay.innerHTML = `<div class="modal" style="background:${isLightMode ? '#ffffff' : '#0f161d'};color:${isLightMode ? '#1e293b' : '#fff'};border:1px solid ${isLightMode ? 'rgba(0,0,0,.12)' : 'rgba(255,255,255,.12)'}">
        <h4 style="color:${isLightMode ? '#1e293b' : '#fff'}">Manage stock</h4>
        <div style="margin-bottom:12px;padding:8px;background:${isLightMode ? '#f1f5f9' : '#1a2332'};border-radius:8px;border:1px solid ${isLightMode ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.1)'}">
          <div style="font-weight:600;margin-bottom:4px;color:${isLightMode ? '#1e293b' : '#fff'}">${cardData?.name || 'Unknown Card'}</div>
          <div style="font-size:12px;color:${isLightMode ? '#64748b' : '#9aa6b2'}">${cardData?.sku || 'No SKU'}</div>
          <div style="font-size:12px;color:${isLightMode ? '#3b82f6' : '#78b7ff'}">${variantLabel}</div>
        </div>
        
        <div id="stockInfo" style="margin-bottom:16px;padding:8px;background:${isLightMode ? '#f8fafc' : '#0f161d'};border-radius:8px;border:1px solid ${isLightMode ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.1)'}">
          <div style="font-size:12px;color:${isLightMode ? '#64748b' : '#9aa6b2'}">Checking stock status...</div>
        </div>
        
        <div><label class="muted" style="display:block;margin-bottom:6px;color:${isLightMode ? '#64748b' : '#9aa6b2'}">Price (£)</label>
          <input id="mPrice" type="number" step="0.01" placeholder="e.g. 1.00" class="inp" style="background:${isLightMode ? '#ffffff' : '#101820'};border:1px solid ${isLightMode ? 'rgba(0,0,0,.14)' : 'rgba(255,255,255,.14)'};color:${isLightMode ? '#1e293b' : '#e9f2f7'}">
        </div>
        <div class="mt"><label class="muted" style="display:block;margin-bottom:6px;color:${isLightMode ? '#64748b' : '#9aa6b2'}">Quantity</label>
          <input id="mQty" type="number" min="1" step="1" value="1" class="inp" style="background:${isLightMode ? '#ffffff' : '#101820'};border:1px solid ${isLightMode ? 'rgba(0,0,0,.14)' : 'rgba(255,255,255,.14)'};color:${isLightMode ? '#1e293b' : '#e9f2f7'}">
        </div>
        
        <div class="mt"><label class="muted" style="display:block;margin-bottom:6px;color:${isLightMode ? '#64748b' : '#9aa6b2'}">Table Price (£)</label>
          <input id="mTablePrice" type="number" step="0.01" value="${tablePrice || ''}" class="inp" readonly style="background:${isLightMode ? '#f1f5f9' : '#1a2332'};color:${isLightMode ? '#64748b' : '#9aa6b2'}">
        </div>
        
        <div class="row" style="margin-top:16px;gap:8px">
          <a href="#" id="mDebug" class="btn" style="background:#f59e0b;color:#fff;font-size:12px;padding:8px 12px;text-align:center;text-decoration:none">Debug</a>
        </div>
        
        <div class="actions">
          <button id="mCancel" class="btn alt" style="background:${isLightMode ? '#64748b' : '#23424e'};color:#ffffff">Cancel</button>
          <button id="mSave" class="btn" style="background:${isLightMode ? '#3b82f6' : '#155e75'};color:#ffffff">Save</button>
        </div>
      </div>`;
      overlay.addEventListener('click', ev=>{ if(ev.target===overlay) overlay.remove(); });
      shadow.appendChild(overlay);

      // Check stock status and update UI
      const checkStockStatus = async () => {
        if (!cardData?.sku) {
          return { inStock: false, currentStock: 0, currentPrice: 0 };
        }
        
        try {
          const response = await ajax('koriv2_check_stock', { sku: cardData.sku });
          
          // Handle nested response structure
          const stockData = response.data?.data || response.data || {};
          
          return {
            inStock: response.success && stockData.stock > 0,
            currentStock: stockData.stock || 0,
            currentPrice: stockData.price || 0
          };
        } catch (error) {
          return { inStock: false, currentStock: 0, currentPrice: 0 };
        }
      };

      checkStockStatus().then(stockInfo => {
        const stockInfoDiv = overlay.querySelector('#stockInfo');
        const priceInput = overlay.querySelector('#mPrice');
        const qtyInput = overlay.querySelector('#mQty');
        
        if (stockInfo.inStock) {
          stockInfoDiv.innerHTML = `
            <div style="color:#10b981;font-weight:600;margin-bottom:4px">✓ Already in Stock</div>
            <div style="font-size:12px;color:${isLightMode ? '#64748b' : '#9aa6b2'}">Current Stock: ${stockInfo.currentStock}</div>
            <div style="font-size:12px;color:${isLightMode ? '#64748b' : '#9aa6b2'}">Current Price: £${stockInfo.currentPrice}</div>
          `;
          priceInput.value = stockInfo.currentPrice;
          qtyInput.value = stockInfo.currentStock;
          qtyInput.setAttribute('data-original-stock', stockInfo.currentStock);
        } else {
          stockInfoDiv.innerHTML = `
            <div style="color:${isLightMode ? '#d97706' : '#f59e0b'};font-weight:600;margin-bottom:4px">⚠ Not in Stock</div>
            <div style="font-size:12px;color:${isLightMode ? '#64748b' : '#9aa6b2'}">This will create a new product listing</div>
          `;
        }
      });

      const $s = s=>overlay.querySelector(s);
      $s('#mCancel').onclick = ()=>overlay.remove();
      $s('#mDebug').onclick = async (e) => {
        e.preventDefault();
        try {
          // Fetch complete card data for eBay URL generation using search endpoint
          const response = await fetch(`${REST}cards/search-local?query=${encodeURIComponent(cardData.card_id)}&expandVariants=true`);
          if (response.ok) {
            const searchResults = await response.json();
            if (searchResults && searchResults.length > 0) {
              const fullCardData = searchResults[0]; // Get the first match
              
              // Get set information from the sets endpoint
              const setResponse = await fetch(`${REST}sets/local/full`);
              const sets = setResponse.ok ? await setResponse.json() : [];
              const setInfo = sets.find(s => s.set_id === fullCardData.set_id) || {};
              
              // Get variant definitions (same as sets/search views)
              const variantDefs = await getVariantDefinitions();
              
              const ebayUrl = buildEbayUrl(fullCardData, variantDefs, { 
                name: setInfo.name || fullCardData.set_name,
                total: setInfo.printed_total || setInfo.total || fullCardData.set_total
              });
              window.open(ebayUrl, '_blank');
            } else {
              // Fallback: try to build URL with available data
              const ebayUrl = buildEbayUrl(cardData, [], { name: cardData?.set_name });
              window.open(ebayUrl, '_blank');
            }
          } else {
            // Fallback: try to build URL with available data
            const ebayUrl = buildEbayUrl(cardData, [], { name: cardData?.set_name });
            window.open(ebayUrl, '_blank');
          }
        } catch (error) {
          console.error('Error fetching card data for debug:', error);
          // Fallback: try to build URL with available data
          const ebayUrl = buildEbayUrl(cardData, [], { name: cardData?.set_name });
          window.open(ebayUrl, '_blank');
        }
      };
      $s('#mSave').onclick = ()=>{
        const qty = parseInt($s('#mQty').value||'0',10); 
        const price = ($s('#mPrice').value||'').trim(); 
        const tablePrice = ($s('#mTablePrice').value||'').trim();
        
        if(!qty || qty<1){ showToast(shadow,'Enter a valid quantity'); return; }
        if(!price){ showToast(shadow,'Enter a valid price'); return; }
        
        const saveData = { 
          card_id, 
          quantity: qty, 
          price, 
          variant: variantLabel,
          table_price: tablePrice
        };
        
        ajax('koriv2_variant_upsert', saveData).then(()=>{
          overlay.remove(); showToast(shadow,'Saved');
        }).catch(()=>{ overlay.remove(); showToast(shadow,'Done'); });
      };
    }

  // ------- Router -------
  function route(shadow, stage){
    const p = (location.pathname||'').toLowerCase();
    if (p.includes('/koricollectiblesv2/sets'))      return viewSets(shadow, stage);
    if (p.includes('/koricollectiblesv2/pricecheck')) return viewPriceCheck(shadow, stage);
    if (p.includes('/koricollectiblesv2/dashboard')) return viewDashboard(shadow, stage);
    return viewSearch(shadow, stage);
  }

  // ------- Price Check View -------
  function viewPriceCheck(shadow, stage){
    setActive(shadow, 'pricecheck');
    
    stage.innerHTML = `
      <div class="view">
        <h2>Price Check</h2>
        <div style="margin-bottom:20px">
          <button id="btnRefreshPriceCheck" class="btn">Refresh Price Check</button>
        </div>
        <div id="priceCheckResults" class="grid pricecheck-grid" style="display:none"></div>
        <div id="priceCheckLoading" style="text-align:center;padding:40px;color:#9aa6b2">
          <div>Click "Refresh Price Check" to compare in-stock product prices with table prices</div>
          <div style="margin-top:8px;font-size:14px">Showing cards with £1+ price differences</div>
        </div>
      </div>
    `;
  }

  function wireDelegates(shadow, stage){
    shadow.addEventListener('click', ev=>{
      const m = ev.target.closest('.manage');
      if (m){ 
        ev.preventDefault(); 
        const cardData = m.getAttribute('data-card-data');
        openModal(shadow, m.getAttribute('data-card-id')||'', cardData ? JSON.parse(cardData) : null); 
      }
      const pill = ev.target.closest('.pill');
      if (pill){ setActive(shadow, pill.dataset.nav); }
      
      // Price Check button handler
      if (ev.target.id === 'btnRefreshPriceCheck') {
        ev.preventDefault();
        handlePriceCheck(shadow);
      }
    });
  }

  async function handlePriceCheck(shadow) {
    const resultsDiv = shadow.getElementById('priceCheckResults');
    const loadingDiv = shadow.getElementById('priceCheckLoading');
    
    if (!loadingDiv) {
      return; // Not on price check page
    }
    
    loadingDiv.innerHTML = '<div style="color:#78b7ff">Checking prices...</div>';
    
    try {
      const response = await ajax('koriv2_price_check');
      // Handle both array and object responses
      let data = response.data;
      if (response.data && response.data.data) {
        data = response.data.data;
      }
      
      if (response.success && data && Array.isArray(data)) {
        displayPriceCheckResults(shadow, data);
        loadingDiv.style.display = 'none';
        if (resultsDiv) resultsDiv.style.display = 'grid';
      } else {
        loadingDiv.innerHTML = '<div style="color:#f59e0b">No price differences found or error occurred</div>';
      }
    } catch (error) {
      loadingDiv.innerHTML = '<div style="color:#ef4444">Error checking prices</div>';
    }
  }

  function displayPriceCheckResults(shadow, data) {
    const container = shadow.getElementById('priceCheckResults');
    if (!container) return;
    
    if (!data.length) {
      container.innerHTML = '<div class="center muted" style="grid-column:1/-1">No products with significant price differences found</div>';
      return;
    }

    // Sort by price difference (largest differences first)
    data.sort((a, b) => {
      const diffA = Math.abs(a.current_price - a.table_price);
      const diffB = Math.abs(b.current_price - b.table_price);
      return diffB - diffA; // Descending order (largest first)
    });

    container.innerHTML = data.map(item => {
      const priceDiff = item.current_price - item.table_price;
      const diffClass = priceDiff > 0 ? 'price-higher' : 'price-lower';
      const diffIcon = priceDiff > 0 ? '↗' : '↘';
      
      // Prepare card data for modal
      const cardData = {
        card_id: item.sku,
        sku: item.sku,  // Add SKU for modal display
        name: item.product_name,
        variant: item.variant_name || 'Normal',
        current_price: item.current_price,
        table_price: item.table_price,  // Add table price for modal display
        stock_quantity: item.stock_quantity
      };
      
      return `
        <div class="tile">
          <div class="cardImgWrap">
            <img class="cardImg" src="${item.card_image || svgPH}" alt="${item.product_name}" loading="lazy">
          </div>
          <div class="cardDetails">
            <div class="cardName">${item.product_name}</div>
            <div class="cardSku">${item.sku}${item.variant_name && item.variant_name !== 'Default' ? ` - ${item.variant_name}` : ''}</div>
            <div class="priceInfo">
              <div class="price-row">
                <span class="price-label">Current:</span>
                <span class="price-value">£${item.current_price}</span>
              </div>
              <div class="price-row">
                <span class="price-label">Table:</span>
                <span class="price-value">£${item.table_price}</span>
              </div>
              <div class="price-row ${diffClass}">
                <span class="price-label">Diff:</span>
                <span class="price-value">${diffIcon} £${Math.abs(priceDiff).toFixed(2)}</span>
              </div>
              <div class="price-row">
                <span class="price-label">Stock:</span>
                <span class="price-value">${item.stock_quantity}</span>
              </div>
            </div>
            <div class="cardActions">
              <button class="btn manage" data-card-id="${item.sku}" data-card-data="${JSON.stringify(cardData).replace(/"/g, '&quot;')}" style="flex:1;font-size:12px;padding:6px 8px">Manage</button>
              <a href="${item.product_url}" target="_blank" class="btn alt" style="flex:1;font-size:12px;padding:6px 8px;text-align:center">View</a>
            </div>
          </div>
        </div>
      `;
    }).join('');
  }

  // ------- Dashboard (kept from your original) -------
  function viewDashboard(shadow, stage){
    setActive(shadow,'dashboard');
    stage.innerHTML = `
	  <div class="muted" style="margin-bottom:8px">Local-only actions (read from your DB):</div>
	  <div class="row" style="margin-bottom:12px">
		<button id="btnLocalSets" class="btn alt">REFRESH SETS (LOCAL)</button>
		<button id="btnLocalCards" class="btn alt">REFRESH CARDS INDEX (LOCAL)</button>
	  </div>
	  <div class="muted">API updates (optional; requires API key in settings):</div>
	  <div class="row mt">
		<button id="btnApiSets" class="btn">UPDATE SETS FROM API</button>
		<button id="btnApiCardsAll" class="btn">UPDATE MISSING CARDS (ALL SETS)</button>
	  </div>
	  <div id="apiStatus" class="muted mt"></div>
	  <div id="cardsStatus" class="muted mt"></div>

	  <!-- NEW: single-set updater -->
	  <div class="row mt" style="align-items:center">
		<select id="ddlOneSet" class="inp" style="flex:2" title="Choose a set…">
		  <option value="">Loading sets…</option>
		</select>
		<button id="btnApiCardsOneSet" class="btn" style="flex:1">UPDATE MISSING CARDS (ONE SET)</button>
	  </div>
	  <div id="oneSetStatus" class="muted mt"></div>

	  <div class="mt"></div>
	  <div class="muted" style="margin-top:16px">Pricing Updates (eBay scraping):</div>
	  <div class="row mt">
		<button id="btnUpdateAllPrices" class="btn" style="background:#4ade80;color:#000">UPDATE PRICING (ALL SETS)</button>
		<button id="btnKillScript" class="btn" style="background:#ef4444;color:#fff">KILL SCRIPT</button>
		<button id="btnTestScript" class="btn" style="background:#f59e0b;color:#fff">TEST PYTHON</button>
	  </div>
	  <div class="row mt" style="align-items:center">
		<select id="ddlPriceSet" class="inp" style="flex:2" title="Choose a set for pricing…">
		  <option value="">Loading sets…</option>
		</select>
		<button id="btnUpdateSelectedSetPrices" class="btn" style="flex:1;background:#4ade80;color:#000">UPDATE PRICING</button>
	  </div>
	  <div id="pricingStatus" class="muted mt"></div>
	  <div id="pricingProgress" class="muted mt" style="font-family:monospace;background:#1a1a1a;padding:8px;border-radius:4px;display:none"></div>
	  <div id="pricingOutput" class="muted mt" style="font-family:monospace;background:#0a0a0a;color:#00ff00;padding:12px;border-radius:4px;border:1px solid #333;max-height:400px;overflow-y:auto;display:none;white-space:pre-wrap;line-height:1.4"></div>

	  <div class="mt"></div>
	  <div class="muted" style="margin-top:16px">Images Management</div>
	  <div class="muted" style="margin-top:8px">Convert API images to local images:</div>
	  <div class="row mt">
		<button id="btnConvertCardImages" class="btn">Convert Card Images to Local</button>
		<button id="btnConvertSetImages" class="btn">Convert Set Images to Local</button>
	  </div>
	  <div class="row mt" style="align-items:center">
		<input id="imageSetId" class="inp" style="flex:2" placeholder="Optional: set_id for card images (e.g., sv9)">
		<button id="btnConvertCardImagesForSet" class="btn" style="flex:1">Convert Card Images for Set</button>
	  </div>
	  <div id="imageProgress" class="muted mt"></div>

	  <div class="mt"></div>
	  <div class="muted" style="margin-top:16px">Advanced: Tables</div>
	  <div class="row mt">
		<input id="tblSets" class="inp" placeholder="Sets table, e.g. wp_koripokemon_sets">
		<input id="tblCards" class="inp" placeholder="Cards table, e.g. wp_koripokemon_cards">
	  </div>
	  <div class="row mt">
		<button id="btnSaveTables" class="btn">SAVE</button>
	  </div>
	`;

    // Local counts
    shadow.getElementById('btnLocalSets').addEventListener('click', e=>{
      const b=e.currentTarget; const keep=b.textContent; b.disabled=true;
      ajax('koriv2_sets_count').then(r=>showToast(shadow, r?.success?`Found ${r.data.count} sets`:'Error'))
        .finally(()=>{b.disabled=false;b.textContent=keep;});
    });
    shadow.getElementById('btnLocalCards').addEventListener('click', e=>{
      const b=e.currentTarget; const keep=b.textContent; b.disabled=true;
      ajax('koriv2_cards_count').then(r=>showToast(shadow, r?.success?`Found ${r.data.count} cards`:'Error'))
        .finally(()=>{b.disabled=false;b.textContent=keep;});
    });

    // Prefill tables from REST if available
    (async ()=>{
      try {
        const r = await fetch((window.KORI_REST||'/wp-json/kori/v2/')+'config/tables', {credentials:'same-origin'});
        if (r.ok){ 
          const j = await r.json();
          if (j && (j.sets_table||j.cards_table)){
            const ts = shadow.getElementById('tblSets'); const tc = shadow.getElementById('tblCards');
            if (j.sets_table) ts.value = j.sets_table;
            if (j.cards_table) tc.value = j.cards_table;
          }
        }
      } catch(_){}
      const ts = shadow.getElementById('tblSets'); const tc = shadow.getElementById('tblCards');
      if (!ts.value) ts.value = 'wp_koripokemon_sets';
      if (!tc.value) tc.value = 'wp_koripokemon_cards';
    })();

    // Save tables
    shadow.getElementById('btnSaveTables').addEventListener('click', async ()=>{
      const ts = shadow.getElementById('tblSets').value.trim();
      const tc = shadow.getElementById('tblCards').value.trim();
      let ok=false;
      try{
        const r = await fetch((window.KORI_REST||'/wp-json/kori/v2/')+'config/tables', {
          method:'POST', credentials:'same-origin',
          headers:{'Content-Type':'application/json'},
          body: JSON.stringify({sets_table: ts, cards_table: tc})
        });
        ok = r.ok;
      }catch(_){}
      if(!ok){
        try{
          const body = new URLSearchParams({action:'koriv2_save_tables', sets_table: ts, cards_table: tc}).toString();
          const r2 = await fetch(window.KORI_AJAX || '/wp-admin/admin-ajax.php', {method:'POST', credentials:'same-origin', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body});
          const j2 = await r2.json(); ok = !!(j2 && (j2.success||j2.ok));
        }catch(_){}
      }
      showToast(shadow, ok?'Saved':'Could not save — please check permissions');
    });

    // ====== Image Conversion Event Handlers ======
    shadow.getElementById('btnConvertCardImages')?.addEventListener('click', async ()=>{
      const btn = shadow.getElementById('btnConvertCardImages');
      const progress = shadow.getElementById('imageProgress');
      const keep = btn.textContent;
      btn.disabled = true;
      progress.textContent = 'Starting card image conversion for all sets...';
      
      try {
        // Get all sets first
        const setsResponse = await fetch((window.KORI_REST||'/wp-json/kori/v2/')+'sets/local/full', {
          credentials: 'same-origin'
        });
        
        if (!setsResponse.ok) {
          throw new Error('Failed to fetch sets');
        }
        
        const sets = await setsResponse.json();
        let totalConverted = 0;
        let totalSkipped = 0;
        let totalErrors = 0;
        
        // Process each set one at a time
        for (let i = 0; i < sets.length; i++) {
          const set = sets[i];
          progress.textContent = `Processing set ${i + 1}/${sets.length}: ${set.name} (${set.set_id})...`;
          
          const response = await fetch((window.KORI_REST||'/wp-json/kori/v2/')+'images/convert-cards', {
            method: 'POST',
            credentials: 'same-origin',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({set_id: set.set_id})
          });
          
          if (response.ok) {
            const result = await response.json();
            totalConverted += result.converted || 0;
            totalSkipped += result.skipped || 0;
            totalErrors += result.errors || 0;
          } else {
            totalErrors++;
          }
        }
        
        progress.textContent = `Card image conversion complete! Converted: ${totalConverted}, Skipped: ${totalSkipped}, Errors: ${totalErrors}`;
        showToast(shadow, `Card images converted! Converted: ${totalConverted}, Skipped: ${totalSkipped}, Errors: ${totalErrors}`);
      } catch (error) {
        progress.textContent = 'Error: ' + error.message;
        showToast(shadow, 'Error converting card images');
      } finally {
        btn.disabled = false;
        btn.textContent = keep;
      }
    });

    shadow.getElementById('btnConvertSetImages')?.addEventListener('click', async ()=>{
      const btn = shadow.getElementById('btnConvertSetImages');
      const progress = shadow.getElementById('imageProgress');
      const keep = btn.textContent;
      btn.disabled = true;
      progress.textContent = 'Starting set image conversion...';
      
      try {
        const response = await fetch((window.KORI_REST||'/wp-json/kori/v2/')+'images/convert-sets', {
          method: 'POST',
          credentials: 'same-origin',
          headers: {'Content-Type': 'application/json'}
        });
        
        if (response.ok) {
          const result = await response.json();
          progress.textContent = result.message || 'Set images converted successfully!';
          showToast(shadow, 'Set images converted successfully!');
        } else {
          progress.textContent = 'Error converting set images';
          showToast(shadow, 'Error converting set images');
        }
      } catch (error) {
        progress.textContent = 'Error: ' + error.message;
        showToast(shadow, 'Error converting set images');
      } finally {
        btn.disabled = false;
        btn.textContent = keep;
      }
    });

    shadow.getElementById('btnConvertCardImagesForSet')?.addEventListener('click', async ()=>{
      const btn = shadow.getElementById('btnConvertCardImagesForSet');
      const progress = shadow.getElementById('imageProgress');
      const setIdInput = shadow.getElementById('imageSetId');
      const setId = setIdInput.value.trim();
      const keep = btn.textContent;
      
      if (!setId) {
        showToast(shadow, 'Please enter a set ID');
        return;
      }
      
      btn.disabled = true;
      progress.textContent = `Starting card image conversion for set: ${setId}...`;
      
      try {
        const response = await fetch((window.KORI_REST||'/wp-json/kori/v2/')+'images/convert-cards', {
          method: 'POST',
          credentials: 'same-origin',
          headers: {'Content-Type': 'application/json'},
          body: JSON.stringify({set_id: setId})
        });
        
        if (response.ok) {
          const result = await response.json();
          progress.textContent = result.message || `Card images converted for set: ${setId}`;
          showToast(shadow, `Card images converted for set: ${setId}`);
        } else {
          progress.textContent = 'Error converting card images for set';
          showToast(shadow, 'Error converting card images for set');
        }
      } catch (error) {
        progress.textContent = 'Error: ' + error.message;
        showToast(shadow, 'Error converting card images for set');
      } finally {
        btn.disabled = false;
        btn.textContent = keep;
      }
    });

    // ====== API: Update Sets from external API (with progress) ======
    (function(){
      const btn = shadow.getElementById('btnApiSets');
      const status = shadow.getElementById('apiStatus');
      btn?.addEventListener('click', async ()=>{
        if (!btn) return;
        const keep = btn.textContent;
        btn.disabled = true;
        let dots = 0;
        const tick = setInterval(()=>{ dots=(dots+1)%4; btn.textContent = 'Updating…' + '.'.repeat(dots); }, 300);
        status.textContent = 'Starting…';

        const REST_BASE = (window.KORI_REST||'/wp-json/kori/v2/');
        const AJAX_URL  = (window.KORI_AJAX || window.ajaxurl || '/wp-admin/admin-ajax.php');

        let ok=false, msg='Sets updated';

        // Try REST first
        try {
          const r = await fetch(REST_BASE+'sets/update-from-api', {method:'POST', credentials:'same-origin'});
          if (r.ok){
            const j = await r.json();
            ok = true;
            if (j && typeof j === 'object'){
              msg = j.message || j.summary || (`Updated ${j.updated||j.count||0} sets`);
            }
          }
        } catch(_){}

        // Fallback to admin-ajax
        if (!ok){
          try{
            const body = new URLSearchParams({action:'koriv2_update_sets_api'}).toString();
            const r2 = await fetch(AJAX_URL, {method:'POST', credentials:'same-origin', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body});
            const j2 = await r2.json();
            ok = !!(j2 && (j2.success||j2.ok));
            if (ok){
              const d = j2.data||{};
              msg = d.message || d.summary || (`Updated ${d.updated||d.count||0} sets`);
            }
          }catch(_){}
        }

        clearInterval(tick);
        btn.textContent = keep;
        btn.disabled = false;
        status.textContent = ok ? msg : 'Update failed — check server/API key.';
        showToast(shadow, ok? msg : 'Update failed');
      });
    })();
	
		// ====== API: Update MISSING cards (ALL sets) with local vs API comparison + report ======
	(function(){
	  const btn = shadow.getElementById('btnApiCardsAll');
	  const status = shadow.getElementById('cardsStatus');
	  if (!btn || !status) return;

	  async function safeFetch(url, opts={}, timeoutMs=180000){
		const ctl = new AbortController();
		const t = setTimeout(()=>ctl.abort(), timeoutMs);
		try{ return await fetch(url, {...opts, signal: ctl.signal}); }
		finally{ clearTimeout(t); }
	  }

	  async function fetchRemoteSets(){
		const headers = {'Accept':'application/json'};
		const key = (window.KORI_V2 && (KORI_V2.apiKey || KORI_V2.tcgApiKey || KORI_V2.PTCG_API_KEY)) || '';
		if (key) headers['X-Api-Key'] = key;
		const map = {};
		let page=1, pageSize=250, more=true, totalCount=null;
		while (more){
		  const r = await safeFetch(`https://api.pokemontcg.io/v2/sets?page=${page}&pageSize=${pageSize}`, {headers});
		  if (!r.ok) throw new Error('Remote sets fetch failed');
		  const j = await r.json();
		  const list = j?.data || j?.sets || [];
		  list.forEach(s=>{
			if (!s || !s.id) return;
			map[String(s.id).toLowerCase()] = Number(s.total || s.totalCards || s.printedTotal || 0) || 0;
		  });
		  totalCount = Number(j?.totalCount ?? j?.total ?? page*pageSize);
		  page++; more = (page-1)*pageSize < totalCount;
		}
		return map;
	  }

	  async function localCount(setId){
		const rows = await rest('sets/local/'+encodeURIComponent(setId)+'/cards?expandVariants=1');
		return Array.isArray(rows) ? rows.length : 0;
	  }

	  // NEW: use our real server endpoint (+ ajax fallback)
	  async function updateOneSet(setId){
		const REST_BASE = (window.KORI_REST||'/wp-json/kori/v2/');
		const AJAX_URL  = (window.KORI_AJAX || window.ajaxurl || '/wp-admin/admin-ajax.php');

		// Try REST route first
		try{
		  const r = await safeFetch(REST_BASE+'cards/update-missing-for-set', {
			method:'POST',
			credentials:'same-origin',
			headers:{'Content-Type':'application/json'},
			body: JSON.stringify({ set_id:setId })
		  }, 180000);
		  if (r.ok){ return await r.json(); }
		}catch(_){}

		// Fallback to admin-ajax
		try{
		  const body = new URLSearchParams({action:'koriv2_fill_missing_cards_set', set_id:setId}).toString();
		  const r = await safeFetch(AJAX_URL, {
			method:'POST',
			credentials:'same-origin',
			headers:{'Content-Type':'application/x-www-form-urlencoded'},
			body
		  }, 180000);
		  const j = await r.json();
		  if (j && (j.success || j.ok)) return j.data || j;
		}catch(_){}

		return {added:0};
	  }

	  btn.addEventListener('click', async ()=>{
		const keep = btn.textContent;
		btn.disabled = true;
		let dots=0;
		const tick = setInterval(()=>{ dots=(dots+1)%4; btn.textContent = 'Scanning…' + '.'.repeat(dots); }, 300);

		try{
		  status.textContent = 'Loading local sets…';
		  const sets = await rest('sets/local/full');
		  const ids = (Array.isArray(sets)?sets:[]).map(s=>s.set_id).filter(Boolean);
		  const meta = {};
		  (sets||[]).forEach(s=>{ meta[(s.set_id||'').toLowerCase()] = {name:s.name||'', code:s.code||''}; });

		  // Local counts
		  const local = {};
		  for (let i=0;i<ids.length;i++){
			const id = ids[i];
			status.textContent = `Counting local cards… ${i+1}/${ids.length} (${id})`;
			local[id.toLowerCase()] = await localCount(id);
		  }

		  status.textContent = 'Fetching remote set totals…';
		  const remote = await fetchRemoteSets();

		  // Work out missing sets
		  const workList = [];
		  for (const id of ids){
			const lid = id.toLowerCase();
			const lc = local[lid]||0;
			const rc = remote[lid]||0;
			if (rc>0 && lc<rc) workList.push({id, lc, rc, missing: rc-lc});
		  }
		  workList.sort((a,b)=> (b.missing-a.missing));

		  if (!workList.length){
			status.textContent = 'All sets look complete locally. Nothing to do.';
			localStorage.setItem('kori_last_missing_sets', JSON.stringify([]));
			return;
		  }

		  // Import per set
		  let totalAdded = 0;
		  const report = [];
		  for (let i=0;i<workList.length;i++){
			const w = workList[i];
			status.textContent = `Checking missing cards for set ${w.id} (${i+1}/${workList.length}) — local ${w.lc} / api ${w.rc}…`;
			const resp = await updateOneSet(w.id);
			const added = Number(
			  (resp && (resp.added ?? resp.new ?? resp.created ?? resp.inserted ?? (resp.stats && (resp.stats.added||resp.stats.new)))) || 0
			) || 0;
			totalAdded += added;
			const metaRow = meta[w.id.toLowerCase()] || {};
			report.push({ id:w.id, name:metaRow.name||'', code:metaRow.code||'', local:w.lc, api:w.rc, added });
			status.textContent = `Done ${w.id} (+${added}). ${i+1}/${workList.length}.`;
		  }

		  localStorage.setItem('kori_last_missing_sets', JSON.stringify(report));
		  const details = report.map(r=>`<li><strong>${r.id}</strong>${r.name?` — ${r.name}`:''} (${r.local} → ${r.api}, +${r.added})</li>`).join('');
		  status.innerHTML = `Finished. ${workList.length} set(s) had missing cards. Added <strong>${totalAdded}</strong> cards in total.
			<div class="mt"></div><div class="muted">Details:</div>
			<ul style="margin:6px 0 0 18px">${details || '<li>No changes</li>'}</ul>`;
		  showToast(shadow, `Added ${totalAdded} new cards across ${workList.length} set(s).`);
		} catch(err){
		  console.error(err);
		  status.textContent = 'Update failed — see console.';
		  showToast(shadow, 'Update failed');
		} finally {
		  clearInterval(tick);
		  btn.disabled = false;
		  btn.textContent = keep;
		}
	  });
	})();

/* ===== Single-set dropdown: populate options ===== */
(function(){
  const ddl = document.querySelector('#kori-v2-root')?.shadowRoot?.getElementById('ddlOneSet');
  if (!ddl) return;
  (async ()=>{
    try{
      const sets = await rest('sets/local/full');
      const opts = ['<option value="">Choose a set…</option>']
        .concat((sets||[]).map(s=>{
          const id = s.set_id || '';
          const code = s.code ? ` (${s.code})` : '';
          const name = s.name || '';
          return `<option value="${id}">${id}${code} — ${name}</option>`;
        }));
      ddl.innerHTML = opts.join('');
      const last = localStorage.getItem('kori_one_set_choice') || '';
      if (last && ddl.querySelector(`option[value="${last}"]`)) ddl.value = last;
    }catch(_){
      ddl.innerHTML = '<option value="">Failed to load sets</option>';
    }
  })();
})();

	/* ===== Single-set updater: call server endpoint with progress ===== */
	(function(){
	  const root = document.querySelector('#kori-v2-root');
	  const shadow = root ? root.shadowRoot : null;
	  if (!shadow) return;
	  const btn    = shadow.getElementById('btnApiCardsOneSet');
	  const ddl    = shadow.getElementById('ddlOneSet');
	  const status = shadow.getElementById('oneSetStatus');
	  if (!btn || !ddl || !status) return;

	  async function safeFetch(url, opts={}, timeoutMs=180000){
		const ctl = new AbortController();
		const t = setTimeout(()=>ctl.abort(), timeoutMs);
		try { return await fetch(url, {...opts, signal: ctl.signal}); }
		finally { clearTimeout(t); }
	  }

	  async function updateOneSet(setId){
		const REST_BASE = (window.KORI_REST||'/wp-json/kori/v2/');
		const AJAX_URL  = (window.KORI_AJAX || window.ajaxurl || '/wp-admin/admin-ajax.php');

		try{
		  const r = await safeFetch(REST_BASE+'cards/update-missing-for-set', {
			method:'POST',
			credentials:'same-origin',
			headers:{'Content-Type':'application/json'},
			body: JSON.stringify({ set_id:setId })
		  }, 180000);
		  if (r.ok) return await r.json();
		}catch(_){}

		try{
		  const body = new URLSearchParams({action:'koriv2_fill_missing_cards_set', set_id:setId}).toString();
		  const r = await safeFetch(AJAX_URL, {
			method:'POST',
			credentials:'same-origin',
			headers:{'Content-Type':'application/x-www-form-urlencoded'},
			body
		  }, 180000);
		  const j = await r.json();
          // eslint-disable-next-line no-unsafe-optional-chaining
		  if (j && (j.success || j.ok)) return j.data || j;
		}catch(_){}

		return {added:0};
	  }

	  btn.addEventListener('click', async ()=>{
		const setId = ddl.value.trim();
		if (!setId){ status.textContent = 'Please choose a set first.'; ddl.focus(); return; }

		localStorage.setItem('kori_one_set_choice', setId);

		const keep = btn.textContent;
		btn.disabled = true;
		let dots=0;
		const tick = setInterval(()=>{ dots=(dots+1)%4; btn.textContent = 'Updating…' + '.'.repeat(dots); }, 300);
		status.textContent = `Fetching missing cards for ${setId}…`;

		try{
		  const res = await updateOneSet(setId);
		  const added = Number(res?.added || res?.new || res?.inserted || 0) || 0;
		  const scanned = Number(res?.scanned || 0);
		  const total = Number(res?.total || 0);
		  status.textContent = `Done ${setId}: +${added} (scanned ${scanned}${total?` / ${total}`:''}).`;
		  showToast(shadow, `Set ${setId}: added ${added} card(s).`);
		}catch(err){
		  console.error(err);
		  status.textContent = `Failed to update ${setId}. See console.`;
		  showToast(shadow, 'Update failed');
		}finally{
		  clearInterval(tick);
		  btn.disabled = false;
		  btn.textContent = keep;
		}
	  });
	})();



    /* ===== Variants (server-backed, AJAX save) ===== */
    (function(){
      const root = document.querySelector('#kori-v2-root');
      const shadow = root ? root.shadowRoot : null;
      if (!shadow) return;
      const stage = shadow.getElementById('stage');
      if (!stage) return;

      const wrap = document.createElement('div');
      wrap.innerHTML = `
        <div class="mt"></div>
        <div class="muted" style="margin-top:16px">Variants</div>
        <div id="variantsPanel" class="tile" style="padding:12px">
          <div class="grid" style="grid-template-columns: 1fr 160px 2fr 80px; gap:8px; align-items:center">
            <div class="muted">Variant</div>
            <div class="muted">Suffix</div>
            <div class="muted">eBay query</div>
            <div></div>
            <div id="varRows" style="grid-column:1/-1"></div>
          </div>
          <div class="row mt" style="gap:8px">
            <button id="btnAddVar" class="btn alt">Add row</button>
            <button id="btnSaveVars" class="btn">Save</button>
          </div>
          <div class="muted" style="margin-top:8px">Saved to WordPress via admin-ajax; loaded via REST (no cache).</div>
        </div>
      `;
      stage.appendChild(wrap);

      const VAR_LS_KEY = 'kori_variant_defs_v1';
      const REST_BASE = (window.KORI_REST||'/wp-json/kori/v2/');
      const AJAX_URL = (window.KORI_AJAX || window.ajaxurl || '/wp-admin/admin-ajax.php');

      const loadLocal = () => { try { return JSON.parse(localStorage.getItem(VAR_LS_KEY) || '[]'); } catch(e){ return []; } };
      const saveLocal = defs => localStorage.setItem(VAR_LS_KEY, JSON.stringify(defs));

      async function getDefs(){
        try {
          const r = await fetch(REST_BASE+'variants/defs?cb='+Date.now(), {credentials:'same-origin'});
          if (!r.ok) throw 0;
          return await r.json();
        } catch(_) {
          return loadLocal();
        }
      }
      async function postDefs(defs){
        const body = new URLSearchParams({action:'koriv2_save_variant_defs', defs: JSON.stringify(defs)}).toString();
        const r2 = await fetch(AJAX_URL, {
          method:'POST', credentials:'same-origin',
          headers:{'Content-Type':'application/x-www-form-urlencoded'},
          body
        });
        const j2 = await r2.json();
        if (j2 && (j2.success || j2.ok)) return {ok:true, saved:(j2.data && j2.data.saved) ? j2.data.saved : defs};
        throw new Error('AJAX save failed');
      }

      function renderRows(defs){
        const rowsEl = shadow.getElementById('varRows');
        rowsEl.innerHTML = '';
        defs.forEach((d, idx) => {
          const row = document.createElement('div');
          row.className = 'grid';
          row.style.gridTemplateColumns = '1fr 160px 2fr 80px';
          row.style.gap = '8px';
          row.style.alignItems = 'center';
          row.dataset.index = idx;
          row.innerHTML = `
            <input class="inp" data-k="name"   value="${(d.name||'').replace(/"/g,'&quot;')}"   placeholder="e.g. Reverse Holo">
            <input class="inp" data-k="suffix" value="${(d.suffix||'').replace(/"/g,'&quot;')}" placeholder="RH">
            <input class="inp" data-k="query"  value="${(d.query||'').replace(/"/g,'&quot;')}"  placeholder="e.g. - (proxy, custom, fake, choose, japanese)">
            <button class="btn alt" data-act="del">Delete</button>
          `;
          rowsEl.appendChild(row);
        });
      }

      let defs = [];
      (async ()=>{
        defs = await getDefs();
        if (!Array.isArray(defs)) defs = [];
        defs = defs.map(d => ({
          name:(d.name||'').trim(),
          suffix:(d.suffix||'').trim().toUpperCase(),
          query:(d.query||'').trim()
        }));
        renderRows(defs);
        try{ window.KORI_VARIANT_DEFS = defs.slice(); }catch(_){}
      })();

      shadow.getElementById('btnAddVar')?.addEventListener('click', () => {
        defs.push({ name:'', suffix:'', query:'' });
        renderRows(defs);
      });

      shadow.getElementById('variantsPanel')?.addEventListener('click', (e) => {
        const btn = e.target.closest('[data-act=del]');
        if (!btn) return;
        const row = btn.closest('[data-index]');
        const idx = parseInt(row?.dataset.index || '-1', 10);
        if (idx >= 0) {
          defs.splice(idx, 1);
          renderRows(defs);
        }
      });

      shadow.getElementById('variantsPanel')?.addEventListener('input', (e) => {
        const row = e.target.closest('[data-index]');
        if (!row) return;
        const idx = parseInt(row.dataset.index || '-1', 10);
        if (idx < 0) return;
        const k = e.target.dataset.k;
        if (!k) return;
        defs[idx][k] = (e.target.value || '').trim();
        if (k === 'suffix') defs[idx][k] = defs[idx][k].toUpperCase();
      });

      shadow.getElementById('btnSaveVars')?.addEventListener('click', async () => {
        const cleaned = defs
          .map(d => ({ name:d.name.trim(), suffix:d.suffix.trim().toUpperCase(), query:d.query.trim() }))
          .filter(d => d.name);
        try {
          const res = await postDefs(cleaned);
          if (res?.ok) {
            saveLocal(res.saved || cleaned);
            try{ window.KORI_VARIANT_DEFS = (res.saved || cleaned).slice(); }catch(_){}
            showToast(shadow, 'Saved');
          } else {
            throw 0;
          }
        } catch(_){
          showToast(shadow, 'Save failed — are you logged in as admin?');
        }
      });
    })();

    // ====== Pricing Updates: eBay scraping with progress ======
    (function(){
      const btnAll = shadow.getElementById('btnUpdateAllPrices');
      const btnSelected = shadow.getElementById('btnUpdateSelectedSetPrices');
      const btnKill = shadow.getElementById('btnKillScript');
      const btnTest = shadow.getElementById('btnTestScript');
      const ddlPriceSet = shadow.getElementById('ddlPriceSet');
      const status = shadow.getElementById('pricingStatus');
      const progress = shadow.getElementById('pricingProgress');
      const output = shadow.getElementById('pricingOutput');
      
      if (!btnAll || !btnSelected || !btnKill || !btnTest || !ddlPriceSet || !status || !progress) return;

      // Populate pricing set dropdown
      (async ()=>{
        try{
          const sets = await rest('sets/local/full');
          const opts = ['<option value="">Choose a set for pricing…</option>']
            .concat((sets||[]).map(s=>{
              const id = s.set_id || '';
              const code = s.code ? ` (${s.code})` : '';
              const name = s.name || '';
              return `<option value="${id}">${id}${code} — ${name}</option>`;
            }));
          ddlPriceSet.innerHTML = opts.join('');
        }catch(_){
          ddlPriceSet.innerHTML = '<option value="">Failed to load sets</option>';
        }
      })();

      async function updatePricingForSet(setId, isAllSets = false) {
        const REST_BASE = (window.KORI_REST||'/wp-json/kori/v2/');
        const AJAX_URL = (window.KORI_AJAX || window.ajaxurl || '/wp-admin/admin-ajax.php');
        
        try {
          // Try REST endpoint first
          const r = await fetch(REST_BASE + 'pricing/update-ebay', {
            method: 'POST',
            credentials: 'same-origin',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({ 
              set_id: setId,
              all_sets: isAllSets,
              progress_callback: true
            })
          });
          
          if (r.ok) {
            const result = await r.json();
            return result;
          }
        } catch(_) {}
        
        // Fallback to admin-ajax
        try {
          const body = new URLSearchParams({
            action: 'koriv2_update_ebay_pricing',
            set_id: setId,
            all_sets: isAllSets ? '1' : '0'
          }).toString();
          
          const r = await fetch(AJAX_URL, {
            method: 'POST',
            credentials: 'same-origin',
            headers: {'Content-Type': 'application/x-www-form-urlencoded'},
            body
          });
          
          const j = await r.json();
          if (j && (j.success || j.ok)) return j.data || j;
        } catch(_) {}
        
        return {success: false, message: 'Failed to update pricing'};
      }

      function showProgress(message) {
        progress.style.display = 'block';
        progress.textContent = message;
      }

      function hideProgress() {
        progress.style.display = 'none';
      }

      function showOutput() {
        output.style.display = 'block';
        output.textContent = '';
      }

      function hideOutput() {
        output.style.display = 'none';
      }

      function appendOutput(text) {
        output.textContent += text + '\n';
        output.scrollTop = output.scrollHeight;
      }
      
      function updateCurrentCard(cardInfo) {
        // Clear the output and show only the current card being processed
        output.textContent = cardInfo;
        output.scrollTop = output.scrollHeight;
      }

      async function runPythonScript(setId, allSets) {
        const REST_BASE = (window.KORI_REST||'/wp-json/kori/v2/');
        
        appendOutput('🚀 Starting Python script execution...');
        appendOutput(`Set ID: ${setId}`);
        appendOutput(`All sets: ${allSets}`);
        appendOutput('');
        
        try {
          // Start the script
          const response = await fetch(REST_BASE + 'pricing/update', {
            method: 'POST',
            credentials: 'same-origin',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({ 
              set_id: setId,
              all_sets: allSets
            })
          });
          
          if (response.ok) {
            const result = await response.json();
            
            if (result.success) {
              if (result.is_running) {
                // Script is already running, show current progress
                appendOutput('⚠️ Script is already running!');
                appendOutput('Showing current progress...');
                appendOutput('');
                
                // Show any existing output
                if (result.output) {
                  appendOutput('Current output:');
                  appendOutput(result.output);
                  appendOutput('');
                }
                
                // Start monitoring the existing script
                await monitorScriptProgress();
                
                return { success: true, message: 'Monitoring existing script' };
              } else {
                // New script started
                appendOutput('✅ Script started in background!');
                appendOutput('');
                appendOutput('Command executed:');
                appendOutput(result.command);
                appendOutput('');
                appendOutput('Monitoring progress...');
                appendOutput('');
                
                // Start monitoring the log file
                await monitorScriptProgress();
                
                return { success: true, message: 'Script completed' };
              }
            } else {
              appendOutput(`❌ ${result.message}`);
              return { success: false, message: result.message };
            }
          } else {
            appendOutput(`❌ Error: ${response.status} ${response.statusText}`);
            return { success: false, message: 'Script failed' };
          }
        } catch(error) {
          appendOutput(`❌ Network error: ${error.message}`);
          return { success: false, message: 'Network error' };
        }
      }
      
      async function monitorScriptProgress() {
        const REST_BASE = (window.KORI_REST||'/wp-json/kori/v2/');
        let lastOutputLength = 0;
        let isRunning = true;
        let currentCardInfo = '';
        let currentVariants = [];
        let currentVariantCount = 0;
        
        while (isRunning) {
          try {
            const response = await fetch(REST_BASE + 'pricing/progress', {
              method: 'GET',
              credentials: 'same-origin'
            });
            
            if (response.ok) {
              const result = await response.json();
              
              if (result.success && result.output) {
                // Only process new content
                const currentOutput = result.output;
                if (currentOutput.length > lastOutputLength) {
                  const newContent = currentOutput.substring(lastOutputLength);
                  
                  // Parse for card progress updates
                  const lines = newContent.split('\n');
                  for (const line of lines) {
                    if (line.trim()) {
                      // Check if this is a card processing line
                      const cardMatch = line.match(/\[(\d+)\/(\d+)\] Processing card: (.+)/);
                      if (cardMatch) {
                        const [, current, total, cardId] = cardMatch;
                        currentCardInfo = `[${current}/${total}] Processing card: ${cardId}`;
                        currentVariants = [];
                        currentVariantCount = 0;
                        updateCurrentCardDisplay();
                        continue;
                      }
                      
                      // Check if this is a variants line
                      const variantsMatch = line.match(/Variants: (\d+)/);
                      if (variantsMatch) {
                        currentVariantCount = parseInt(variantsMatch[1]);
                        updateCurrentCardDisplay();
                        continue;
                      }
                      
                      // Check if this is a variant processing line
                      const variantMatch = line.match(/\[(\d+)\/(\d+)\] Variant: (.+)/);
                      if (variantMatch) {
                        const [, vCurrent, vTotal, variantName] = variantMatch;
                        const variantIndex = parseInt(vCurrent) - 1;
                        
                        // Initialize variants array if needed
                        if (currentVariants.length < vTotal) {
                          currentVariants = new Array(parseInt(vTotal)).fill(null);
                        }
                        
                        currentVariants[variantIndex] = {
                          name: variantName,
                          cardName: '',
                          price: ''
                        };
                        updateCurrentCardDisplay();
                        continue;
                      }
                      
                      // Check if this is a card name line
                      const cardNameMatch = line.match(/Card: (.+)/);
                      if (cardNameMatch) {
                        const cardName = cardNameMatch[1];
                        // Add to the most recent variant
                        if (currentVariants.length > 0) {
                          const lastVariantIndex = currentVariants.findLastIndex(v => v !== null);
                          if (lastVariantIndex >= 0) {
                            currentVariants[lastVariantIndex].cardName = cardName;
                          }
                        }
                        updateCurrentCardDisplay();
                        continue;
                      }
                      
                      // Check for price information
                      const priceMatch = line.match(/Average price: £([\d.]+)/);
                      if (priceMatch) {
                        const price = priceMatch[1];
                        // Add to the most recent variant
                        if (currentVariants.length > 0) {
                          const lastVariantIndex = currentVariants.findLastIndex(v => v !== null);
                          if (lastVariantIndex >= 0) {
                            currentVariants[lastVariantIndex].price = price;
                          }
                        }
                        updateCurrentCardDisplay();
                        continue;
                      }
                    }
                  }
                  
                  lastOutputLength = currentOutput.length;
                }
                
                // Check if script is still running
                if (!result.is_running) {
                  updateCurrentCard('✅ Script completed successfully!');
                  isRunning = false;
                }
              } else {
                updateCurrentCard('⏳ Waiting for script to start...');
              }
            } else {
              updateCurrentCard('❌ Error checking progress');
              isRunning = false;
            }
          } catch(error) {
            updateCurrentCard(`❌ Error monitoring progress: ${error.message}`);
            isRunning = false;
          }
          
          // Wait 1 second before checking again for more responsive updates
          if (isRunning) {
            await new Promise(resolve => setTimeout(resolve, 1000));
          }
        }
        
        function updateCurrentCardDisplay() {
          let displayText = '';
          if (currentCardInfo) displayText += currentCardInfo;
          if (currentVariantCount > 0) displayText += ` | Variants: ${currentVariantCount}`;
          displayText += '\n';
          
          // Show all variants for the current card
          currentVariants.forEach((variant, index) => {
            if (variant) {
              displayText += `[${index + 1}/${currentVariantCount}] ${variant.name}`;
              if (variant.cardName) {
                displayText += ` | ${variant.cardName}`;
              }
              if (variant.price) {
                displayText += ` | £${variant.price}`;
              }
              displayText += '\n';
            }
          });
          
          
          updateCurrentCard(displayText);
        }
      }

      // Global state for continuous pricing updates
      let isContinuousMode = false;
      let continuousInterval = null;
      let currentCycle = 0;
      let progressInterval = null;

      // Check if continuous pricing is already running on page load
      async function checkContinuousStatus() {
        try {
          const response = await fetch((window.KORI_REST||'/wp-json/kori/v2/') + 'pricing/status', {
            method: 'GET',
            credentials: 'same-origin'
          });
          
          if (response.ok) {
            const result = await response.json();
            console.log('Initial status check - Status data:', result);
            
            if (result.is_running && result.is_continuous) {
              // Continuous mode is running on server
              isContinuousMode = true;
              btnAll.textContent = 'Stop Continuous Update';
              btnAll.style.background = '#dc2626';
              currentCycle = result.current_cycle || 1;
              
              // Add progress information if available
              let progressText = '';
              if (result.progress_text && result.total_cards > 0) {
                progressText = ` - ${result.progress_text}`;
              }
              
              console.log('Initial progress text:', progressText);
              status.textContent = `Continuous pricing update is running (Cycle ${currentCycle})${progressText}`;
              appendOutput('');
              appendOutput(`🔄 Continuous pricing update is already running on server (Cycle ${currentCycle})${progressText}`);
              
              // Start progress updates every 5 seconds
              if (progressInterval) clearInterval(progressInterval);
              progressInterval = setInterval(updateProgressDisplay, 5000);
            }
          }
        } catch (error) {
          console.log('Could not check pricing status:', error);
        }
      }

      // Function to update progress display
      async function updateProgressDisplay() {
        if (!isContinuousMode) return;
        
        try {
          const response = await fetch((window.KORI_REST||'/wp-json/kori/v2/') + 'pricing/status', {
            method: 'GET',
            credentials: 'same-origin'
          });
          
          if (response.ok) {
            const result = await response.json();
            console.log('Progress update - Status data:', result);
            
            if (result.is_running && result.is_continuous) {
              let progressText = '';
              if (result.progress_text && result.total_cards > 0) {
                progressText = ` - ${result.progress_text}`;
              }
              
              console.log('Progress text:', progressText);
              status.textContent = `Continuous pricing update is running (Cycle ${result.current_cycle || 1})${progressText}`;
            } else {
              // Continuous mode stopped
              isContinuousMode = false;
              status.textContent = '';
              if (progressInterval) {
                clearInterval(progressInterval);
                progressInterval = null;
              }
            }
          }
        } catch (error) {
          console.log('Could not update progress:', error);
        }
      }

      // Test Python script functionality
      async function testPythonScript() {
        try {
          const response = await fetch((window.KORI_REST||'/wp-json/kori/v2/') + 'pricing/test-script', {
            method: 'GET',
            credentials: 'same-origin'
          });
          
          if (response.ok) {
            const result = await response.json();
            console.log('Python script test result:', result);
            appendOutput('');
            appendOutput('🔍 Python Script Test Results:');
            appendOutput(`Script exists: ${result.script_exists}`);
            appendOutput(`Script path: ${result.script_path}`);
            appendOutput(`WordPress URL: ${result.wp_url}`);
            appendOutput(`Python version: ${result.python_version}`);
            appendOutput(`Python return code: ${result.python_return_code}`);
          }
        } catch (error) {
          console.log('Could not test Python script:', error);
          appendOutput('');
          appendOutput('❌ Could not test Python script: ' + error.message);
        }
      }

      // Check status on page load
      checkContinuousStatus();

      // Update all sets pricing with toggle functionality
      btnAll.addEventListener('click', async () => {
        if (isContinuousMode) {
          // Stop continuous mode via server
          try {
            const response = await fetch((window.KORI_REST||'/wp-json/kori/v2/') + 'pricing/stop-continuous', {
              method: 'POST',
              credentials: 'same-origin',
              headers: {'Content-Type': 'application/json'}
            });
            
            if (response.ok) {
              const result = await response.json();
              if (result.success) {
                isContinuousMode = false;
                btnAll.textContent = 'Update Pricing (All Sets)';
                btnAll.style.background = '#155e75';
                status.textContent = 'Continuous pricing update stopped';
                showToast(shadow, 'Continuous pricing update stopped');
                appendOutput('');
                appendOutput('🛑 Continuous pricing update stopped');
                
                // Clear progress interval
                if (progressInterval) {
                  clearInterval(progressInterval);
                  progressInterval = null;
                }
              } else {
                showToast(shadow, 'Failed to stop continuous update');
                appendOutput('');
                appendOutput('❌ Failed to stop continuous update: ' + result.message);
              }
            }
          } catch (error) {
            showToast(shadow, 'Error stopping continuous update');
            appendOutput('');
            appendOutput('❌ Error stopping continuous update: ' + error.message);
          }
          return;
        }

        // Start continuous mode via server
        try {
          const response = await fetch((window.KORI_REST||'/wp-json/kori/v2/') + 'pricing/start-continuous', {
            method: 'POST',
            credentials: 'same-origin',
            headers: {'Content-Type': 'application/json'}
          });
          
          if (response.ok) {
            const result = await response.json();
            if (result.success) {
              isContinuousMode = true;
              btnAll.textContent = 'Stop Continuous Update';
              btnAll.style.background = '#dc2626';
              status.textContent = 'Continuous pricing update started on server';
              showToast(shadow, 'Continuous pricing update started');
              appendOutput('');
              appendOutput('🔄 Continuous pricing update started on server');
              appendOutput('The script will run in the background and restart automatically');
              
              // Start progress updates every 5 seconds
              if (progressInterval) clearInterval(progressInterval);
              progressInterval = setInterval(updateProgressDisplay, 5000);
            } else {
              showToast(shadow, 'Failed to start continuous update');
              appendOutput('');
              appendOutput('❌ Failed to start continuous update: ' + result.message);
            }
          }
        } catch (error) {
          showToast(shadow, 'Error starting continuous update');
          appendOutput('');
          appendOutput('❌ Error starting continuous update: ' + error.message);
        }
      });



      // Update selected set pricing
      btnSelected.addEventListener('click', async () => {
        const setId = ddlPriceSet.value;
        if (!setId) {
          showToast(shadow, 'Please select a set first');
          return;
        }
        
        const keep = btnSelected.textContent;
        btnSelected.disabled = true;
        status.textContent = `Starting pricing update for set: ${setId}`;
        showOutput();
        appendOutput(`Starting eBay pricing update for set: ${setId}`);
        appendOutput('');
        
        try {
          // Get set info for progress display
          const sets = await rest('sets/local/full');
          const setInfo = sets.find(s => s.set_id === setId);
          const setName = setInfo ? setInfo.name : setId;
          const totalCards = setInfo ? (setInfo.total || setInfo.printed_total || 0) : 0;
          
          appendOutput(`Set: ${setName} (${setId})`);
          appendOutput(`Total cards: ${totalCards}`);
          appendOutput('');
          
          // Run Python script for this specific set
          const result = await runPythonScript(setId, false);
          
          if (result.success) {
            const updatedCards = result.updated_cards || result.data?.updated_cards || 0;
            status.textContent = `Pricing update completed for ${setName}! Updated ${updatedCards} cards.`;
            showToast(shadow, `Pricing update completed for ${setName}!`);
            appendOutput('');
            appendOutput('✅ Pricing update completed successfully!');
            appendOutput(`Updated ${updatedCards} cards in ${setName}.`);
          } else {
            status.textContent = `Pricing update failed for ${setName}: ${result.message || 'Unknown error'}`;
            showToast(shadow, 'Pricing update failed');
            appendOutput('');
            appendOutput('❌ Pricing update failed: ' + (result.message || 'Unknown error'));
          }
        } catch (err) {
          console.error('❌ Single set pricing update error:', err);
          status.textContent = 'Pricing update failed — see console.';
          showToast(shadow, 'Pricing update failed');
          appendOutput('');
          appendOutput('❌ Pricing update failed: ' + err.message);
        } finally {
          btnSelected.disabled = false;
          btnSelected.textContent = keep;
        }
      });

      // Kill script button
      btnKill.addEventListener('click', async () => {
        // First stop continuous mode if it's running
        if (isContinuousMode) {
          try {
            const response = await fetch((window.KORI_REST||'/wp-json/kori/v2/') + 'pricing/stop-continuous', {
              method: 'POST',
              credentials: 'same-origin',
              headers: {'Content-Type': 'application/json'}
            });
            
            if (response.ok) {
              const result = await response.json();
              if (result.success) {
                isContinuousMode = false;
                btnAll.textContent = 'Update Pricing (All Sets)';
                btnAll.style.background = '#155e75';
                status.textContent = '';
                appendOutput('');
                appendOutput('🛑 Continuous mode stopped by kill script');
                
                // Clear progress interval
                if (progressInterval) {
                  clearInterval(progressInterval);
                  progressInterval = null;
                }
              }
            }
          } catch (error) {
            console.log('Could not stop continuous mode:', error);
          }
        }
        
        const keep = btnKill.textContent;
        btnKill.disabled = true;
        btnKill.textContent = 'KILLING...';
        
        try {
          const response = await fetch((window.KORI_REST||'/wp-json/kori/v2/') + 'pricing/kill', {
            method: 'POST',
            credentials: 'same-origin',
            headers: {'Content-Type': 'application/json'}
          });
          
          if (response.ok) {
            const result = await response.json();
            if (result.success) {
              status.textContent = 'Script killed successfully';
              showToast(shadow, 'Script killed successfully');
              appendOutput('');
              appendOutput('🛑 Script killed successfully');
            } else {
              status.textContent = `Failed to kill script: ${result.message}`;
              showToast(shadow, 'Failed to kill script');
              appendOutput('');
              appendOutput(`❌ Failed to kill script: ${result.message}`);
            }
          } else {
            status.textContent = 'Error killing script';
            showToast(shadow, 'Error killing script');
            appendOutput('');
            appendOutput('❌ Error killing script');
          }
        } catch (error) {
          status.textContent = 'Error killing script';
          showToast(shadow, 'Error killing script');
          appendOutput('');
          appendOutput(`❌ Error killing script: ${error.message}`);
        } finally {
          btnKill.disabled = false;
          btnKill.textContent = keep;
        }
      });

      // Test Python script button
      btnTest.addEventListener('click', async () => {
        const keep = btnTest.textContent;
        btnTest.disabled = true;
        btnTest.textContent = 'TESTING...';
        
        try {
          await testPythonScript();
        } catch (error) {
          appendOutput('');
          appendOutput('❌ Test failed: ' + error.message);
        } finally {
          btnTest.disabled = false;
          btnTest.textContent = keep;
        }
      });
    })();
  }

  function sizeSymbols(shadow){
    // keep callable if needed later
  }

  // ------- Boot -------
  function boot(){
    const mounted = mountRoot(); if (!mounted) return;
    const {shadow, stage} = mounted;
    route(shadow, stage);
    wireDelegates(shadow, stage);

    window.addEventListener('popstate', ()=>route(shadow, stage));

    const mo = new MutationObserver(()=>{
      const root = document.getElementById('kori-v2-root');
      if (!root){ boot(); }
    });
    mo.observe(document.body, {childList:true, subtree:true});
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', boot);
  } else {
    boot();
  }
})();
