// ============================================================================
// StyleKro — SHOPWISE module (split from app.jsx on 2026-06-26).
// Shop browse/detail/settings + seller manage + buyer/seller garment detail and
// the shared category form-field components (TaxonomySelect, CategoryExtraFields,
// ...). Reuses shopcat/* registries. Depends on common.jsx (loaded first).
// ============================================================================
// ===== Shops browse page =====
function ShopsPage() {
  const { apiCall, t } = useAuth();
  const [shops, setShops] = useState([]);
  const [q, setQ] = useState('');
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');
  const [filterCat, setFilterCat] = useState(''); // Fashion Category key
  const [filterLoc, setFilterLoc] = useState(''); // city/location filter
  const [minPieces, setMinPieces] = useState(0); // 0 = any
  const [filtersOpen, setFiltersOpen] = useState(false);
  const [locating, setLocating] = useState(false);
  const [nearMeNote, setNearMeNote] = useState('');
  useEffect(() => {
    document.body.classList.toggle('filters-open', filtersOpen);
    return () => document.body.classList.remove('filters-open');
  }, [filtersOpen]);

  const load = useCallback(async (query) => {
    setLoading(true);
    setError('');
    try {
      const path = query ? `/shops?q=${encodeURIComponent(query)}` : '/shops';
      const list = await apiCall(path);
      setShops(list || []);
    } catch (e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  }, [apiCall]);

  useEffect(() => { load(''); }, [load]);

  // Detect the user's city via geolocation + Google reverse-geocode and
  // auto-pick the matching LOCATION filter chip. Falls back to a hint if no
  // shop matches the detected area.
  async function findNearMe() {
    setNearMeNote('');
    if (!navigator.geolocation) {
      setNearMeNote('Geolocation is not supported by this browser.');
      return;
    }
    setLocating(true);
    try {
      const pos = await new Promise((resolve, reject) => {
        navigator.geolocation.getCurrentPosition(resolve, reject, {
          enableHighAccuracy: true,
          timeout: 12000,
          maximumAge: 60000,
        });
      });
      const { latitude: lat, longitude: lng } = pos.coords;
      const google = await loadGoogleMaps();
      const geocoder = new google.maps.Geocoder();
      const results = await new Promise((resolve, reject) => {
        geocoder.geocode({ location: { lat, lng } }, (r, status) => {
          if (status === 'OK' && r && r[0]) resolve(r); else reject(new Error(status));
        });
      });
      // Pull candidate place names: locality (city), sublocality, area.
      const tokens = new Set();
      for (const res of results) {
        for (const c of res.address_components || []) {
          if ((c.types || []).some((t) => ['locality', 'sublocality', 'administrative_area_level_2', 'administrative_area_level_1'].includes(t))) {
            if (c.long_name) tokens.add(c.long_name.toLowerCase());
            if (c.short_name) tokens.add(c.short_name.toLowerCase());
          }
        }
      }
      // Find the best matching shop location bucket.
      const locKeys = Object.keys(locCounts);
      let match = '';
      for (const k of locKeys) {
        const lk = k.toLowerCase();
        for (const tok of tokens) {
          if (lk.includes(tok) || tok.includes(lk)) { match = k; break; }
        }
        if (match) break;
      }
      if (match) {
        setFilterLoc(match);
        setNearMeNote(`Showing shops in ${match}.`);
      } else {
        const detected = Array.from(tokens)[0] || `${lat.toFixed(3)}, ${lng.toFixed(3)}`;
        setNearMeNote(`No shops match your area (${detected}).`);
      }
    } catch (err) {
      const msg = err && err.code === 1
        ? 'Permission denied. Allow location access to use "Near me".'
        : (err && err.message) || 'Could not get your location.';
      setNearMeNote(msg);
    } finally {
      setLocating(false);
    }
  }

  function onSubmit(e) {
    e.preventDefault();
    load(q.trim());
  }

  // Detect a category from the shop name + description so we can show a badge.
  // The shop owner's explicit ``shop.category`` takes priority; the keyword
  // matcher is only a fallback for legacy shops that haven't set one.
  function categoryFor(s) {
    const cats = [
      { k: 'garment', label: 'Fashion', match: /garment|cloth|dress|shirt|tshirt|jean|trouser|kurta|saree|lehenga|jacket|coat|sweater|hoodie|blazer|skirt|outfit|apparel/ },
      { k: 'tailor', label: 'Tailor', match: /tailor|tailoring|bespoke|custom stitch|fabric|textile|design/ },
      { k: 'salon', label: 'Salon', match: /hair|salon|saloon|barber|haircut|wig|extensions|makeup|cosmetic|lipstick|beauty|mua|nail art|nailart|manicure|pedicure/ },
      { k: 'tile', label: 'Tile & Paint', match: /tile|paint|emulsion|distemper|flooring|granite|ceramic|porcelain|vitrified|stone/ },
      { k: 'tattoo', label: 'Tattoo', match: /tattoo|ink|body art/ },
      { k: 'jewelry', label: 'Jewelry', match: /jewel|necklace|earring|bracelet|ring|pendant/ },
      { k: 'eyewear', label: 'Eyewear', match: /glasses|sunglass|spectacle|optical|frames|eyewear/ },
      { k: 'watch', label: 'Watches', match: /watch|timepiece|wristwatch/ },
    ];
    const labelMap = Object.fromEntries(cats.map((c) => [c.k, c.label]));
    if (s.category) {
      const k = String(s.category).toLowerCase();
      return { k, label: labelMap[k] || (k.charAt(0).toUpperCase() + k.slice(1)) };
    }
    const t = `${s.name || ''} ${s.description || ''}`.toLowerCase();
    for (const c of cats) if (c.match.test(t)) return { k: c.k, label: c.label };
    return { k: 'fashion', label: 'Fashion' };
  }

  function avatarFor(s) {
    return (s.name || '?').trim().charAt(0).toUpperCase();
  }

  const totalPieces = shops.reduce((n, s) => n + (s.garment_count || 0), 0);

  // Build the Fashion Category filter chip list from the loaded shops.
  const catCounts = shops.reduce((m, s) => {
    const c = categoryFor(s);
    if (!m.has(c.k)) m.set(c.k, { label: c.label, count: 0 });
    m.get(c.k).count += 1;
    return m;
  }, new Map());
  const catChips = Array.from(catCounts.entries())
    .map(([k, v]) => ({ k, label: v.label, count: v.count }))
    .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label));

  let displayed = shops.filter((s) => {
    if (filterCat && categoryFor(s).k !== filterCat) return false;
    if (filterLoc && (s.location || '').trim() !== filterLoc) return false;
    if (minPieces > 0 && (s.garment_count || 0) < minPieces) return false;
    return true;
  });

  // Location counts (for the rail)
  const locCounts = makeCountMap(shops, (s) => (s.location || '').trim(), { skipEmpty: true });

  const anyShopFilter = !!filterCat || !!filterLoc || minPieces > 0 || !!q;

  return (
    <section className="screen">
      <NavBar active="shops" />
      <main className="page-wrap shops-page shops-v2">
        {error && <p className="error">{error}</p>}
        {loading ? (
          <div className="shop-grid">
            {[0, 1, 2, 3, 4, 5].map((i) => (
              <div key={i} className="shop-card-skel">
                <div className="skel-line" style={{ width: '60%' }} />
                <div className="skel-line" style={{ width: '40%' }} />
                <div className="skel-line" style={{ width: '90%' }} />
                <div className="skel-line" style={{ width: '80%' }} />
              </div>
            ))}
          </div>
        ) : shops.length === 0 ? (
          <div className="looks-empty">
            <div className="looks-empty-icon">🛍</div>
            <h3>{t('shops.noShopsFound')}</h3>
            <p className="muted">
              {q
                ? t('shops.tryDifferent')
                : t('shops.beFirst')}
            </p>
            <div className="looks-empty-actions">
              {q ? (
                <button className="btn btn-ghost" onClick={() => { setQ(''); load(''); }}>{t('shops.clearSearch')}</button>
              ) : (
                <Link to="/shop/manage" className="btn btn-primary">{t('shop.open')} →</Link>
              )}
            </div>
          </div>
        ) : (
          <div className="inv-layout">
            <aside className={`inv-rail${filtersOpen ? ' open' : ''}`}>
              <div className="rail-head">
                <span className="rail-title">{t('common.filters')}</span>
                {anyShopFilter && (
                  <button type="button" className="rail-clear" onClick={() => { setFilterCat(''); setFilterLoc(''); setMinPieces(0); setQ(''); load(''); }}>{t('common.clearAll')}</button>
                )}
              </div>

              {catChips.length > 0 && (
                <details className="rail-section" open>
                  <summary className="rail-section-title">{t('shops.categoryRail')}</summary>
                  <label className="rail-opt">
                    <input type="radio" name="sf-cat" checked={!filterCat} onChange={() => setFilterCat('')} />
                    <span>{t('common.all')} <span className="rail-count">({shops.length})</span></span>
                  </label>
                  {catChips.map((c) => (
                    <label key={c.k} className="rail-opt">
                      <input type="radio" name="sf-cat" checked={filterCat === c.k} onChange={() => setFilterCat(c.k)} />
                      <span>{c.label} <span className="rail-count">({c.count})</span></span>
                    </label>
                  ))}
                </details>
              )}


              <details className="rail-section" open>
                <summary className="rail-section-title">{t('shops.inventoryRail')}</summary>
                {[
                  { v: 0, label: t('shops.anySize') },
                  { v: 1, label: t('shops.onePlus') },
                  { v: 5, label: t('shops.fivePlus') },
                  { v: 20, label: t('shops.twentyPlus') },
                ].map((o) => (
                  <label key={o.v} className="rail-opt">
                    <input type="radio" name="sf-min" checked={minPieces === o.v} onChange={() => setMinPieces(o.v)} />
                    <span>{o.label}</span>
                  </label>
                ))}
              </details>
              <div className="rail-footer">
                <button type="button" className="btn-primary rail-apply-btn" onClick={() => setFiltersOpen(false)}>{t('common.apply')}</button>
              </div>
            </aside>
            {filtersOpen && <div className="filters-backdrop show" onClick={() => setFiltersOpen(false)} />}

            <div className="inv-content">
              <div className="inv-toolbar looks-toolbar-v3">
                <div className="looks-toolbar-head">
                </div>

                <div className="looks-toolbar-row looks-search-row">
                  <form className="looks-search" onSubmit={onSubmit}>
                    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
                    <input
                      type="search"
                      placeholder={t('shops.searchPlaceholder')}
                      value={q}
                      onChange={(e) => setQ(e.target.value)}
                    />
                  </form>
                  <button
                    type="button"
                    className={`looks-filter-iconbtn${anyShopFilter ? ' has-active' : ''}`}
                    onClick={() => setFiltersOpen(true)}
                    aria-label={t('common.openFilters')}
                    title={t('common.filters')}
                  >
                    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><line x1="4" y1="6" x2="20" y2="6"/><line x1="7" y1="12" x2="17" y2="12"/><line x1="10" y1="18" x2="14" y2="18"/></svg>
                    {anyShopFilter && <span className="looks-filter-dot" />}
                  </button>
                </div>
              </div>

              <div className="shop-grid">
                {displayed.length === 0 ? (
                  <div className="looks-empty" style={{ gridColumn: '1 / -1' }}>
                    <div className="looks-empty-icon">🛍</div>
                    <h3>{t('shops.noMatch')}</h3>
                    <p className="muted">{t('shops.tryDifferentFilter')}</p>
                    <div className="looks-empty-actions">
                      <button className="btn btn-ghost" onClick={() => { setFilterCat(''); setFilterLoc(''); setMinPieces(0); }}>{t('shops.clearFilters')}</button>
                    </div>
                  </div>
                ) : displayed.map((s) => {
                  const cat = categoryFor(s);
                  const locShort = s.location ? (() => {
                    const words = s.location.trim().split(/\s+/);
                    return words.length > 3 ? words.slice(0, 3).join(' ') + '…' : s.location;
                  })() : '';
                  return (
                    <Link key={s.id} to={`/shops/${s.id}`} className={`shop-card shop-card-v2 cat-${cat.k}`}>
                      <div
                        className={`scv2-banner cat-${cat.k}${s.has_banner ? ' has-photo' : ''}`}
                        style={s.has_banner ? { backgroundImage: `url(${API}/shops/${s.id}/banner)` } : undefined}
                      >
                        <div className="scv2-logo">{avatarFor(s)}</div>
                      </div>
                      <div className="scv2-content">
                        <div className="scv2-top">
                          <h3 className="scv2-name">{s.name}</h3>
                        </div>
                        <span className={`scv2-cat cat-${cat.k}`}>{cat.label}</span>
                        {s.location && (
                          <div className="scv2-loc" title={s.location}>📍 {locShort}</div>
                        )}
                        {s.description && <p className="scv2-desc">{s.description}</p>}
                        <div className="scv2-bottom">
                          <span className="scv2-pieces">{s.garment_count} piece{s.garment_count === 1 ? '' : 's'}</span>
                          <span className="scv2-cta">{t('shops.explore')}</span>
                        </div>
                      </div>
                    </Link>
                  );
                })}
              </div>
            </div>
          </div>
        )}
      </main>
    </section>
  );
}

// ===== Shop detail page =====

// Buyer-facing tattoo placement chips for the inline studio. Selecting a limb
// part + the "wrap" toggle produces an armband-style instruction; otherwise a
// flat placement. The chosen phrase is sent as the try-on `note` (which the
// backend treats as a high-priority user instruction, overriding the seller's
// per-design default). `auto` sends no note so the seller default / base
// prompt decide.
// Instruction sent with the composited tattoo image: the design has already
// been positioned/scaled/rotated onto the photo client-side, so the AI only
// needs to render it as believable ink at that exact spot.
const TATTOO_COMPOSITE_NOTE = 'A tattoo design has been digitally overlaid onto this photo at the exact position, size and rotation chosen by the user. Render it as real ink on the skin at that same location and scale: follow the body\'s contours and perspective, match the skin\'s lighting and shading, remove any white or transparent background from the design, and blend the edges naturally so it looks like a real tattoo. Do not move, resize, rotate, or duplicate it, and do not change the person otherwise.';

function ShopDetailPage() {
  const { id } = useParams();
  const { apiCall, token, profile, ready: authReady, t, photoVersion } = useAuth();
  const ensureProfilePhoto = useRequireProfilePhoto();
  const navigate = useNavigate();
  const [shop, setShop] = useState(null);
  const [garments, setGarments] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');
  const [selected, setSelected] = useState(() => new Set());
  const [generating, setGenerating] = useState(false);
  const [search, setSearch] = useState('');
  const [tailorDesignSearch, setTailorDesignSearch] = useState('');
  const [tailorFabricSearch, setTailorFabricSearch] = useState('');
  const [filterGroup, setFilterGroup] = useState('');
  const [filterSub, setFilterSub] = useState('');
  // results: map of garment_id -> { status: 'pending'|'done'|'error', image_url?, error? }
  const [results, setResults] = useState({});
  // garment_ids currently flipped to the "Original" face. Click toggles.
  const [flipped, setFlipped] = useState(() => new Set());
  const [linkCopied, setLinkCopied] = useState(false);
  // Combined-look result (when user selects multiple items and tries them together)
  const [combinedResult, setCombinedResult] = useState(null); // { status, image_url?, error?, titles }
  const [shopExpanded, setShopExpanded] = useState(false);
  // Filter sheet (replaces the inline category rows)
  const [filterOpen, setFilterOpen] = useState(false);
  // Tailor: individual garment pieces the buyer wants stitched. Empty means
  // stitch the selected design as shown.
  const [tailorPieceKeys, setTailorPieceKeys] = useState([]);
  const [tailorFabricAssignments, setTailorFabricAssignments] = useState({});
  const [activeTailorFabricSlot, setActiveTailorFabricSlot] = useState('');
  const [tailorPiecesExpanded, setTailorPiecesExpanded] = useState(false);
  const [tailorAnimatingId, setTailorAnimatingId] = useState(null);
  const [tailorSetupPulse, setTailorSetupPulse] = useState('');
  const [tailorSetupFlipped, setTailorSetupFlipped] = useState(false);
  const tailorSetupRef = useRef(null);
  const tailorDesignsRef = useRef(null);
  const tailorFabricsRef = useRef(null);
  // Lightbox URL for the fullscreen zoom view.
  const [lightbox, setLightbox] = useState(null);
  const [sheet360MsgId, setSheet360MsgId] = useState(null);
  // When true, the 360° spin renders INLINE inside the tile/tattoo studio
  // canvas (instead of the popup modal), mirroring the buyer-item hero 360.
  const [studio360, setStudio360] = useState(false);
  const fetchCombinedSheet360 = useCallback(
    () => fetchSheet360(
      `/chat/messages/${sheet360MsgId}/sheet360` +
      (shop?.category ? `?category=${encodeURIComponent(shop.category)}` : ''),
    ),
    [sheet360MsgId, shop],
  );
  function openStudio360(messageId) {
    if (!messageId) return;
    setSheet360MsgId(messageId);
    setStudio360(true);
  }
  function closeStudio360() {
    setStudio360(false);
    setSheet360MsgId(null);
  }
  const isTailorShop = (shop?.category || '').toLowerCase() === 'tailor';
  const isTileShop = (shop?.category || '').toLowerCase() === 'tile';
  const isTattooShop = (shop?.category || '').toLowerCase() === 'tattoo';
  // Garment-piece options this tailor shop offers. If the seller has not
  // configured any, fall back to the full master list (keeps old shops working).
  const tailorPieceChoices = (() => {
    const gender = (shop?.shop_gender || '').toLowerCase();
    let base = TAILOR_PIECE_OPTIONS;
    if (gender === 'men' || gender === 'women') {
      const g = base.filter((o) => (o.group || 'men') === gender);
      if (g.length) base = g;
    }
    const cfg = Array.isArray(shop?.tailor_pieces) ? shop.tailor_pieces : [];
    if (!cfg.length) return base;
    const filtered = base.filter((o) => cfg.includes(o.value));
    return filtered.length ? filtered : base;
  })();
  const tailorSlotChoices = (() => {
    const seen = new Set();
    const out = [];
    for (const opt of tailorPieceChoices) {
      for (const slot of tailorSlotsForOption(opt)) {
        if (!seen.has(slot.key)) {
          seen.add(slot.key);
          out.push(slot);
        }
      }
    }
    return out;
  })();
  const selectedTailorSlots = tailorSlotChoices.filter((slot) => tailorPieceKeys.includes(slot.key));
  const selectedTailorPreset = tailorPresetForSlots(tailorPieceKeys);
  const selectedTailorLabel = selectedTailorSlots.length ? selectedTailorSlots.map((s) => s.label).join(' + ') : '';
  const tailorPrimarySlots = tailorSlotChoices.filter((slot) => TAILOR_PRIMARY_SLOT_KEYS.includes(slot.key));
  const tailorMoreSlots = tailorSlotChoices.filter((slot) => !TAILOR_PRIMARY_SLOT_KEYS.includes(slot.key));
  const visibleTailorSlots = tailorPiecesExpanded ? [...tailorPrimarySlots, ...tailorMoreSlots] : tailorPrimarySlots;
  const tailorMoreSelected = tailorMoreSlots.some((slot) => tailorPieceKeys.includes(slot.key));
  useEffect(() => {
    if (!isTailorShop) return;
    const allowed = new Set(tailorSlotChoices.map((slot) => slot.key));
    setTailorPieceKeys((cur) => cur.filter((key) => allowed.has(key)));
    setTailorFabricAssignments((cur) => Object.fromEntries(Object.entries(cur).filter(([key]) => allowed.has(key))));
    setActiveTailorFabricSlot((cur) => (allowed.has(cur) ? cur : ''));
  }, [isTailorShop, shop?.id, Array.isArray(shop?.tailor_pieces) ? shop.tailor_pieces.join('|') : '']);
  const garmentKind = (g) => g?.asset_kind || shopMeta(shop).defaultAssetKind;

  const [tileRoomPrompt, setTileRoomPrompt] = useState(null);
  // Defined in web/shops/tile.js; keep the same name for local readability.
  const TILE_ROOM_SURFACES = (_ShopCategories?.byKey?.tile?.ROOM_SURFACES) || [];
  // Inline StyleKro Room studio (tile/paint shops): assign a material to each
  // room surface, then generate one styled-room image — no popup.
  const [tileAssignments, setTileAssignments] = useState({});
  const [activeTileSurface, setActiveTileSurface] = useState('floor');
  const [tileRoomSearch, setTileRoomSearch] = useState('');
  const [tileCat, setTileCat] = useState('All');
  const [tileFavs, setTileFavs] = useState(() => new Set());
  // Track WHICH room-photo src has finished loading rather than a boolean that
  // a passive effect resets to false: a freshly captured camera photo becomes a
  // data: URL that can fire <img> onLoad before a passive reset effect flushes,
  // which on phones left the "Loading room photo..." pill stuck forever. Deriving
  // the loading state from the loaded src removes that ordering race entirely.
  const [loadedRoomSrc, setLoadedRoomSrc] = useState(null);
  // One-shot fresh room snapshot captured from the device camera. When set,
  // it overrides the user's stored profile photo for the tile-room preview.
  const [cameraRoomPhoto, setCameraRoomPhoto] = useState(null);
  const roomCameraRef = useRef(null);
  const roomCanvasRef = useRef(null);
  // Scroll the room canvas into view whenever a new tile-room styling starts
  // so the user sees the generated image without manual scrolling.
  useEffect(() => {
    if ((combinedResult?.kind === 'tile-room' || combinedResult?.kind === 'tattoo') && combinedResult?.status === 'pending') {
      roomCanvasRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }
  }, [combinedResult?.kind, combinedResult?.status]);
  function selectTileSurfaceAndScroll(surface) {
    setActiveTileSurface(surface);
    window.setTimeout(() => {
      const picker = document.querySelector('.shop-detail-v2 .trs-picker-v2');
      if (picker) picker.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }, 0);
  }
  function assignTileToSurface(gid) {
    setTileAssignments((cur) => {
      if (cur[activeTileSurface] === gid) {
        const next = { ...cur };
        delete next[activeTileSurface];
        return next;
      }
      return { ...cur, [activeTileSurface]: gid };
    });
  }

  // Tailor multi-piece outfit: assign one of the selected fabrics to each
  // garment piece (coat/pant/shirt…) before generating ONE combined outfit.
  // { design, slots: [{key,label}], fabrics: Garment[], assignments: {slotKey: fabricId}, error }
  const [pieceFabricPrompt, setPieceFabricPrompt] = useState(null);

  // Tattoo studio — inline canvas (no popup). Uses the buyer's profile photo by
  // default; an uploaded camera/gallery photo overrides it for this try-on.
  // The photo is sent as a multipart file (never base64-in-JSON).
  const [tattooDesignId, setTattooDesignId] = useState(null);
  // Optional uploaded photo: upload-ready Blob in a ref + an object URL for the
  // on-screen preview. Clearing it falls back to the profile photo.
  const [tattooPhotoUrl, setTattooPhotoUrl] = useState(null);
  const tattooPhotoFileRef = useRef(null);
  const tattooFileInputRef = useRef(null);
  // Interactive overlay editor: the selected tattoo is shown on the photo and
  // the buyer can drag (move), handle-resize and rotate it. The chosen
  // transform is baked into the image sent to the AI on generate.
  //   xPct/yPct = centre position as % of the 4:5 canvas box
  //   wPct      = tattoo width as % of the canvas width
  //   rot       = rotation in degrees
  const [tatXf, setTatXf] = useState({ xPct: 50, yPct: 42, wPct: 34, rot: 0 });
  const tatXfRef = useRef(tatXf);
  const setTat = (next) => setTatXf((p) => {
    const n = typeof next === 'function' ? next(p) : next;
    tatXfRef.current = n;
    return n;
  });
  const tatCanvasRef = useRef(null);
  const tatGesture = useRef(null);
  const tatNatRef = useRef({ w: 1, h: 1 });

  async function promptTileRoom(picks) {
    if (!picks || picks.length === 0) return;
    if (!(await ensureProfilePhoto())) return;
    const assignments = {};
    TILE_ROOM_SURFACES.forEach((surface, idx) => {
      assignments[surface.value] = picks[idx]?.id || (idx === 0 ? picks[0]?.id || '' : '');
    });
    setTileRoomPrompt({ picks, assignments, error: '' });
  }

  function tileRoomTitle(picks, assignments) {
    const byId = Object.fromEntries(picks.map((g) => [g.id, g]));
    return TILE_ROOM_SURFACES.map((surface) => {
      const selectedId = assignments[surface.value];
      const item = selectedId ? byId[selectedId] : null;
      return item ? `${surface.label}: ${item.title || 'Item'}` : null;
    }).filter(Boolean).join(' · ');
  }

  const shareLink = shop
    ? `${window.location.protocol}//${window.location.host}${window.location.pathname}#/shops/${shop.id}`
    : '';

  async function copyShareLink() {
    if (!shareLink) return;
    try {
      await navigator.clipboard.writeText(shareLink);
      setLinkCopied(true);
      setTimeout(() => setLinkCopied(false), 1800);
    } catch {
      try {
        const ta = document.createElement('textarea');
        ta.value = shareLink; ta.style.position = 'fixed'; ta.style.opacity = '0';
        document.body.appendChild(ta); ta.select();
        document.execCommand('copy');
        document.body.removeChild(ta);
        setLinkCopied(true);
        setTimeout(() => setLinkCopied(false), 1800);
      } catch {}
    }
  }

  async function shareShop() {
    if (!shareLink) return;
    const data = {
      title: shop?.name ? `${shop.name} · StyleKro` : 'StyleKro Shop',
      text: shop?.name
        ? `Explore ${shop.name} on StyleKro and try on any piece with AI before you buy.`
        : 'Explore this shop on StyleKro and try on any piece with AI before you buy.',
      url: shareLink,
    };
    if (navigator.share) {
      try { await navigator.share(data); return; } catch {}
    }
    copyShareLink();
  }

  useEffect(() => {
    let cancelled = false;
    (async () => {
      setLoading(true);
      try {
        const [s, g] = await Promise.all([
          apiCall(`/shops/${id}`),
          apiCall(`/shops/${id}/garments`),
        ]);
        if (cancelled) return;
        setShop(s);
        setGarments(sortByCreatedDesc(g || []));
      } catch (e) {
        if (!cancelled) setError(e.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [id, apiCall]);

  // Restore previously-saved try-ons so the user can come back and see
  // any looks they generated in earlier sessions. Also restores the
  // spinner for any tryon still in flight (e.g. user refreshed mid-generation).
  useEffect(() => {
    if (!token) return;
    let cancelled = false;
    const fetchPrev = async () => {
      try {
        const prev = await apiCall(`/shops/${id}/my-tryons`);
        if (cancelled || !prev) return null;
        setResults((cur) => {
          const next = { ...cur };
          for (const [gid, info] of Object.entries(prev)) {
            const existing = next[gid];
            const hasImage = info && info.image_url;
            const isPending = info && info.pending;
            // Don't overwrite an in-flight local stream that's already further along.
            if (existing && existing.status === 'done') continue;
            if (existing && existing.status === 'fast' && !hasImage) continue;
            if (isPending && hasImage) {
              // FAST image is ready but SLOW is still cooking — show partial + refining hint.
              next[gid] = {
                status: 'fast',
                image_url: imgUrl(info.image_url),
                saved: true,
                merge_id: info.merge_id || null,
                message_id: info.message_id || (existing && existing.message_id) || null,
              };
            } else if (isPending) {
              next[gid] = { status: 'pending', message_id: info.message_id };
            } else if (hasImage) {
              next[gid] = { status: 'done', image_url: imgUrl(info.image_url), saved: true, merge_id: info.merge_id || null };
            }
          }
          // If a card was marked pending locally but the server no longer
          // reports it (e.g. job aged out or was abandoned), drop the spinner.
          for (const [gid, existing] of Object.entries(next)) {
            if (existing && existing.status === 'pending' && !prev[gid]) {
              delete next[gid];
            }
          }
          return next;
        });
        return prev;
      } catch (_e) { return null; }
    };
    let pollId = null;
    const startPolling = () => {
      if (pollId) return;
      pollId = setInterval(async () => {
        const prev = await fetchPrev();
        if (cancelled) return;
        const stillPending = prev && Object.values(prev).some((info) => info && info.pending);
        if (!stillPending && pollId) { clearInterval(pollId); pollId = null; }
      }, 4000);
    };
    (async () => {
      const prev = await fetchPrev();
      if (cancelled || !prev) return;
      const hasPending = Object.values(prev).some((info) => info && info.pending);
      if (hasPending) startPolling();
    })();
    return () => { cancelled = true; if (pollId) clearInterval(pollId); };
  }, [id, token, apiCall]);

  function scrollToTailorRef(ref) {
    ref?.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
  }

  function pulseTailorSetup(kind) {
    setTailorSetupPulse(kind);
    window.setTimeout(() => setTailorSetupPulse(''), 900);
  }

  function toggle(gid) {
    const garment = garments.find((g) => g.id === gid);
    if (!garment) return;
    const kind = garmentKind(garment);
    const selecting = !selected.has(gid);
    if (isTailorShop && selecting && (kind === 'design' || kind === 'fabric')) {
      setTailorAnimatingId(gid);
      window.setTimeout(() => setTailorAnimatingId(null), 650);
      pulseTailorSetup(kind);
      window.setTimeout(() => scrollToTailorRef(tailorSetupRef), 220);
    }
    setSelected((prev) => {
      const next = new Set(prev);
      if (!isTailorShop) {
        if (next.has(gid)) next.delete(gid); else next.add(gid);
        return next;
      }
      if (kind === 'design') {
        const wasSelected = next.has(gid);
        for (const id of Array.from(next)) {
          const picked = garments.find((item) => item.id === id);
          if (garmentKind(picked) === 'design') next.delete(id);
        }
        if (!wasSelected) next.add(gid);
        return next;
      }
      if (kind === 'fabric' && selectedTailorSlots.length > 0) {
        const slotKey = activeTailorFabricSlot || selectedTailorSlots.find((slot) => !tailorFabricAssignments[slot.key])?.key || selectedTailorSlots[0].key;
        const prevFabricId = tailorFabricAssignments[slotKey];
        const nextAssignments = { ...tailorFabricAssignments, [slotKey]: gid };
        setTailorFabricAssignments(nextAssignments);
        if (prevFabricId && prevFabricId !== gid && !Object.entries(nextAssignments).some(([key, value]) => key !== slotKey && value === prevFabricId)) {
          next.delete(prevFabricId);
        }
        next.add(gid);
        setActiveTailorFabricSlot('');
        return next;
      }
      if (next.has(gid)) next.delete(gid); else next.add(gid);
      return next;
    });
  }

  function clearSelection() { setSelected(new Set()); }

  async function runTryon(picks, opts = {}) {
    if (!picks || picks.length === 0) return;
    const { note } = opts;
    // TEMP DIAGNOSTIC: tag which handler fired so the backend log can name it.
    const bodyJson = JSON.stringify(note ? { note, _diag: { _src: 'generic' } } : { _diag: { _src: 'generic' } });
    const extraHeaders = { 'Content-Type': 'application/json' };
    setError('');
    setResults((cur) => {
      const next = { ...cur };
      for (const g of picks) next[g.id] = { status: 'pending' };
      return next;
    });
    await Promise.all(picks.map((g) => _StyleKroGen.runTryon({
      streamSSE,
      url: `/garments/${g.id}/tryon/stream`,
      token,
      body: bodyJson,
      headers: extraHeaders,
      onUpdate: ({ status, image_url, pending_text, message_id, error }) => {
        setResults((cur) => {
          if (status === 'error') return { ...cur, [g.id]: { status: 'error', error } };
          const prev = cur[g.id] || {};
          const next = { ...prev, status };
          if (pending_text !== undefined) next.pending_text = pending_text;
          next.message_id = message_id || prev.message_id;
          if (image_url !== undefined) next.image_url = imgUrl(image_url);
          return { ...cur, [g.id]: next };
        });
      },
    })));
  }

  async function generateCombined() {
    if (selected.size === 0 || generating) return;
    const picks = garments.filter((g) => selected.has(g.id));
    setGenerating(true);
    const titles = picks.map((g) => g.title).join(', ');
    setCombinedResult({ status: 'pending', titles, kind: 'combine' });
    await _StyleKroGen.runTryon({
      streamSSE,
      url: '/garments/tryon-multi/stream',
      token,
      body: JSON.stringify({ garment_ids: picks.map((g) => g.id) }),
      headers: { 'Content-Type': 'application/json' },
      onUpdate: ({ status, image_url, pending_text, message_id, error }) => {
        setCombinedResult((cur) => {
          if (status === 'error') return { status: 'error', titles, kind: 'combine', error };
          const next = { status, titles, kind: 'combine' };
          if (pending_text !== undefined) next.pending_text = pending_text;
          if (image_url !== undefined) next.image_url = imgUrl(image_url);
          next.message_id = message_id || cur?.message_id;
          return next;
        });
      },
    });
    setGenerating(false);
    clearSelection();
  }

  async function confirmTileRoomTryon(autoAssign = false, picksOverride = null, assignmentsOverride = null) {
    const inline = !!picksOverride;
    const picks = picksOverride || tileRoomPrompt?.picks;
    const assignments = assignmentsOverride || (!inline && tileRoomPrompt?.assignments) || {};
    if (!picks || picks.length === 0) return;
    if (inline && !(await ensureProfilePhoto())) return;
    const placements = TILE_ROOM_SURFACES.map((surface) => ({
      surface: surface.value,
      garment_id: assignments[surface.value],
    })).filter((placement) => placement.garment_id);
    if (!autoAssign && placements.length === 0) {
      if (inline) setError('Add a tile to at least one surface to style the room.');
      else setTileRoomPrompt((cur) => cur ? { ...cur, error: 'Choose at least one room surface to style.' } : cur);
      return;
    }
    if (autoAssign && picks.length > 6) {
      if (inline) setError('Select up to 6 materials to style a room.');
      else setTileRoomPrompt((cur) => cur ? { ...cur, error: 'AI choose works with up to 6 selected materials.' } : cur);
      return;
    }
    const titles = autoAssign
      ? (picks.map((g) => g.title || 'Item').join(' · ') || 'AI room styling')
      : (tileRoomTitle(picks, assignments) || 'Styled room');
    setTileRoomPrompt((cur) => cur ? { ...cur, error: '' } : cur);
    setError('');
    setGenerating(true);
    setCombinedResult({ status: 'pending', titles, kind: 'tile-room', ai: autoAssign });
    await _StyleKroGen.runTryon({
      streamSSE,
      url: '/garments/tile-room/stream',
      token,
      body: JSON.stringify(autoAssign ? {
        auto_assign: true,
        garment_ids: picks.map((g) => g.id),
        ...(cameraRoomPhoto ? { room_photo_b64: cameraRoomPhoto } : {}),
      } : {
        placements,
        ...(cameraRoomPhoto ? { room_photo_b64: cameraRoomPhoto } : {}),
      }),
      headers: { 'Content-Type': 'application/json' },
      errorLabel: 'Room styling failed',
      onUpdate: ({ status, image_url, pending_text, message_id, error }) => {
        setCombinedResult((cur) => {
          if (status === 'error') return { status: 'error', titles, kind: 'tile-room', ai: autoAssign, error };
          const next = { status, titles, kind: 'tile-room', ai: autoAssign };
          if (pending_text !== undefined) next.pending_text = pending_text;
          if (image_url !== undefined) next.image_url = imgUrl(image_url);
          next.message_id = message_id || cur?.message_id;
          return next;
        });
      },
    });
    setGenerating(false);
    clearSelection();
  }

  async function generateTileRoomInline() {
    if (generating) return;
    const ids = Object.values(tileAssignments).filter(Boolean);
    if (ids.length === 0) {
      setError('Add a tile to at least one surface to style the room.');
      return;
    }
    const picks = garments.filter((g) => ids.includes(g.id));
    setError('');
    setStudio360(false);
    setSheet360MsgId(null);
    await confirmTileRoomTryon(false, picks, tileAssignments);
  }

  async function generateTailorSelections() {
    if (selected.size === 0 || generating) return;
    const picks = garments.filter((g) => selected.has(g.id));
    const design = picks.find((g) => garmentKind(g) === 'design');
    const assignedFabricIds = selectedTailorSlots.map((slot) => tailorFabricAssignments[slot.key]).filter(Boolean);
    const fabrics = (selectedTailorSlots.length ? assignedFabricIds : picks.filter((g) => garmentKind(g) === 'fabric').map((g) => g.id))
      .map((fid) => garments.find((g) => g.id === fid))
      .filter(Boolean);
    if (!design || fabrics.length === 0) {
      setError(t('shop.pickDesignFabric'));
      return;
    }
    // Multi-piece outfit (e.g. Coat + Pant + Shirt): let the buyer assign a
    // fabric to each piece, then build ONE combined outfit. Single-piece
    // options keep the per-fabric behaviour below.
    const pieceSlots = selectedTailorSlots;
    if (pieceSlots.length >= 2) {
      const missing = pieceSlots.filter((slot) => !tailorFabricAssignments[slot.key]);
      if (missing.length) {
        setError(`Pick fabric for ${missing.map((slot) => slot.label).join(', ')}.`);
        setActiveTailorFabricSlot(missing[0].key);
        scrollToTailorRef(tailorFabricsRef);
        return;
      }
      const assignments = Object.fromEntries(pieceSlots.map((slot) => [slot.key, tailorFabricAssignments[slot.key]]));
      setError('');
      await runPieceFabricTryon({ design, slots: pieceSlots, fabrics, assignments, piecesPreset: selectedTailorPreset, error: '' });
      return;
    }
    // Single-piece outfit: still render the finished look in the "Outfit
    // setup" panel (step 2) — the same place the multi-piece flow uses —
    // instead of flipping the design/fabric card in the rails below.
    const fabric = fabrics[0];
    if (!(await ensureProfilePhoto())) return;
    const slotLabel = selectedTailorSlots[0]?.label || selectedTailorLabel || 'Outfit';
    const slotKey = selectedTailorSlots[0]?.key || 'outfit';
    const assignmentSummary = [{ slot: { key: slotKey, label: slotLabel }, fabric_title: fabric.title || 'Fabric', fabric_url: fabric.photo_url || '' }];
    const titles = `${slotLabel}: ${fabric.title || 'Fabric'}`;
    setError('');
    setTailorSetupFlipped(false);
    scrollToTailorRef(tailorSetupRef);
    const baseTailor = { titles, kind: 'tailor-outfit', design_title: design.title, original_url: imgUrl(design.photo_url), assignments: assignmentSummary };
    setCombinedResult({ status: 'pending', ...baseTailor });
    setGenerating(true);
    await _StyleKroGen.runTryon({
      streamSSE,
      url: '/garments/tailor-tryon/stream',
      token,
      body: JSON.stringify({ design_id: design.id, fabric_id: fabric.id, pieces: selectedTailorPreset || undefined }),
      headers: { 'Content-Type': 'application/json' },
      onUpdate: ({ status, image_url, pending_text, message_id, error }) => {
        setCombinedResult((cur) => {
          const next = { ...(cur || {}), ...baseTailor, status };
          if (status === 'error') { next.error = error; return next; }
          if (pending_text !== undefined) next.pending_text = pending_text;
          if (image_url !== undefined) next.image_url = imgUrl(image_url);
          if (message_id || cur?.message_id) next.message_id = message_id || cur?.message_id;
          return next;
        });
      },
    });
    setGenerating(false);
    clearSelection();
  }

  // Generate ONE combined outfit where each garment piece uses its assigned
  // fabric (driven by the pieceFabricPrompt mapping overlay).
  async function runPieceFabricTryon(promptOverride = null) {
    const prompt = promptOverride || pieceFabricPrompt;
    if (!prompt || generating) return;
    const { design, slots, fabrics, assignments, piecesPreset } = prompt;
    if (slots.some((s) => !assignments[s.key])) {
      setPieceFabricPrompt((cur) => (cur ? { ...cur, error: 'Pick a fabric for every piece.' } : cur));
      return;
    }
    if (!(await ensureProfilePhoto())) return;
    const byId = Object.fromEntries(fabrics.map((g) => [g.id, g]));
    const piece_fabrics = slots.map((s) => ({ piece: s.key, fabric_id: assignments[s.key] }));
    const assignmentSummary = slots.map((s) => {
      const fabric = byId[assignments[s.key]] || null;
      return { slot: s, fabric_title: fabric?.title || 'Fabric', fabric_url: fabric?.photo_url || '' };
    });
    const titles = slots
      .map((s) => `${s.label}: ${(byId[assignments[s.key]] || {}).title || 'Fabric'}`)
      .join(' · ');
    setPieceFabricPrompt(null);
    setTailorSetupFlipped(false);
    scrollToTailorRef(tailorSetupRef);
    const baseTailor = { titles, kind: 'tailor-outfit', design_title: design.title, original_url: imgUrl(design.photo_url), assignments: assignmentSummary };
    setCombinedResult({ status: 'pending', ...baseTailor });
    setGenerating(true);
    try {
      await _StyleKroGen.runTryon({
        streamSSE,
        url: '/garments/tailor-tryon/stream',
        token,
        body: JSON.stringify({ design_id: design.id, pieces: piecesPreset || selectedTailorPreset || undefined, piece_fabrics }),
        headers: { 'Content-Type': 'application/json' },
        onUpdate: ({ status, image_url, pending_text, message_id, error }) => {
          setCombinedResult((cur) => {
            const next = { ...(cur || {}), ...baseTailor, status };
            if (status === 'error') { next.error = error; return next; }
            if (pending_text !== undefined) next.pending_text = pending_text;
            if (image_url !== undefined) next.image_url = imgUrl(image_url);
            if (message_id || cur?.message_id) next.message_id = message_id || cur?.message_id;
            return next;
          });
        },
      });
    } finally {
      setGenerating(false);
      clearSelection();
    }
  }

  async function generateSeparate() {
    if (selected.size === 0 || generating) return;
    const picks = garments.filter((g) => selected.has(g.id));
    setGenerating(true);
    await runTryon(picks);
    setGenerating(false);
    clearSelection();
  }

  async function generateSelected() {
    if (selected.size === 0 || generating) return;
    if (!(await ensureProfilePhoto())) return;
    if (isTailorShop) return generateTailorSelections();
    const picks = garments.filter((g) => selected.has(g.id));
    if (isTileShop) {
      await promptTileRoom(picks);
      return;
    }
    if (picks.length === 1) return generateSeparate();
    return generateCombined();
  }

  async function generateOne(g) {
    if (!g || generating) return;
    if (!(await ensureProfilePhoto())) return;
    const r = results[g.id];
    if (r && r.status === 'pending') return;
    setGenerating(true);
    await runTryon([g]);
    setGenerating(false);
  }

  // Normalise + stash the buyer's uploaded photo (HEIC->JPEG + downscale), keep
  // the upload-ready Blob in a ref and an object URL for the preview.
  async function onTattooFileChange(e) {
    const f = e.target.files && e.target.files[0];
    e.target.value = '';
    if (!f) return;
    const Tattoo = (typeof window !== 'undefined' && window.ShopCategories?.byKey?.tattoo) || null;
    try {
      const blob = Tattoo && Tattoo.prepareTattooPhoto ? await Tattoo.prepareTattooPhoto(f) : f;
      if (!blob) throw new Error('empty photo');
      tattooPhotoFileRef.current = blob;
      setTattooPhotoUrl((prev) => {
        if (prev) { try { URL.revokeObjectURL(prev); } catch (_) {} }
        return URL.createObjectURL(blob);
      });
      // A new photo invalidates any previous result.
      setCombinedResult(null);
      setError('');
    } catch (err) {
      console.error('Tattoo photo load failed', err);
      alert("Couldn't read that photo. Try a JPG or PNG.");
    }
  }

  function clearTattooPhoto() {
    tattooPhotoFileRef.current = null;
    setTattooPhotoUrl((prev) => {
      if (prev) { try { URL.revokeObjectURL(prev); } catch (_) {} }
      return null;
    });
    if (tattooFileInputRef.current) tattooFileInputRef.current.value = '';
  }

  async function getTattooProfilePhotoFile() {
    let profilePhotoUrl = profile?.photo_url || '';
    if (!profilePhotoUrl) {
      if (!(await ensureProfilePhoto())) return null;
      try {
        const latestProfile = await apiCall('/profiles/me');
        profilePhotoUrl = latestProfile?.photo_url || '';
      } catch (_) {}
    }
    if (!profilePhotoUrl) return null;
    const url = imgUrl(bust(profilePhotoUrl, photoVersion));
    const headers = token ? { Authorization: `Bearer ${token}` } : undefined;
    const res = await fetch(url, { headers });
    if (!res.ok) throw new Error('Could not read your profile photo.');
    const blob = await res.blob();
    const file = new File([blob], 'profile-photo.jpg', { type: blob.type || 'image/jpeg' });
    const Tattoo = (typeof window !== 'undefined' && window.ShopCategories?.byKey?.tattoo) || null;
    return Tattoo && Tattoo.prepareTattooPhoto ? await Tattoo.prepareTattooPhoto(file) : file;
  }

  function tatPointerDown(mode) {
    return (e) => {
      if (e.pointerType === 'mouse' && e.button !== 0) return;
      e.preventDefault();
      e.stopPropagation();
      const rect = tatCanvasRef.current && tatCanvasRef.current.getBoundingClientRect();
      if (!rect) return;
      const start = tatXfRef.current;
      const cx = rect.left + rect.width * (start.xPct / 100);
      const cy = rect.top + rect.height * (start.yPct / 100);
      tatGesture.current = {
        mode, rect, start,
        sx: e.clientX, sy: e.clientY, cx, cy,
        startAngle: Math.atan2(e.clientY - cy, e.clientX - cx),
      };
      try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) {}
    };
  }

  function tatPointerMove(e) {
    const gobj = tatGesture.current;
    if (!gobj) return;
    e.preventDefault();
    const { mode, rect, start, sx, sy, cx, cy, startAngle } = gobj;
    if (mode === 'move') {
      const dxPct = ((e.clientX - sx) / rect.width) * 100;
      const dyPct = ((e.clientY - sy) / rect.height) * 100;
      setTat({ ...start, xPct: Math.max(2, Math.min(98, start.xPct + dxPct)), yPct: Math.max(2, Math.min(98, start.yPct + dyPct)) });
    } else if (mode === 'scale') {
      const nat = tatNatRef.current;
      const aspect = nat.w ? nat.h / nat.w : 1;
      const dist = Math.hypot(e.clientX - cx, e.clientY - cy);
      const wPx = (2 * dist) / Math.sqrt(1 + aspect * aspect);
      setTat({ ...start, wPct: Math.max(6, Math.min(100, (wPx / rect.width) * 100)) });
    } else if (mode === 'rotate') {
      const ang = Math.atan2(e.clientY - cy, e.clientX - cx);
      const deg = ((ang - startAngle) * 180) / Math.PI;
      setTat({ ...start, rot: start.rot + deg });
    }
  }

  function tatPointerEnd(e) {
    if (tatGesture.current) {
      try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) {}
    }
    tatGesture.current = null;
  }

  function loadImageEl(src) {
    return new Promise((resolve, reject) => {
      const im = new Image();
      im.onload = () => resolve(im);
      im.onerror = () => reject(new Error('image load failed'));
      im.src = src;
    });
  }

  // The image used for the tattoo design everywhere in the studio: the seller
  // can publish a background-removed cut-out (transparent PNG) and toggle
  // whether buyers see it; when active we prefer it so the design overlays and
  // composites cleanly without a white box.
  function tattooDesignSrc(g) {
    return (g && g.cutout_active && g.cutout_url) ? g.cutout_url : (g ? g.photo_url : '');
  }

  async function fetchTattooDesignBlob(g) {
    const url = imgUrl(tattooDesignSrc(g));
    const headers = token ? { Authorization: `Bearer ${token}` } : undefined;
    const res = await fetch(url, { headers });
    if (!res.ok) throw new Error('Could not load the tattoo design.');
    return await res.blob();
  }

  // Flatten the buyer photo + the positioned tattoo into one 4:5 image that
  // matches the on-screen preview (the canvas uses object-fit: cover), so the
  // AI receives the design exactly where it was dragged. Returns a JPEG Blob.
  async function compositeTattooPhoto(personBlob, designBlob, xf) {
    const personUrl = URL.createObjectURL(personBlob);
    const designUrl = URL.createObjectURL(designBlob);
    try {
      const [pimg, dimg] = await Promise.all([loadImageEl(personUrl), loadImageEl(designUrl)]);
      const targetAR = 4 / 5;
      const pw = pimg.naturalWidth || 1;
      const ph = pimg.naturalHeight || 1;
      let sx, sy, sw, sh;
      if (pw / ph > targetAR) { sh = ph; sw = ph * targetAR; sx = (pw - sw) / 2; sy = 0; }
      else { sw = pw; sh = pw / targetAR; sx = 0; sy = (ph - sh) / 2; }
      const canvasW = Math.min(1080, Math.round(sw));
      const canvasH = Math.round(canvasW / targetAR);
      const cvs = document.createElement('canvas');
      cvs.width = canvasW; cvs.height = canvasH;
      const ctx = cvs.getContext('2d');
      ctx.drawImage(pimg, sx, sy, sw, sh, 0, 0, canvasW, canvasH);
      const tw = (xf.wPct / 100) * canvasW;
      const dAspect = dimg.naturalWidth ? dimg.naturalHeight / dimg.naturalWidth : 1;
      const th = tw * dAspect;
      const tcx = (xf.xPct / 100) * canvasW;
      const tcy = (xf.yPct / 100) * canvasH;
      ctx.save();
      ctx.translate(tcx, tcy);
      ctx.rotate((xf.rot * Math.PI) / 180);
      ctx.drawImage(dimg, -tw / 2, -th / 2, tw, th);
      ctx.restore();
      return await new Promise((resolve) => cvs.toBlob((b) => resolve(b), 'image/jpeg', 0.92));
    } finally {
      URL.revokeObjectURL(personUrl);
      URL.revokeObjectURL(designUrl);
    }
  }

  async function generateTattooInline() {
    if (generating) return;
    const g = garments.find((x) => x.id === tattooDesignId);
    if (!g) { setError('Pick a tattoo design first.'); return; }
    let photoFile = tattooPhotoFileRef.current;
    if (!photoFile) {
      try {
        photoFile = await getTattooProfilePhotoFile();
      } catch (err) {
        console.error('Tattoo profile photo fallback failed', err);
        setError((err && err.message) || 'Could not read your profile photo.');
        return;
      }
      if (!photoFile) return;
    }
    const titles = g.title || 'Tattoo';
    setError('');
    setGenerating(true);
    setStudio360(false);
    setSheet360MsgId(null);
    setCombinedResult({ status: 'pending', titles, kind: 'tattoo' });
    const onUpdate = (gg, { status, image_url, pending_text, message_id, error }) => {
      setCombinedResult((cur) => {
        if (status === 'error') return { status: 'error', titles, kind: 'tattoo', error };
        const next = { status, titles, kind: 'tattoo' };
        if (pending_text !== undefined) next.pending_text = pending_text;
        if (image_url !== undefined) next.image_url = imgUrl(image_url);
        next.message_id = message_id || cur?.message_id;
        return next;
      });
    };
    // Bake the positioned/resized/rotated tattoo into the photo so the AI sees
    // it exactly where the buyer placed it; fall back to the plain photo if the
    // client-side composite fails for any reason.
    let effectiveNote = '';
    try {
      const designBlob = await fetchTattooDesignBlob(g);
      const composited = await compositeTattooPhoto(photoFile, designBlob, tatXfRef.current);
      if (composited) { photoFile = composited; effectiveNote = TATTOO_COMPOSITE_NOTE; }
    } catch (err) {
      console.error('Tattoo composite failed; sending original photo', err);
    }
    const Tattoo = (typeof window !== 'undefined' && window.ShopCategories?.byKey?.tattoo) || null;
    if (Tattoo && Tattoo.runTattooTryon) {
      await Tattoo.runTattooTryon({
        picks: [g],
        streamSSE,
        token,
        photoFile,
        note: effectiveNote,
        src: 'tattooStudio',
        errorLabel: 'Tattoo try-on failed',
        onUpdate,
      });
    }
    setGenerating(false);
  }

  function tailorCardSubtitle(g) {
    const r = results[g.id];
    const kind = garmentKind(g);
    if (kind === 'design') return r?.fabric_title ? `Styled with ${r.fabric_title}` : (g.description || g.subcategory || g.category_group || 'Select this design');
    if (r?.design_title) return `Styled with ${r.design_title}`;
    return g.description || g.color || g.subcategory || 'Select this fabric';
  }

  function renderTailorGarmentCard(g) {
    const isSel = selected.has(g.id);
    const r = results[g.id];
    const kind = garmentKind(g);
    const showFlip = r && (r.status === 'done' || r.status === 'fast');
    const upgrading = r && r.status === 'fast';
    const isFlipped = showFlip && flipped.has(g.id);
    return (
      <div
        key={g.id}
        className={`sdv2-tailor-card ${kind}${isSel ? ' on' : ''}${showFlip ? ' has-result' : ''}${tailorAnimatingId === g.id ? ' fly-to-setup' : ''}`}
      >
        <button
          type="button"
          className={`sdv2-card-select${isSel ? ' on' : ''}`}
          aria-pressed={isSel}
          aria-label={isSel ? 'Deselect' : 'Select'}
          onClick={() => toggle(g.id)}
        >{isSel ? '✓' : ''}</button>
        <div className="sdv2-tailor-card-media-wrap">
          <div className="sdv2-tailor-card-media" onClick={() => { if (!showFlip) { if (isTailorShop) toggle(g.id); else navigate(`/shops/${id}/items/${g.id}`); } }}>
            {showFlip ? (
              <div
                className={`flip${isFlipped ? ' flipped' : ''}`}
                role="button"
                tabIndex={0}
                onClick={(e) => { e.stopPropagation(); setFlipped((cur) => { const next = new Set(cur); if (next.has(g.id)) next.delete(g.id); else next.add(g.id); return next; }); }}
                title={t('tailor.tapFlip')}
              >
                <button
                  type="button"
                  className="sdv2-card-zoom"
                  aria-label="Zoom"
                  onClick={(e) => { e.stopPropagation(); setLightbox(isFlipped ? imgUrl(g.photo_url) : r.image_url); }}
                >
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
                </button>
                <div className="flip-inner">
                  <div className="flip-face flip-front">
                    <img src={r.image_url} alt={`${g.title} on you`} loading="lazy" decoding="async" />
                    <span className="flip-tag">{upgrading ? t('tailor.quickPreview') : t('tailor.tapFlip')}</span>
                  </div>
                  <div className="flip-face flip-back">
                    <img src={imgUrl(g.photo_url)} alt={g.title} loading="lazy" decoding="async" />
                    <span className="flip-tag">{t('tailor.original')}</span>
                  </div>
                </div>
                {upgrading && (
                  <div className="upgrade-hint upgrade-hint-floating">
                    <span className="upgrade-hint-spinner" aria-hidden="true" />
                    <span className="upgrade-hint-title">{t('chat.refiningLook')}</span>
                  </div>
                )}
              </div>
            ) : (
              <>
                <img src={imgUrl(g.photo_url)} alt={g.title} loading="lazy" decoding="async" />
                <button
                  type="button"
                  className="sdv2-card-zoom"
                  aria-label="Zoom"
                  onClick={(e) => { e.stopPropagation(); setLightbox(imgUrl(g.photo_url)); }}
                >
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
                </button>
                {r && r.status === 'pending' && (
                  <div className="sdv2-tailor-card-overlay">
                    <TryonProgress label={t('chat.loading')} />
                  </div>
                )}
                {r && r.status === 'error' && (
                  <div className="sdv2-tailor-card-overlay error">
                    <div className="tryon-error">⚠ {r.error}</div>
                  </div>
                )}
              </>
            )}
          </div>
        </div>
        <div className="sdv2-tailor-card-body">
          <button
            type="button"
            className="sdv2-card-title-link"
            onClick={(e) => { e.stopPropagation(); navigate(`/shops/${id}/items/${g.id}`); }}
          >{g.title}</button>
          <div className="sdv2-tailor-card-subtitle" onClick={() => toggle(g.id)}>{tailorCardSubtitle(g)}</div>
        </div>
      </div>
    );
  }

  function renderGarmentCard(g) {
    const isSel = selected.has(g.id);
    const r = results[g.id];
    const showFlip = r && (r.status === 'done' || r.status === 'fast');
    const upgrading = r && r.status === 'fast';
    const isFlipped = showFlip && flipped.has(g.id);
    const kind = garmentKind(g);
    return (
      <div key={g.id} className={`sdv2-card${isSel ? ' selected' : ''}`}>
        <button
          type="button"
          className={`sdv2-card-select${isSel ? ' on' : ''}`}
          aria-pressed={isSel}
          aria-label={isSel ? 'Deselect' : 'Select'}
          onClick={() => toggle(g.id)}
        >{isSel ? '✓' : ''}</button>
        <div className="sdv2-card-img" onClick={() => navigate(`/shops/${id}/items/${g.id}`)}>
          {showFlip ? (
            <div
              className={`flip${isFlipped ? ' flipped' : ''}`}
              role="button"
              tabIndex={0}
              onClick={(e) => { e.stopPropagation(); setFlipped((cur) => { const next = new Set(cur); if (next.has(g.id)) next.delete(g.id); else next.add(g.id); return next; }); }}
              title={t('tailor.tapFlip')}
            >
              <button
                type="button"
                className="sdv2-card-zoom"
                aria-label={t('tailor.zoom')}
                onClick={(e) => { e.stopPropagation(); setLightbox(isFlipped ? imgUrl(g.photo_url) : r.image_url); }}
              >
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
              </button>
              <div className="flip-inner">
                <div className="flip-face flip-front">
                  <img src={r.image_url} alt={`${g.title} on you`} loading="lazy" decoding="async" />
                  <span className="flip-tag">{upgrading ? t('tailor.quickPreview') : t('tailor.tapFlip')}</span>
                </div>
                <div className="flip-face flip-back">
                  <img src={imgUrl(g.photo_url)} alt={g.title} loading="lazy" decoding="async" />
                  <span className="flip-tag">{t('tailor.original')}</span>
                </div>
              </div>
              {upgrading && (
                <div className="upgrade-hint upgrade-hint-floating">
                  <span className="upgrade-hint-spinner" aria-hidden="true" />
                  <span className="upgrade-hint-title">{t('chat.refiningLook')}</span>
                </div>
              )}
            </div>
          ) : (
            <>
              <img src={imgUrl(g.photo_url)} alt={g.title} loading="lazy" decoding="async" />
              <button
                type="button"
                className="sdv2-card-zoom"
                aria-label="Zoom"
                onClick={(e) => { e.stopPropagation(); setLightbox(imgUrl(g.photo_url)); }}
              >
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
              </button>
              {r && r.status === 'pending' && (
                <div className="tryon-overlay">
                  <TryonProgress label={t('chat.loading')} />
                </div>
              )}
              {r && r.status === 'error' && (
                <div className="tryon-overlay error">
                  <div className="tryon-error">⚠ {r.error}</div>
                </div>
              )}
            </>
          )}
        </div>
        <div className="sdv2-card-meta">
          <button
            type="button"
            className="sdv2-card-title-link"
            onClick={(e) => { e.stopPropagation(); navigate(`/shops/${id}/items/${g.id}`); }}
          >{g.title}</button>
          {((kind || g.subcategory || g.category_group) || g.color || r?.design_title) && (
            <div className="sdv2-card-sub">{[
              assetKindLabel(kind),
              g.subcategory || g.category_group,
              g.color,
              isTailorShop && kind === 'fabric' && r?.design_title ? `with ${r.design_title}` : null,
            ].filter(Boolean).join(' · ')}</div>
          )}
          {g.price && <div className="sdv2-card-price">{g.price}</div>}
        </div>
      </div>
    );
  }

  return (
    <section className="screen shop-detail-v2">
      <NavBar active="shops" />
      <main className="page-wrap sdv2-page">
        {loading ? <p className="muted">Loading…</p> : (
          shop ? (() => {
            // Derive filter state.
            const q = search.trim().toLowerCase();
            const groupCounts = makeCountMap(garments, 'category_group', { asMap: true, skipEmpty: true });
            const groupsSorted = Array.from(groupCounts.entries())
              .sort((a, b) => b[1] - a[1])
              .map(([k]) => k);
            const primaryGroups = groupsSorted.slice(0, 5);
            const hasMoreGroups = groupsSorted.length > 5;
            const subCounts = makeCountMap(
              filterGroup ? garments.filter((g) => g.category_group === filterGroup) : garments,
              'subcategory', { asMap: true, skipEmpty: true }
            );
            const subs = Array.from(subCounts.keys()).sort();
            const list = garments.filter((g) => {
              if (isTailorShop) return true;
              if (filterGroup && g.category_group !== filterGroup) return false;
              if (filterSub && g.subcategory !== filterSub) return false;
              if (!q) return true;
              return (
                (g.title || '').toLowerCase().includes(q) ||
                (g.description || '').toLowerCase().includes(q) ||
                String(g.price || '').toLowerCase().includes(q) ||
                (g.asset_kind || '').toLowerCase().includes(q) ||
                (g.subcategory || '').toLowerCase().includes(q) ||
                (g.category_group || '').toLowerCase().includes(q) ||
                (g.item_code || '').toLowerCase().includes(q) ||
                (g.color || '').toLowerCase().includes(q)
              );
            });
            const selectedItems = garments.filter((g) => selected.has(g.id));
            const selectedDesign = selectedItems.find((g) => garmentKind(g) === 'design') || null;
            const selectedFabrics = selectedItems.filter((g) => garmentKind(g) === 'fabric');
            const tailorOutfitResult = combinedResult && combinedResult.kind === 'tailor-outfit' ? combinedResult : null;
            const showTailorSetupEmpty = !selectedDesign && !tailorOutfitResult && selectedTailorSlots.length === 0;
            const tailorPreviewAssignments = Array.isArray(tailorOutfitResult?.assignments) && tailorOutfitResult.assignments.length
              ? tailorOutfitResult.assignments.map((a) => ({ slot: a.slot, fabric: a.fabric_url ? { title: a.fabric_title, photo_url: a.fabric_url } : null }))
              : selectedTailorSlots.length
              ? selectedTailorSlots.map((slot) => ({
                slot,
                fabric: tailorFabricAssignments[slot.key]
                  ? garments.find((g) => g.id === tailorFabricAssignments[slot.key]) || null
                  : null,
              }))
              : selectedFabrics.slice(0, 3).map((fabric, idx) => ({ slot: { key: `fabric_${idx}`, label: idx === 0 ? 'Fabric' : `Fabric ${idx + 1}` }, fabric }));
            const matchesTailorSearch = (g, term) => {
              const needle = term.trim().toLowerCase();
              if (!needle) return true;
              return (
                (g.title || '').toLowerCase().includes(needle) ||
                (g.description || '').toLowerCase().includes(needle) ||
                (g.subcategory || '').toLowerCase().includes(needle) ||
                (g.category_group || '').toLowerCase().includes(needle) ||
                (g.item_code || '').toLowerCase().includes(needle) ||
                (g.color || '').toLowerCase().includes(needle)
              );
            };
            const designList = isTailorShop ? list.filter((g) => garmentKind(g) === 'design' && matchesTailorSearch(g, tailorDesignSearch)) : [];
            const fabricList = isTailorShop ? list.filter((g) => garmentKind(g) === 'fabric' && matchesTailorSearch(g, tailorFabricSearch)) : [];
            // Icons keyed by lowercase group name (fall back to a generic icon).
            const GROUP_ICONS = {
              topwear: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M4 7l4-3 4 2 4-2 4 3-2 3-2-1v11H8V9L6 10z"/></svg>),
              tops: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M4 7l4-3 4 2 4-2 4 3-2 3-2-1v11H8V9L6 10z"/></svg>),
              dresses: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M9 3h6l2 5-3 1 4 12H6l4-12-3-1z"/></svg>),
              bottoms: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M6 3h12l-1 18h-4l-1-10-1 10H7z"/></svg>),
              shoes: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M3 17c0-3 2-5 5-5l3-3 3 1 1 2 5 1c1 0 2 1 2 2v2c0 1-1 2-2 2H5c-1 0-2-1-2-2z"/></svg>),
              bags: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M5 9h14l-1 11H6z"/><path d="M9 9V6a3 3 0 0 1 6 0v3"/></svg>),
              accessories: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="15" r="5"/><path d="M9 10l-2-5h10l-2 5"/></svg>),
            };
            const iconFor = (label) => GROUP_ICONS[(label || '').toLowerCase()] || (
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><rect x="4" y="4" width="16" height="16" rx="3"/></svg>
            );
            return (
              <>
                {!isTailorShop && !isTileShop && (
                  <div className="sdv2-search-row">
                    <button
                      type="button"
                      className={`sdv2-filter-burger${(filterGroup || filterSub) ? ' has-active' : ''}`}
                      onClick={() => setFilterOpen(true)}
                      aria-label="Filter"
                      aria-haspopup="dialog"
                    >
                      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></svg>
                      {(filterGroup || filterSub) && <span className="sdv2-filter-dot" aria-hidden="true" />}
                    </button>
                    <div className="sdv2-search">
                      <svg className="sdv2-search-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
                      <input
                        type="search"
                        placeholder={t('shop.searchPlaceholder')}
                        value={search}
                        onChange={(e) => setSearch(e.target.value)}
                        aria-label={t('shop.searchPlaceholder')}
                      />
                    </div>
                  </div>
                )}

                {/* Shop hero card (compact by default, expandable) */}
                <div className={`sdv2-shop-card${shopExpanded ? ' expanded' : ' compact'}`}>
                  <div className="sdv2-shop-card-main">
                    <div className="sdv2-shop-logo">{(shop.name || '?').trim().charAt(0).toUpperCase()}</div>
                    <div className="sdv2-shop-info">
                      <div className="sdv2-shop-eyebrow">{t('shop.eyebrow')}</div>
                      <div className="sdv2-shop-name-row">
                        <h2 className="sdv2-shop-name">{shop.name}</h2>
                        <button
                          type="button"
                          className="sdv2-shop-share"
                          onClick={shareShop}
                          aria-label="Share shop"
                        >
                          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16,6 12,2 8,6"/><line x1="12" y1="2" x2="12" y2="15"/></svg>
                          {linkCopied ? 'Copied' : 'Share'}
                        </button>
                      </div>
                      {shopExpanded && (
                        <>
                          {shop.location && (
                            <div className="sdv2-shop-addr">
                              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
                              <a
                                href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(shop.location)}`}
                                target="_blank"
                                rel="noopener noreferrer"
                              >{shop.location}</a>
                            </div>
                          )}
                          {shop.phone && (
                            <div className="sdv2-shop-addr">
                              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.95.37 1.88.72 2.77a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.31-1.31a2 2 0 0 1 2.11-.45c.89.35 1.82.59 2.77.72A2 2 0 0 1 22 16.92z"/></svg>
                              <a href={`tel:${shop.phone.replace(/[^+\d]/g, '')}`}>{shop.phone}</a>
                            </div>
                          )}
                          <div className="sdv2-shop-pills">
                            {shop.category && <span className="sdv2-pill">{shopCategoryLabel(shop.category)}</span>}
                            <span className="sdv2-pill">{garments.length} piece{garments.length === 1 ? '' : 's'}</span>
                          </div>
                          {shop.description && <p className="sdv2-shop-desc">{shop.description}</p>}
                        </>
                      )}
                      <button
                        type="button"
                        className="sdv2-shop-more"
                        onClick={() => setShopExpanded((v) => !v)}
                        aria-expanded={shopExpanded}
                      >
                        {shopExpanded ? 'Less' : 'More'}
                        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ transform: shopExpanded ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s' }}><polyline points="6,9 12,15 18,9"/></svg>
                      </button>
                    </div>
                  </div>
                  {shopExpanded && (
                    <svg className="sdv2-shop-deco" viewBox="0 0 100 80" aria-hidden="true">
                      <g fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round">
                        <path d="M15 70 V35 L40 18 L65 35 V70 Z"/>
                        <path d="M40 18 L40 6 L75 6 L75 35"/>
                        <rect x="25" y="42" width="8" height="10"/>
                        <rect x="44" y="42" width="8" height="10"/>
                        <rect x="44" y="58" width="8" height="12"/>
                        <rect x="50" y="14" width="6" height="8"/>
                        <rect x="62" y="14" width="6" height="8"/>
                        <rect x="50" y="26" width="6" height="8"/>
                        <rect x="62" y="26" width="6" height="8"/>
                      </g>
                    </svg>
                  )}
                </div>

                {error && <p className="error">{error}</p>}

                {garments.length === 0 ? (
                  <div className="looks-empty">
                    <div className="looks-empty-icon">🪡</div>
                    <h3>{t('shop.noPiecesYet')}</h3>
                    <p className="muted">{t('shop.checkBackSoon')}</p>
                  </div>
                ) : (
                  <>
                    {!isTailorShop && filterOpen && (
                      <div className="sdv2-filter-sheet-overlay" role="dialog" aria-modal="true" aria-label="Filter" onClick={() => setFilterOpen(false)}>
                        <div className="sdv2-filter-sheet" onClick={(e) => e.stopPropagation()}>
                          <div className="sdv2-filter-sheet-head">
                            <h3>{t('filter.title')}</h3>
                            <button type="button" className="sdv2-filter-sheet-close" onClick={() => setFilterOpen(false)} aria-label="Close">×</button>
                          </div>
                          <div className="sdv2-filter-sheet-body">
                            <div className="sdv2-filter-group">
                              <div className="sdv2-filter-label">{t('filter.category')}</div>
                              <div className="sdv2-filter-chips">
                                <button type="button" className={`sdv2-fpill small${filterGroup === '' ? ' on' : ''}`} onClick={() => { setFilterGroup(''); setFilterSub(''); }}>{t('common.all')}</button>
                                {groupsSorted.map((grp) => (
                                  <button key={grp} type="button" className={`sdv2-fpill small${filterGroup === grp ? ' on' : ''}`} onClick={() => { setFilterGroup(grp); setFilterSub(''); }}>{grp} ({groupCounts.get(grp)})</button>
                                ))}
                              </div>
                            </div>
                            {filterGroup && subs.length > 0 && (
                              <div className="sdv2-filter-group">
                                <div className="sdv2-filter-label">{t('filter.subcategory')}</div>
                                <div className="sdv2-filter-chips">
                                  <button type="button" className={`sdv2-fpill small${filterSub === '' ? ' on' : ''}`} onClick={() => setFilterSub('')}>{t('common.all')} {filterGroup}</button>
                                  {subs.map((sub) => (
                                    <button key={sub} type="button" className={`sdv2-fpill small${filterSub === sub ? ' on' : ''}`} onClick={() => setFilterSub(sub)}>{sub} ({subCounts.get(sub)})</button>
                                  ))}
                                </div>
                              </div>
                            )}
                          </div>
                          <div className="sdv2-filter-sheet-foot">
                            <button type="button" className="sdv2-fpill small" onClick={() => { setFilterGroup(''); setFilterSub(''); }}>{t('common.clear')}</button>
                            <button type="button" className="sdv2-fpill small on" onClick={() => setFilterOpen(false)}>{t('common.apply')}</button>
                          </div>
                        </div>
                      </div>
                    )}

                    {list.length === 0 ? (
                      <p className="muted" style={{ marginTop: 24, textAlign: 'center' }}>{t('shop.noMatchingPieces')}</p>
                    ) : isTailorShop ? (
                      <div className="sdv2-tailor-picker">
                        <section className="sdv2-tailor-section tailor-piece-section" aria-label="What to stitch">
                          <div className="sdv2-tailor-section-head">
                            <h3>1. {t('tailor.chooseType')}</h3>
                            <div className="sdv2-tailor-section-count">{tailorSlotChoices.length} {tailorSlotChoices.length === 1 ? t('tailor.pieceLabel') : t('tailor.piecesLabel')}</div>
                          </div>
                          <div className="sdv2-filter-chips" role="group" aria-label="Garment pieces">
                            {visibleTailorSlots.map((slot) => (
                              <button
                                key={slot.key}
                                type="button"
                                aria-pressed={tailorPieceKeys.includes(slot.key)}
                                className={`sdv2-fpill small tailor-piece-chip${tailorPieceKeys.includes(slot.key) ? ' on' : ''}`}
                                onClick={() => setTailorPieceKeys((cur) => {
                                  if (!cur.includes(slot.key)) return [...cur, slot.key];
                                  setTailorFabricAssignments((assignments) => {
                                    const next = { ...assignments };
                                    delete next[slot.key];
                                    return next;
                                  });
                                  setActiveTailorFabricSlot((active) => (active === slot.key ? '' : active));
                                  return cur.filter((key) => key !== slot.key);
                                })}
                              >
                                <span className="tailor-piece-icon" aria-hidden="true">
                                    <TailorPieceIcon piece={slot.key} />
                                </span>
                                {slot.label}
                              </button>
                            ))}
                              {tailorMoreSlots.length > 0 && (
                                <button
                                  key="more-tailor-pieces"
                                  type="button"
                                  aria-expanded={tailorPiecesExpanded}
                                  className={`sdv2-fpill small tailor-piece-chip more${tailorMoreSelected ? ' on' : ''}`}
                                  onClick={() => setTailorPiecesExpanded((v) => !v)}
                                >
                                  <span className="tailor-piece-icon" aria-hidden="true">
                                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
                                  </span>
                                  {tailorPiecesExpanded ? t('tailor.less') : t('common.more')}
                                </button>
                              )}
                          </div>
                        </section>

                        <section ref={tailorSetupRef} className={`tailor-studio-card${tailorSetupPulse ? ` pulse-${tailorSetupPulse}` : ''}`} aria-label="Outfit setup">
                          <div className="tailor-setup-head">
                            <div className="tailor-setup-icon" aria-hidden="true">
                              <svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2l2.2 5.2L20 9.4l-5 3.5.3 6.1L12 15.9 8.7 19l.3-6.1-5-3.5 5.8-2.2z"/></svg>
                            </div>
                            <div>
                              <h3>2. {t('tailor.outfitSetup')}</h3>
                              <p>{t('tailor.customizePieces')}</p>
                            </div>
                          </div>
                          {showTailorSetupEmpty ? (
                            <div className="tailor-setup-empty-message">
                              <TailorPieceIcon piece="coat" />
                              <strong>{t('tailor.selectDesignFabric')}</strong>
                              <span>{t('tailor.selectHint')}</span>
                            </div>
                          ) : (
                          <>
                          <div className={`tailor-setup-result-card${tailorOutfitResult?.image_url ? ' has-result' : ''}`}>
                            <div className="tailor-setup-result-media">
                              {tailorOutfitResult?.status === 'pending' && (
                                <div className="tailor-setup-result-pending"><TryonProgress label="Stitching outfit…" /></div>
                              )}
                              {tailorOutfitResult?.status === 'error' && (
                                <div className="tailor-setup-result-error">⚠ {tailorOutfitResult.error}</div>
                              )}
                              {(tailorOutfitResult?.status === 'fast' || tailorOutfitResult?.status === 'done') && tailorOutfitResult.image_url ? (
                                <div
                                  className={`flip${tailorSetupFlipped ? ' flipped' : ''}`}
                                  role="button"
                                  tabIndex={0}
                                  onClick={(e) => { e.stopPropagation(); setTailorSetupFlipped((v) => !v); }}
                                  title={t('tailor.tapFlip')}
                                >
                                  <button
                                    type="button"
                                    className="sdv2-card-zoom"
                                    aria-label={t('tailor.zoom')}
                                    onClick={(e) => { e.stopPropagation(); setLightbox(tailorSetupFlipped ? (tailorOutfitResult.original_url || tailorOutfitResult.image_url) : tailorOutfitResult.image_url); }}
                                  >
                                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
                                  </button>
                                  <div className="flip-inner">
                                    <div className="flip-face flip-front">
                                      <img src={tailorOutfitResult.image_url} alt="Generated tailor outfit" loading="lazy" decoding="async" />
                                      <span className="flip-tag">{tailorOutfitResult.status === 'fast' ? t('tailor.quickPreview') : t('tailor.tapFlip')}</span>
                                    </div>
                                    <div className="flip-face flip-back">
                                      <img src={tailorOutfitResult.original_url || tailorOutfitResult.image_url} alt="Original design" loading="lazy" decoding="async" />
                                      <span className="flip-tag">{t('tailor.original')}</span>
                                    </div>
                                  </div>
                                  {tailorOutfitResult.status === 'fast' && (
                                    <div className="upgrade-hint upgrade-hint-floating">
                                      <span className="upgrade-hint-spinner" aria-hidden="true" />
                                      <span className="upgrade-hint-title">{t('chat.refiningLook')}</span>
                                    </div>
                                  )}
                                </div>
                              ) : selectedDesign && !tailorOutfitResult?.status ? (
                                <>
                                  <img src={imgUrl(selectedDesign.photo_url)} alt={selectedDesign.title || 'Selected design'} loading="lazy" decoding="async" />
                                  <button
                                    type="button"
                                    className="sdv2-card-zoom"
                                    aria-label={t('tailor.zoom')}
                                    onClick={(e) => { e.stopPropagation(); setLightbox(imgUrl(selectedDesign.photo_url)); }}
                                  >
                                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
                                  </button>
                                </>
                              ) : !tailorOutfitResult?.status ? (
                                <div className="tailor-setup-result-empty" aria-hidden="true">
                                  <TailorPieceIcon piece="coat" />
                                </div>
                              ) : null}
                            </div>
                          </div>
                          <div className="tailor-assignment-list">
                            <button type="button" className={`tailor-assignment-row design-row${(selectedDesign || tailorOutfitResult?.original_url) ? ' filled' : ''}`} onClick={() => scrollToTailorRef(tailorDesignsRef)}>
                              <div className="tailor-assignment-thumb design-thumb">
                                {(selectedDesign || tailorOutfitResult?.original_url) ? (
                                  <img src={selectedDesign ? imgUrl(selectedDesign.photo_url) : tailorOutfitResult.original_url} alt={selectedDesign?.title || tailorOutfitResult?.design_title || 'Selected design'} loading="lazy" decoding="async" />
                                ) : (
                                  <span aria-hidden="true">
                                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M7 4l3 2 2-1 2 1 3-2 3 4-3 2v10H7V10L4 8z"/><path d="M10 6v14"/><path d="M14 6v14"/></svg>
                                  </span>
                                )}
                              </div>
                              <div className="tailor-assignment-copy">
                                <span>{selectedDesign ? (selectedDesign.title || t('tailor.selectedDesign')) : (tailorOutfitResult?.design_title || t('tailor.chooseOneDesign'))}</span>
                                <strong>{selectedTailorLabel || t('tailor.selectPiecesFirst')}</strong>
                              </div>
                              <span className="tailor-row-chevron" aria-hidden="true">›</span>
                            </button>
                            <div className="tailor-fabric-assignment-strip">
                              {(tailorPreviewAssignments.length ? tailorPreviewAssignments : [{ slot: { key: 'empty', label: t('tailor.fabricWord') }, fabric: null }]).map(({ slot, fabric }) => (
                                <button
                                  type="button"
                                  key={slot.key}
                                  className={`tailor-assignment-row fabric-row${fabric ? ' filled' : ''}${activeTailorFabricSlot === slot.key ? ' active' : ''}`}
                                  onClick={() => { setActiveTailorFabricSlot(slot.key); scrollToTailorRef(tailorFabricsRef); }}
                                >
                                  <div className="tailor-assignment-thumb">
                                    {fabric ? <img src={imgUrl(fabric.photo_url)} alt={fabric.title || 'Fabric'} loading="lazy" decoding="async" /> : <span />}
                                  </div>
                                  <div className="tailor-assignment-copy">
                                    <span>{slot.label}</span>
                                    <strong>{fabric ? (fabric.title || t('tailor.untitledFabric')) : t('tailor.selectFabric')}</strong>
                                  </div>
                                  <span className="tailor-row-chevron" aria-hidden="true">›</span>
                                </button>
                              ))}
                            </div>
                          </div>
                          </>
                          )}
                        </section>

                        <section ref={tailorDesignsRef} className="sdv2-tailor-section" aria-label="Design selection">
                          <div className="sdv2-tailor-section-head">
                            <h3>3. {t('tailor.exploreDesigns')}</h3>
                            <div className="sdv2-tailor-section-count">{designList.length} {designList.length === 1 ? t('tailor.designLabel') : t('tailor.designsLabel')}</div>
                          </div>
                          <label className="tailor-section-search">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
                            <input
                              type="search"
                              placeholder={t('tailor.searchDesigns')}
                              value={tailorDesignSearch}
                              onChange={(e) => setTailorDesignSearch(e.target.value)}
                              aria-label={t('tailor.searchDesigns')}
                            />
                          </label>
                          {designList.length === 0 ? (
                            <p className="muted" style={{ marginTop: 16, textAlign: 'center' }}>{t('shop.noMatchingDesigns')}</p>
                          ) : (
                            <div className={`sdv2-tailor-rail designs${designList.length === 1 ? ' single' : ''}`}>
                              {designList.map(renderTailorGarmentCard)}
                            </div>
                          )}
                        </section>

                        <section ref={tailorFabricsRef} className="sdv2-tailor-section" aria-label="Fabric selection">
                          <div className="sdv2-tailor-section-head">
                            <h3>4. {t('tailor.fabricCollection')}</h3>
                            <div className="sdv2-tailor-section-count">{fabricList.length} {fabricList.length === 1 ? t('tailor.fabricLabel') : t('tailor.fabricsLabel')}</div>
                          </div>
                          <label className="tailor-section-search">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
                            <input
                              type="search"
                              placeholder={t('tailor.searchFabrics')}
                              value={tailorFabricSearch}
                              onChange={(e) => setTailorFabricSearch(e.target.value)}
                              aria-label={t('tailor.searchFabrics')}
                            />
                          </label>
                          {fabricList.length === 0 ? (
                            <p className="muted" style={{ marginTop: 16, textAlign: 'center' }}>{t('shop.noMatchingFabrics')}</p>
                          ) : (
                            <div className={`sdv2-tailor-rail fabrics${fabricList.length <= 2 ? ' roomy' : ''}`}>
                              {fabricList.map(renderTailorGarmentCard)}
                            </div>
                          )}
                        </section>
                      </div>
                    ) : isTileShop ? (() => {
                      const q2 = tileRoomSearch.trim().toLowerCase();
                      const tileCategoryOf = (g) => (g.category_group || '').trim();
                      const tileCats = ['All', ...garments.reduce((acc, g) => {
                        const c = tileCategoryOf(g);
                        if (c && !acc.includes(c)) acc.push(c);
                        return acc;
                      }, [])];
                      const tileList = garments.filter((g) => {
                        if (tileCat !== 'All' && tileCategoryOf(g) !== tileCat) return false;
                        if (!q2) return true;
                        return (
                          (g.title || '').toLowerCase().includes(q2) ||
                          (g.subcategory || '').toLowerCase().includes(q2) ||
                          (g.category_group || '').toLowerCase().includes(q2) ||
                          (g.color || '').toLowerCase().includes(q2) ||
                          (g.asset_kind || '').toLowerCase().includes(q2)
                        );
                      });
                      const assignedCount = Object.values(tileAssignments).filter(Boolean).length;
                      const gen = combinedResult && combinedResult.kind === 'tile-room' ? combinedResult : null;
                      const genBusy = gen && (gen.status === 'pending' || gen.status === 'fast');
                      const genShot = gen && (gen.status === 'fast' || gen.status === 'done') ? gen.image_url : null;
                      const roomBase = cameraRoomPhoto
                        ? cameraRoomPhoto
                        : (profile?.photo_url ? bust(imgUrl(profile.photo_url), photoVersion) : null);
                      const SURFACE_ICON = {
                        floor: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M3 8l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4"/><path d="M3 16l9 4 9-4"/></svg>),
                        front_wall: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><rect x="4" y="3" width="16" height="18" rx="1.5"/><line x1="12" y1="3" x2="12" y2="21"/></svg>),
                        left_wall: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M5 4l8-2v20l-8 2z"/><path d="M13 2l6 3v14l-6 3"/></svg>),
                        right_wall: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M19 4l-8-2v20l8 2z"/><path d="M11 2L5 5v14l6 3"/></svg>),
                      };
                      const activeSurfaceLabel = (TILE_ROOM_SURFACES.find((s) => s.value === activeTileSurface) || {}).label || '';
                      return (
                        <div className="trs-studio trs-v2">
                          <section className="trs-room trs-room-v2">
                            <div className="trs-room-head">
                              <h3 className="trs-room-title">StyleKro Room</h3>
                            </div>
                            <div className="trs-tabs" role="tablist" aria-label="Room surfaces">
                              {TILE_ROOM_SURFACES.map((s) => (
                                <button
                                  key={s.value}
                                  type="button"
                                  role="tab"
                                  aria-selected={activeTileSurface === s.value}
                                  className={`trs-tab${activeTileSurface === s.value ? ' on' : ''}${tileAssignments[s.value] ? ' filled' : ''}`}
                                  onClick={() => setActiveTileSurface(s.value)}
                                >
                                  <span className="trs-tab-ico" aria-hidden="true">{SURFACE_ICON[s.value]}</span>
                                  <span className="trs-tab-label">{s.label}</span>
                                </button>
                              ))}
                            </div>
                            <div ref={roomCanvasRef} className={`trs-canvas surface-${activeTileSurface}`}>
                              {studio360 ? (
                                <div className="trs-canvas-360">
                                  <Sheet360Viewer
                                    active={studio360}
                                    fetchSheet={fetchCombinedSheet360}
                                    onExit={closeStudio360}
                                    exitLabel="✕ Exit 360°"
                                  />
                                </div>
                              ) : genShot ? (
                                <img className="trs-canvas-img" src={genShot} alt="Room styled with selected materials" onClick={() => setLightbox(genShot)} style={{ cursor: 'zoom-in' }} />
                              ) : roomBase ? (
                                <>
                                  <img
                                    key={roomBase}
                                    className="trs-canvas-img"
                                    src={roomBase}
                                    alt="Your room"
                                    ref={(node) => { if (node && node.complete && node.naturalWidth > 0) setLoadedRoomSrc(roomBase); }}
                                    onLoad={() => setLoadedRoomSrc(roomBase)}
                                    onError={() => setLoadedRoomSrc(roomBase)}
                                    onClick={() => setLightbox(roomBase)}
                                    style={{ cursor: 'zoom-in' }}
                                  />
                                  {loadedRoomSrc !== roomBase && (
                                    <div className="trs-canvas-overlay trs-photo-loading" aria-live="polite">
                                      <button type="button" className="trs-loading-pill" disabled>
                                        <span className="trs-loading-dot" aria-hidden="true" />
                                        Loading room photo...
                                      </button>
                                    </div>
                                  )}
                                </>
                              ) : !authReady ? (
                                <div className="trs-canvas-empty trs-photo-loading" aria-live="polite">
                                  <button type="button" className="trs-loading-pill" disabled>
                                    <span className="trs-loading-dot" aria-hidden="true" />
                                    Loading room photo...
                                  </button>
                                </div>
                              ) : (
                                <div className="trs-canvas-empty">
                                  <svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 10l9-6 9 6"/><path d="M5 9v11h14V9"/><circle cx="12" cy="14" r="2.5"/></svg>
                                  <strong>Add a room photo</strong>
                                  <span>Set your room photo in Profile to preview materials here.</span>
                                </div>
                              )}
                              {gen && gen.status === 'pending' && (
                                <div className="trs-canvas-overlay"><TryonProgress label={gen.ai ? 'AI is styling your room…' : 'Styling your room…'} /></div>
                              )}
                              {gen && gen.status === 'error' && (
                                <div className="trs-canvas-overlay"><div className="tryon-error">⚠ {gen.error}</div></div>
                              )}
                              {gen && gen.status === 'fast' && (
                                <div className="upgrade-hint trs-canvas-hint"><span className="upgrade-hint-spinner" aria-hidden="true" /><span className="upgrade-hint-title">Refining look</span></div>
                              )}
                              {!gen && roomBase && TILE_ROOM_SURFACES.map((s) => (
                                <button
                                  key={s.value}
                                  type="button"
                                  className={`trs-label trs-label-${s.value}${activeTileSurface === s.value ? ' on' : ''}`}
                                  onClick={() => setActiveTileSurface(s.value)}
                                >{s.label}</button>
                              ))}
                              {!gen && (
                                <button
                                  type="button"
                                  className="trs-room-camera-btn"
                                  onClick={() => roomCameraRef.current && roomCameraRef.current.click()}
                                  aria-label="Use camera photo"
                                  title={cameraRoomPhoto ? 'Retake room photo' : 'Use camera photo'}
                                >
                                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
                                </button>
                              )}
                              {!gen && cameraRoomPhoto && (
                                <button
                                  type="button"
                                  className="trs-room-camera-clear"
                                  onClick={() => { setCameraRoomPhoto(null); if (roomCameraRef.current) roomCameraRef.current.value = ''; }}
                                  aria-label="Use profile room photo"
                                  title="Use profile room photo"
                                >×</button>
                              )}
                              <input
                                ref={roomCameraRef}
                                type="file"
                                accept="image/*,.heic,.heif"
                                capture="environment"
                                hidden
                                onChange={async (e) => {
                                  const f = e.target.files && e.target.files[0];
                                  e.target.value = '';
                                  if (!f) return;
                                  try {
                                    const normalized = await prepareCameraImage(f);
                                    const url = await readFileAsDataURL(normalized);
                                    if (url) setCameraRoomPhoto(url);
                                  } catch (err) {
                                    console.error('Room photo load failed', err);
                                    alert("Couldn't read that photo. Try a JPG or PNG.");
                                  }
                                }}
                              />
                              {gen && gen.status === 'done' && !studio360 && (
                                <div className="trs-canvas-actions">
                                  {gen.message_id && (
                                    <button type="button" className="trs-canvas-icon-btn" title="360° view" aria-label="360 degree view" onClick={() => openStudio360(gen.message_id)}>
                                      <span style={{ fontWeight: 700, fontSize: 12, letterSpacing: 0.3 }}>360°</span>
                                    </button>
                                  )}
                                  <button type="button" className="trs-canvas-icon-btn" title="Use camera photo" aria-label="Use camera photo" onClick={() => roomCameraRef.current?.click()}>
                                    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
                                  </button>
                                  {gen.image_url && (
                                    <button type="button" className="trs-canvas-icon-btn" title="Share" aria-label="Share" onClick={(e) => handleShareClick(e, gen.image_url, 'stylekro-room.png')}>
                                      <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
                                    </button>
                                  )}
                                </div>
                              )}
                            </div>
                          </section>

                          <section className="trs-applied">
                            <div className="trs-applied-head">
                              <h3>Applied Materials</h3>
                              {assignedCount > 0 && (
                                <button type="button" className="trs-edit-all" onClick={() => setTileAssignments({})}>Reset all</button>
                              )}
                            </div>
                            <div className="trs-applied-grid">
                              {TILE_ROOM_SURFACES.map((s) => {
                                const gid = tileAssignments[s.value];
                                const item = gid ? garments.find((g) => g.id === gid) : null;
                                const active = activeTileSurface === s.value;
                                return (
                                  <button
                                    key={s.value}
                                    type="button"
                                    className={`trs-applied-card${item ? ' filled' : ''}${active ? ' on' : ''}`}
                                    onClick={() => selectTileSurfaceAndScroll(s.value)}
                                  >
                                    <span className="trs-applied-top"><span className="trs-applied-ico" aria-hidden="true">{SURFACE_ICON[s.value]}</span>{s.label}</span>
                                    <span className="trs-applied-swatch">
                                      {item ? <img src={imgUrl(item.photo_url)} alt={item.title || 'Material'} loading="lazy" /> : <span className="trs-applied-empty">No change</span>}
                                    </span>
                                    {item && <span className="trs-applied-name">{item.title || 'Material'}</span>}
                                    {item ? (
                                      <span className="trs-applied-status applied">
                                        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20,6 9,17 4,12"/></svg>
                                        Applied
                                      </span>
                                    ) : null}
                                  </button>
                                );
                              })}
                            </div>
                          </section>

                          <section className="trs-picker trs-picker-v2">
                            <div className="trs-search-row">
                              <label className="trs-search">
                                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
                                <input type="search" placeholder="Search tiles & paint…" value={tileRoomSearch} onChange={(e) => setTileRoomSearch(e.target.value)} aria-label="Search tiles and paint" />
                              </label>
                              <button type="button" className="trs-filter-btn" aria-label="Filters">
                                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="4" y1="6" x2="20" y2="6"/><line x1="7" y1="12" x2="17" y2="12"/><line x1="10" y1="18" x2="14" y2="18"/></svg>
                              </button>
                            </div>
                            <div className="trs-cats" role="tablist" aria-label="Material categories">
                              {tileCats.map((c) => (
                                <button key={c} type="button" role="tab" aria-selected={tileCat === c} className={`trs-cat${tileCat === c ? ' on' : ''}`} onClick={() => setTileCat(c)}>{c}</button>
                              ))}
                            </div>
                            {tileList.length === 0 ? (
                              <p className="muted" style={{ textAlign: 'center', marginTop: 16 }}>{t('shop.noMatchingPieces')}</p>
                            ) : (
                              <div className="trs-prod-grid">
                                {tileList.map((g) => {
                                  const onActive = tileAssignments[activeTileSurface] === g.id;
                                  return (
                                    <div key={g.id} className={`trs-prod${onActive ? ' on' : ''}`}>
                                      <button type="button" className="trs-prod-media" onClick={() => assignTileToSurface(g.id)} title={onActive ? `Remove from ${activeSurfaceLabel}` : `Apply to ${activeSurfaceLabel}`}>
                                        <img src={imgUrl(g.photo_url)} alt={g.title || 'Material'} loading="lazy" decoding="async" />
                                        <span className={`trs-prod-check${onActive ? ' on' : ''}`} aria-hidden="true">
                                          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20,6 9,17 4,12"/></svg>
                                        </span>
                                      </button>
                                      <div className="trs-prod-info">
                                        <div className="trs-prod-name">{g.title || 'Material'}</div>
                                        {g.price ? (
                                          <div className="trs-prod-price">₹{g.price} <span>/ sqft</span></div>
                                        ) : tileCategoryOf(g) ? (
                                          <div className="trs-prod-cat">{tileCategoryOf(g)}</div>
                                        ) : null}
                                      </div>
                                    </div>
                                  );
                                })}
                              </div>
                            )}
                          </section>

                          {!genBusy && assignedCount > 0 && (
                            <StyleKroButton
                              floating
                              busy={generating}
                              disabled={generating}
                              label={(gen && (gen.status === 'fast' || gen.status === 'done')) ? 'Style New' : undefined}
                              count={assignedCount}
                              onClick={generateTileRoomInline}
                              ariaLabel="Style this room with StyleKro"
                            />
                          )}
                        </div>
                      );
                    })() : isTattooShop ? (() => {
                      const gen = combinedResult && combinedResult.kind === 'tattoo' ? combinedResult : null;
                      const genBusy = gen && (gen.status === 'pending' || gen.status === 'fast');
                      const genShot = gen && (gen.status === 'fast' || gen.status === 'done') ? gen.image_url : null;
                      const profileTattooPhotoUrl = profile?.photo_url ? imgUrl(bust(profile.photo_url, photoVersion)) : null;
                      const canvasImg = genShot || tattooPhotoUrl || profileTattooPhotoUrl;
                      const tattooList = list;
                      const tatDesign = tattooList.find((x) => x.id === tattooDesignId) || null;
                      const tatDesignUrl = tatDesign ? imgUrl(tattooDesignSrc(tatDesign)) : null;
                      const tatOverlayOn = !gen && !studio360 && !!tattooDesignId && !!canvasImg && !!tatDesignUrl;
                      return (
                        <div className="tat-studio">
                          <input
                            ref={tattooFileInputRef}
                            type="file"
                            accept="image/*,.heic,.heif"
                            capture="environment"
                            hidden
                            onChange={onTattooFileChange}
                          />
                          <section className="tat-stage">
                            <div className="tat-canvas" ref={tatCanvasRef}>
                              {studio360 ? (
                                <div className="trs-canvas-360">
                                  <Sheet360Viewer
                                    active={studio360}
                                    fetchSheet={fetchCombinedSheet360}
                                    onExit={closeStudio360}
                                    exitLabel="✕ Exit 360°"
                                  />
                                </div>
                              ) : canvasImg ? (
                                <img className="tat-canvas-img" src={canvasImg} alt="Tattoo preview" onClick={() => setLightbox(canvasImg)} />
                              ) : (
                                <div className="tat-canvas-empty">
                                  <div className="tat-empty-icon">
                                    <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
                                    <span className="tat-empty-badge" aria-hidden="true">+</span>
                                  </div>
                                </div>
                              )}
                              {tatOverlayOn && (
                                <div
                                  className="tat-overlay"
                                  style={{ left: `${tatXf.xPct}%`, top: `${tatXf.yPct}%`, width: `${tatXf.wPct}%`, transform: `translate(-50%, -50%) rotate(${tatXf.rot}deg)` }}
                                  onPointerDown={tatPointerDown('move')}
                                  onPointerMove={tatPointerMove}
                                  onPointerUp={tatPointerEnd}
                                  onPointerCancel={tatPointerEnd}
                                >
                                  <img
                                    className="tat-overlay-img"
                                    src={tatDesignUrl}
                                    alt=""
                                    draggable="false"
                                    onLoad={(e) => { const im = e.currentTarget; if (im.naturalWidth) tatNatRef.current = { w: im.naturalWidth, h: im.naturalHeight }; }}
                                  />
                                  <span
                                    className="tat-overlay-handle tat-handle-rotate"
                                    onPointerDown={tatPointerDown('rotate')}
                                    onPointerMove={tatPointerMove}
                                    onPointerUp={tatPointerEnd}
                                    onPointerCancel={tatPointerEnd}
                                    aria-label="Rotate tattoo"
                                  >
                                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/></svg>
                                  </span>
                                  <span
                                    className="tat-overlay-handle tat-handle-scale"
                                    onPointerDown={tatPointerDown('scale')}
                                    onPointerMove={tatPointerMove}
                                    onPointerUp={tatPointerEnd}
                                    onPointerCancel={tatPointerEnd}
                                    aria-label="Resize tattoo"
                                  >
                                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg>
                                  </span>
                                </div>
                              )}
                              {!gen && !studio360 && (
                                <button
                                  type="button"
                                  className="tat-cam-fab"
                                  onClick={() => tattooFileInputRef.current && tattooFileInputRef.current.click()}
                                  aria-label={tattooPhotoUrl ? 'Change photo' : 'Add photo'}
                                  title={tattooPhotoUrl ? 'Change photo' : 'Add photo'}
                                >
                                  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
                                </button>
                              )}
                              {!gen && !studio360 && tattooPhotoUrl && (
                                <button
                                  type="button"
                                  className="tat-cam-clear"
                                  onClick={clearTattooPhoto}
                                  aria-label="Use profile photo"
                                  title="Use profile photo"
                                >×</button>
                              )}
                              {gen && gen.status === 'pending' && (
                                <div className="tat-canvas-overlay"><TryonProgress label="Placing your tattoo…" /></div>
                              )}
                              {gen && gen.status === 'error' && (
                                <div className="tat-canvas-overlay"><div className="tryon-error">⚠ {gen.error}</div></div>
                              )}
                              {gen && gen.status === 'fast' && (
                                <div className="upgrade-hint trs-canvas-hint"><span className="upgrade-hint-spinner" aria-hidden="true" /><span className="upgrade-hint-title">Refining look</span></div>
                              )}
                              {gen && gen.status === 'done' && !studio360 && (
                                <div className="tat-canvas-actions">
                                  {gen.message_id && (
                                    <button type="button" className="tat-canvas-icon-btn" title="360° view" aria-label="360 degree view" onClick={() => openStudio360(gen.message_id)}>
                                      <span style={{ fontWeight: 700, fontSize: 12, letterSpacing: 0.3 }}>360°</span>
                                    </button>
                                  )}
                                  {gen.image_url && (
                                    <button type="button" className="tat-canvas-icon-btn" title="Share" aria-label="Share" onClick={(e) => handleShareClick(e, gen.image_url, 'stylekro-tattoo.png')}>
                                      <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
                                    </button>
                                  )}
                                </div>
                              )}
                            </div>
                          </section>

                          <section className="tat-designs">
                            <h3 className="tat-designs-title">Pick a tattoo</h3>
                            {tattooList.length === 0 ? (
                              <p className="muted" style={{ textAlign: 'center', marginTop: 16 }}>{t('shop.noMatchingPieces')}</p>
                            ) : (
                              <div className="tat-grid">
                                {tattooList.map((g) => {
                                  const onActive = tattooDesignId === g.id;
                                  return (
                                    <button
                                      type="button"
                                      key={g.id}
                                      className={`tat-card${onActive ? ' on' : ''}`}
                                      onClick={() => { const nid = onActive ? null : g.id; setTattooDesignId(nid); if (nid) { setTat({ xPct: 50, yPct: 42, wPct: 34, rot: 0 }); setCombinedResult(null); } }}
                                      title={onActive ? 'Deselect' : 'Select this tattoo'}
                                    >
                                      <img src={imgUrl(tattooDesignSrc(g))} alt={g.title || 'Tattoo'} loading="lazy" decoding="async" />
                                      {onActive && <span className="tat-card-check" aria-hidden="true">✓</span>}
                                      <span className="tat-card-name">{g.title || 'Tattoo'}</span>
                                    </button>
                                  );
                                })}
                              </div>
                            )}
                          </section>

                          {tattooDesignId && canvasImg && (
                            <p className="tat-hint">Drag to move • corner handle to resize • top handle to rotate, then tap StyleKro.</p>
                          )}

                          {!genBusy && tattooDesignId && (
                            <StyleKroButton
                              floating
                              busy={generating}
                              disabled={generating}
                              label={(gen && (gen.status === 'fast' || gen.status === 'done')) ? 'Style New' : undefined}
                              onClick={generateTattooInline}
                              ariaLabel="Place this tattoo with StyleKro"
                            />
                          )}
                        </div>
                      );
                    })() : (
                      <div className="sdv2-grid">
                        {list.map(renderGarmentCard)}
                      </div>
                    )}
                  </>
                )}

                {/* Floating action bar */}
                {selected.size > 0 && (() => {
                  const cats = selectedItems.map((g) => g.category_group || '').filter(Boolean);
                  const allUniqueCats = cats.length === selectedItems.length && new Set(cats).size === cats.length;
                  const canCombine = !isTailorShop && !isTileShop && selected.size >= 2 && allUniqueCats;
                  const canTileRoom = isTileShop && selected.size >= 1;
                  const canTailorGenerate = isTailorShop && !!selectedDesign && selectedFabrics.length > 0;
                  return (
                    <div className={`sdv2-fab-bar${isTailorShop ? ' tailor' : ''}`} role="region" aria-label={isTailorShop ? 'Tailor Stylekro actions' : 'Stylekro actions'}>
                      {!isTailorShop && <span className="sdv2-fab-count">{selected.size}</span>}
                      {isTailorShop ? (
                        <>
                          <span className="sdv2-fab-meta">
                            {selectedTailorLabel ? `${selectedTailorLabel} · ` : ''}
                            {selectedDesign ? '1 design selected' : 'Pick 1 design'}
                            {` · ${selectedFabrics.length} fabric${selectedFabrics.length === 1 ? '' : 's'}`}
                          </span>
                          <StyleKroButton
                            busy={generating}
                            disabled={generating || !canTailorGenerate}
                            label={(combinedResult && combinedResult.kind === 'tailor-outfit' && (combinedResult.status === 'fast' || combinedResult.status === 'done')) ? 'Style New' : undefined}
                            onClick={generateTailorSelections}
                            title={canTailorGenerate ? 'Generate each selected fabric with the chosen design' : 'Pick one design and at least one fabric'}
                          />
                        </>
                      ) : (
                        <>
                          {canTileRoom && (
                            <button
                              type="button"
                              className="sdv2-fab-btn primary"
                              disabled={generating}
                              onClick={() => confirmTileRoomTryon(true, selectedItems)}
                              title="Style a room with your selected materials"
                            >
                              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 10l9-5 9 5"/><path d="M5 10v8h14v-8"/><path d="M9 18v-4h6v4"/></svg>
                              Stylekro Room
                            </button>
                          )}
                          {canCombine && (
                            <button
                              type="button"
                              className="sdv2-fab-btn primary"
                              disabled={generating}
                              onClick={generateCombined}
                              title="Generate one image with all items on you"
                            >
                              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="12,2 15,8.5 22,9.3 17,14 18.2,21 12,17.8 5.8,21 7,14 2,9.3 9,8.5"/></svg>
                              Stylekro Combine
                            </button>
                          )}
                          <button
                            type="button"
                            className="sdv2-fab-btn secondary"
                            disabled={generating}
                            onClick={generateSeparate}
                            title="Try each item individually"
                          >
                            <StyleKroLockup />
                          </button>
                        </>
                      )}
                    </div>
                  );
                })()}
              </>
            );
          })() : <p className="error">{t('shop.notFound')}</p>
        )}

        {combinedResult && combinedResult.kind !== 'tailor-outfit' && combinedResult.kind !== 'tile-room' && combinedResult.kind !== 'tattoo' && (
          <div className="combined-result-overlay" onClick={() => setCombinedResult(null)}>
            <div className="combined-result-card" onClick={(e) => e.stopPropagation()}>
              <button className="combined-result-close" onClick={() => setCombinedResult(null)} aria-label="Close">×</button>
              <div className="combined-result-title">{combinedResult.kind === 'tile-room' ? 'Your styled room' : 'Your combined look'}</div>
              <div className="combined-result-subtitle">{combinedResult.titles}</div>
              <div className="combined-result-body">
                {combinedResult.status === 'pending' && <TryonProgress label={combinedResult.kind === 'tile-room' ? (combinedResult.ai ? 'AI is styling your room…' : 'Styling your room…') : 'Generating combined look…'} />}
                {(combinedResult.status === 'fast' || combinedResult.status === 'done') && (
                  <>
                    <img src={combinedResult.image_url} alt={combinedResult.kind === 'tile-room' ? 'Room styled with selected tiles' : 'Combined look on you'} onClick={() => setLightbox(combinedResult.image_url)} style={{ cursor: 'zoom-in' }} />
                    {combinedResult.status === 'fast' && (
                      <div className="upgrade-hint">
                        <span className="upgrade-hint-spinner" aria-hidden="true" />
                        <span className="upgrade-hint-title">Refining look</span>
                      </div>
                    )}
                  </>
                )}
                {combinedResult.status === 'error' && (
                  <div className="tryon-error">⚠ {combinedResult.error}</div>
                )}
              </div>
              {combinedResult.status === 'done' && combinedResult.message_id && (
                <div className="combined-result-footer">
                  <button
                    type="button"
                    className="bi-cta-secondary"
                    onClick={() => setSheet360MsgId(combinedResult.message_id)}
                  >
                    360° view
                  </button>
                </div>
              )}
            </div>
          </div>
        )}
      </main>
      {lightbox && (
        <div className="lightbox" onClick={() => setLightbox(null)}>
          <button className="lightbox-close" title="Close">×</button>
          <ZoomableImage src={lightbox} />
          <LightboxActions
            url={lightbox}
            filename="stylekro-tryon.png"
            onRotate360={combinedResult?.message_id
              ? (e) => { e.stopPropagation(); setLightbox(null); setSheet360MsgId(combinedResult.message_id); }
              : null}
          />
        </div>
      )}
      <Sheet360Modal
        open={!!sheet360MsgId && !studio360}
        fetchSheet={fetchCombinedSheet360}
        onClose={() => setSheet360MsgId(null)}
      />
      {tileRoomPrompt && (() => {
        const assignedItems = TILE_ROOM_SURFACES
          .map((s) => tileRoomPrompt.picks.find((g) => g.id === tileRoomPrompt.assignments[s.value]))
          .filter(Boolean);
        const heroItem = assignedItems[0] || tileRoomPrompt.picks[0] || null;
        const SURFACE_ICONS = {
          floor: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.2"/><rect x="14" y="3" width="7" height="7" rx="1.2"/><rect x="3" y="14" width="7" height="7" rx="1.2"/><rect x="14" y="14" width="7" height="7" rx="1.2"/></svg>),
          front_wall: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="4" y="3" width="16" height="18" rx="1.5"/><line x1="4" y1="9" x2="20" y2="9"/><line x1="4" y1="15" x2="20" y2="15"/></svg>),
          left_wall: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M5 4l10-1v18l-10-1z"/></svg>),
          right_wall: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M19 4l-10-1v18l10-1z"/></svg>),
        };
        const resetAll = () => setTileRoomPrompt((cur) => ({
          ...cur,
          error: '',
          assignments: TILE_ROOM_SURFACES.reduce((m, s) => ({ ...m, [s.value]: '' }), {}),
        }));
        return (
        <div className="tr2-overlay" onClick={() => setTileRoomPrompt(null)}>
          <div className="tr2-sheet" onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" aria-label="Style your room">
            <button type="button" className="tr2-close" aria-label="Close" onClick={() => setTileRoomPrompt(null)}>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 6L6 18"/><path d="M6 6l12 12"/></svg>
            </button>
            <header className="tr2-head">
              <h2 className="tr2-title">{t('room.styleYour')} <span className="tr2-title-accent">{t('room.room')}</span></h2>
            </header>

            <div className="tr2-preview">
              {(() => {
                const gen = combinedResult && combinedResult.kind === 'tile-room' ? combinedResult : null;
                const showResult = gen && gen.image_url && (gen.status === 'fast' || gen.status === 'done');
                if (showResult) {
                  return <img src={gen.image_url} alt="Styled room" />;
                }
                if (profile?.photo_url) {
                  return <img src={bust(imgUrl(profile.photo_url), photoVersion)} alt="Your photo" />;
                }
                return <div className="tr2-preview-placeholder" aria-hidden="true" />;
              })()}
              {combinedResult && combinedResult.kind === 'tile-room' && (combinedResult.status === 'pending' || combinedResult.status === 'fast') && (
                <div className={`tr2-preview-overlay${combinedResult.status === 'fast' ? ' subtle' : ''}`}>
                  <span className="tr2-spinner" aria-hidden="true" />
                  <span>{combinedResult.status === 'fast' ? t('room.refining') : t('room.stylingRoom')}</span>
                </div>
              )}
              {combinedResult && combinedResult.kind === 'tile-room' && combinedResult.status === 'error' && (
                <div className="tr2-preview-overlay error">⚠ {combinedResult.error}</div>
              )}
              <button type="button" className="tr2-preview-expand" aria-label="Expand preview" onClick={() => {
                const gen = combinedResult && combinedResult.kind === 'tile-room' ? combinedResult : null;
                const url = (gen && gen.image_url) || (profile?.photo_url ? bust(imgUrl(profile.photo_url), photoVersion) : null);
                if (url) setLightbox(url);
              }}>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
              </button>
            </div>

            <div className="tr2-grid">
              {TILE_ROOM_SURFACES.map((surface) => {
                const selectedId = tileRoomPrompt.assignments[surface.value] || '';
                const item = tileRoomPrompt.picks.find((g) => g.id === selectedId) || null;
                return (
                  <label key={surface.value} className={`tr2-card${item ? ' assigned' : ''}`}>
                    <div className="tr2-card-head">
                      <span className="tr2-card-icon">{SURFACE_ICONS[surface.value]}</span>
                      <span className="tr2-card-label">{surface.label.toUpperCase()}</span>
                      {item && (
                        <span className="tr2-card-check" aria-hidden="true">
                          <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
                        </span>
                      )}
                    </div>
                    <div className="tr2-card-body">
                      <div className="tr2-card-thumb">
                        {item ? (
                          <img src={`${API}/garments/${item.id}/photo`} alt={item.title || 'Material'} />
                        ) : (
                          <span className="tr2-card-thumb-empty" aria-hidden="true">
                            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
                          </span>
                        )}
                      </div>
                      <div className="tr2-card-info">
                        <div className="tr2-card-name">{item ? (item.title || t('room.untitled')) : t('room.noChange')}</div>
                        <span className="tr2-card-cta">
                          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4z"/></svg>
                          {item ? t('room.change') : t('room.selectMaterial')}
                        </span>
                      </div>
                    </div>
                    <select
                      className="tr2-card-select"
                      value={selectedId}
                      onChange={(e) => setTileRoomPrompt((cur) => ({
                        ...cur,
                        error: '',
                        assignments: { ...cur.assignments, [surface.value]: e.target.value },
                      }))}
                      aria-label={`${surface.label} material`}
                    >
                      <option value="">{t('room.leaveUnchanged')}</option>
                      {tileRoomPrompt.picks.map((g) => (
                        <option key={g.id} value={g.id}>{g.title || t('room.untitledItem')}</option>
                      ))}
                    </select>
                  </label>
                );
              })}
            </div>

            {tileRoomPrompt.error && <div className="tr2-error">⚠ {tileRoomPrompt.error}</div>}

            <div className="tr2-actions">
              <button type="button" className="tr2-btn ghost" onClick={resetAll}>
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>
                {t('room.resetAll')}
              </button>
              <StyleKroButton
                busy={generating}
                busyLabel={t('room.stylingShort')}
                disabled={generating}
                label={(combinedResult && combinedResult.kind === 'tile-room' && (combinedResult.status === 'fast' || combinedResult.status === 'done')) ? 'Style New' : undefined}
                onClick={() => confirmTileRoomTryon(false)}
              />
            </div>
          </div>
        </div>
        );
      })()}
      {false && pieceFabricPrompt && (
        <div className="tr2-overlay" onClick={() => setPieceFabricPrompt(null)}>
          <div className="tr2-sheet tailor-piece-sheet" onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" aria-label="Assign a fabric to each piece">
            <button type="button" className="tr2-close" aria-label="Close" onClick={() => setPieceFabricPrompt(null)}>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 6L6 18"/><path d="M6 6l12 12"/></svg>
            </button>
            <header className="tr2-head">
              <p className="tailor-piece-kicker">Customize your outfit</p>
              <h2 className="tr2-title">Choose fabric for each piece</h2>
              <p className="tr2-sub">{pieceFabricPrompt.design?.title || 'Selected design'}</p>
            </header>
            <div className="tr2-grid">
              {pieceFabricPrompt.slots.map((slot) => {
                const selectedId = pieceFabricPrompt.assignments[slot.key] || '';
                const item = pieceFabricPrompt.fabrics.find((g) => g.id === selectedId) || null;
                return (
                  <label key={slot.key} className={`tr2-card${item ? ' assigned' : ''}`}>
                    <div className="tr2-card-head">
                      <span className="tr2-card-label">{slot.label.toUpperCase()}</span>
                      {item && (
                        <span className="tr2-card-check" aria-hidden="true">
                          <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
                        </span>
                      )}
                    </div>
                    <div className="tr2-card-body">
                      <div className="tr2-card-thumb">
                        {item ? (
                          <img src={`${API}/garments/${item.id}/photo`} alt={item.title || 'Fabric'} />
                        ) : (
                          <span className="tr2-card-thumb-empty" aria-hidden="true">
                            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
                          </span>
                        )}
                      </div>
                      <div className="tr2-card-info">
                        <div className="tr2-card-name">{item ? (item.title || 'Untitled') : 'Choose fabric'}</div>
                        <span className="tr2-card-cta">
                          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4z"/></svg>
                          {item ? 'Change' : 'Select fabric'}
                        </span>
                      </div>
                    </div>
                    <select
                      className="tr2-card-select"
                      value={selectedId}
                      onChange={(e) => setPieceFabricPrompt((cur) => ({
                        ...cur,
                        error: '',
                        assignments: { ...cur.assignments, [slot.key]: e.target.value },
                      }))}
                      aria-label={`${slot.label} fabric`}
                    >
                      {pieceFabricPrompt.fabrics.map((g) => (
                        <option key={g.id} value={g.id}>{g.title || 'Untitled fabric'}</option>
                      ))}
                    </select>
                  </label>
                );
              })}
            </div>

            {pieceFabricPrompt.error && <div className="tr2-error">{pieceFabricPrompt.error}</div>}

            <div className="tr2-actions tailor-piece-actions">
              <button type="button" className="tr2-btn primary" disabled={generating} onClick={runPieceFabricTryon}>
                <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="12,2 15,8.5 22,9.3 17,14 18.2,21 12,17.8 5.8,21 7,14 2,9.3 9,8.5"/></svg>
                {generating ? 'Stitching…' : 'Stitch outfit'}
              </button>
            </div>
          </div>
        </div>
      )}
    </section>
  );
}
function ShopSettingsPage() {
  const { apiCall, t } = useAuth();
  const confirmAction = useConfirmDialog();
  const navigate = useNavigate();
  const [shop, setShop] = useState(null);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState('');
  const [savedFlash, setSavedFlash] = useState(false);
  const [linkCopied, setLinkCopied] = useState(false);

  const shareLink = shop
    ? `${window.location.protocol}//${window.location.host}${window.location.pathname}#/shops/${shop.id}`
    : '';

  async function copyShareLink() {
    if (!shareLink) return;
    try {
      await navigator.clipboard.writeText(shareLink);
      setLinkCopied(true);
      setTimeout(() => setLinkCopied(false), 1800);
    } catch {
      try {
        const ta = document.createElement('textarea');
        ta.value = shareLink; ta.style.position = 'fixed'; ta.style.opacity = '0';
        document.body.appendChild(ta); ta.select();
        document.execCommand('copy');
        document.body.removeChild(ta);
        setLinkCopied(true);
        setTimeout(() => setLinkCopied(false), 1800);
      } catch {}
    }
  }

  async function shareShop() {
    if (!shareLink) return;
    const data = {
      title: shop?.name ? `${shop.name} · StyleKro` : 'StyleKro Shop',
      text: shop?.name
        ? `Explore ${shop.name} on StyleKro and try on any piece with AI before you buy.`
        : 'Explore this shop on StyleKro and try on any piece with AI before you buy.',
      url: shareLink,
    };
    if (navigator.share) {
      try { await navigator.share(data); return; } catch {}
    }
    copyShareLink();
  }

  const [name, setName] = useState('');
  const [description, setDescription] = useState('');
  const [location, setLocation] = useState('');
  const [phone, setPhone] = useState('');
  const [category, setCategory] = useState('');
  const [isPublic, setIsPublic] = useState(true);
  const [categories, setCategories] = useState([]);
  // Tailor shops: which garment-piece options the buyer can request.
  const [tailorPieces, setTailorPieces] = useState([]);
  // Tailor shops: gender focus ('' = both/all, 'men', 'women') filters the
  // stitching-option groups the seller (and buyer) sees.
  const [shopGender, setShopGender] = useState('');

  // Banner upload
  const bannerInputRef = useRef(null);
  const [bannerVersion, setBannerVersion] = useState(0);
  const [bannerBusy, setBannerBusy] = useState(false);

  async function uploadBannerFile(file) {
    if (!file || !shop) return;
    setBannerBusy(true);
    setError('');
    try {
      const fd = new FormData();
      fd.append('photo', file);
      const s = await apiCall(`/shops/${shop.id}/banner`, { method: 'POST', body: fd });
      setBannerVersion(Date.now());
      setShop(s);
    } catch (err) {
      setError(err.message || 'Could not upload banner.');
    } finally {
      setBannerBusy(false);
    }
  }

  async function onBannerPick(e) {
    const file = e.target.files && e.target.files[0];
    e.target.value = '';
    await uploadBannerFile(file);
  }

  function pickBannerNative() {
    if (bannerBusy) return;
    nativePickFiles(false)
      .then((files) => { if (files && files[0]) uploadBannerFile(files[0]); })
      .catch((err) => setError(err && err.message ? err.message : 'Could not pick photo.'));
  }

  async function removeBanner() {
    if (!shop || !shop.has_banner) return;
    if (!(await confirmAction(t('shop.confirmRemoveBanner'), { confirmLabel: t('common.delete'), tone: 'danger' }))) return;
    setBannerBusy(true);
    setError('');
    try {
      const s = await apiCall(`/shops/${shop.id}/banner`, { method: 'DELETE' });
      setBannerVersion(Date.now());
      setShop(s);
    } catch (err) {
      setError(err.message || 'Could not remove banner.');
    } finally {
      setBannerBusy(false);
    }
  }

  // Inventory snapshot for stats + bulk uploader
  const [garments, setGarments] = useState([]);
  const bulkInputRef = useRef(null);
  const [queue, setQueue] = useState([]);
  const [uploading, setUploading] = useState(false);

  // Google Places Autocomplete on the Location input.
  const locationInputRef = useRef(null);
  const autocompleteRef = useRef(null);
  const autocompleteNodeRef = useRef(null);
  const nameRef = useRef('');
  const descriptionRef = useRef('');
  const phoneRef = useRef('');
  const categoryRef = useRef('');
  const [placesLoading, setPlacesLoading] = useState(false);
  const [placesError, setPlacesError] = useState('');
  const [locating, setLocating] = useState(false);
  useEffect(() => { nameRef.current = name; }, [name]);
  useEffect(() => { descriptionRef.current = description; }, [description]);
  useEffect(() => { phoneRef.current = phone; }, [phone]);
  useEffect(() => { categoryRef.current = category; }, [category]);

  async function locateMe() {
    setPlacesError('');
    if (!navigator.geolocation) {
      setPlacesError('Geolocation is not supported by this browser.');
      return;
    }
    setLocating(true);
    try {
      const pos = await new Promise((resolve, reject) => {
        navigator.geolocation.getCurrentPosition(resolve, reject, {
          enableHighAccuracy: true,
          timeout: 12000,
          maximumAge: 60000,
        });
      });
      const { latitude: lat, longitude: lng } = pos.coords;
      const google = await loadGoogleMaps();
      const geocoder = new google.maps.Geocoder();
      const result = await new Promise((resolve, reject) => {
        geocoder.geocode({ location: { lat, lng } }, (results, status) => {
          if (status === 'OK' && results && results[0]) resolve(results);
          else reject(new Error(`Geocoder failed: ${status}`));
        });
      });
      // Prefer an establishment/POI if Google returned one (closest shop), else street address.
      const poi = result.find((r) => (r.types || []).some((t) => t === 'establishment' || t === 'point_of_interest'));
      const best = poi || result[0];
      setLocation(best.formatted_address || `${lat.toFixed(5)}, ${lng.toFixed(5)}`);
    } catch (err) {
      const msg = err && err.code === 1
        ? 'Permission denied. Allow location access in your browser to use "Locate me".'
        : (err && err.message) || 'Could not get your location.';
      setPlacesError(msg);
    } finally {
      setLocating(false);
    }
  }

  useEffect(() => {
    if (loading) return; // wait until input is rendered
    if (!locationInputRef.current) return;
    // If the autocomplete is bound to a stale DOM node (e.g. user moved
    // from the onboarding form to the edit form), tear it down and re-attach.
    if (autocompleteRef.current) {
      if (autocompleteNodeRef.current === locationInputRef.current) return;
      try { window.google?.maps?.event?.clearInstanceListeners?.(autocompleteRef.current); } catch {}
      autocompleteRef.current = null;
      autocompleteNodeRef.current = null;
    }
    let cancelled = false;
    setPlacesLoading(true);
    loadGoogleMaps()
      .then((google) => {
        if (cancelled || !locationInputRef.current) return;
        const ac = new google.maps.places.Autocomplete(locationInputRef.current, {
          fields: [
            'name',
            'formatted_address',
            'address_components',
            'geometry',
            'editorial_summary',
            'types',
            'website',
            'international_phone_number',
          ],
          types: ['establishment'],
        });
        autocompleteNodeRef.current = locationInputRef.current;
        autocompleteRef.current = ac;
        ac.addListener('place_changed', () => {
          const place = ac.getPlace();
          if (!place) return;
          // Always update location to the formatted address (or name fallback).
          const addr = place.formatted_address || place.name || '';
          if (addr) setLocation(addr);
          // Auto-fill shop name only if empty.
          if (place.name && !nameRef.current.trim()) setName(place.name);
          // Auto-fill description from editorial summary if empty.
          const summary = place.editorial_summary && place.editorial_summary.overview;
          if (summary && !descriptionRef.current.trim()) setDescription(summary);
          // Auto-fill phone if empty.
          if (place.international_phone_number && !phoneRef.current.trim()) {
            setPhone(place.international_phone_number);
          }
          // Auto-fill shop category from Google Places types if empty.
          if (!categoryRef.current && Array.isArray(place.types)) {
            const guess = placeTypesToShopCategory(place.types);
            if (guess) setCategory(guess);
          }
        });
      })
      .catch((err) => {
        if (!cancelled) setPlacesError(err.message || 'Could not load location autocomplete.');
      })
      .finally(() => { if (!cancelled) setPlacesLoading(false); });
    return () => { cancelled = true; };
  }, [loading, shop]);

  const reloadGarments = useCallback(async (shopId) => {
    try {
      const g = await apiCall(`/shops/${shopId}/garments`);
      setGarments(sortByCreatedDesc(g || []));
    } catch {}
  }, [apiCall]);

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const s = await apiCall('/shops/me');
        if (cancelled) return;
        setShop(s);
        if (s) {
          setName(s.name || '');
          setDescription(s.description || '');
          setLocation(s.location || '');
          setCategory(s.category || '');
          setPhone(s.phone || '');
          setIsPublic(s.is_public !== false);
          setTailorPieces(Array.isArray(s.tailor_pieces) ? s.tailor_pieces : []);
          setShopGender(s.shop_gender || '');
          const g = await apiCall(`/shops/${s.id}/garments`);
          if (!cancelled) setGarments(sortByCreatedDesc(g || []));
        }
      } catch (e) {
        if (!cancelled) setError(e.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    apiCall('/prompts/categories')
      .then((r) => { if (!cancelled) setCategories(r?.categories || []); })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [apiCall]);

  async function saveShop(e) {
    e.preventDefault();
    setError('');
    if (!name.trim()) return setError(t('shop.nameRequired'));
    if (isPublic && !category) return setError(t('shop.categoryRequired'));
    if (category === 'tailor' && !shopGender) return setError(t('shop.genderRequired'));
    setSaving(true);
    try {
      const path = shop ? `/shops/${shop.id}` : '/shops';
      const method = shop ? 'PUT' : 'POST';
      const s = await apiCall(path, {
        method,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name: name.trim(),
          description: description.trim() || null,
          location: location.trim() || null,
          category: category || null,
          phone: phone.trim() || null,
          is_public: isPublic,
          tailor_pieces: category === 'tailor' ? tailorPieces : null,
          shop_gender: category === 'tailor' ? shopGender : null,
        }),
      });
      setShop(s);
      if (s) setIsPublic(s.is_public !== false);
      setSavedFlash(true);
      setTimeout(() => setSavedFlash(false), 1800);
    } catch (e) {
      setError(e.message);
    } finally {
      setSaving(false);
    }
  }

  // ----- Bulk upload (mirrors ManageShopPage logic) -----
  function addFilesToQueue(files) {
    const arr = Array.from(files || []).filter((f) => f.type.startsWith('image/'));
    if (arr.length === 0) return;
    const items = arr.map((f) => {
      // Title = the original file name (extension removed), shown as-is.
      const base = (f.name || '').replace(/\.[^.]+$/, '').trim();
      return {
        key: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
        file: f,
        preview: URL.createObjectURL(f),
        title: base || 'Untitled',
        price: '',
        description: '',
        status: 'ready',
      };
    });
    setQueue((cur) => [...cur, ...items]);
  }

  function onBulkPick(e) {
    addFilesToQueue(e.target.files);
    if (bulkInputRef.current) bulkInputRef.current.value = '';
  }
  function onDropFiles(e) {
    e.preventDefault();
    addFilesToQueue(e.dataTransfer?.files);
  }
  function updateQueueItem(key, patch) {
    setQueue((cur) => cur.map((q) => (q.key === key ? { ...q, ...patch } : q)));
  }
  function removeQueueItem(key) {
    setQueue((cur) => cur.filter((q) => q.key !== key));
  }
  function clearQueue() { setQueue([]); }

  async function uploadAll() {
    if (!shop) return setError(t('shop.saveFirst'));
    if (queue.length === 0) return;
    setError('');
    setUploading(true);
    for (const item of queue) {
      if (item.status === 'done') continue;
      if (!item.title.trim()) {
        updateQueueItem(item.key, { status: 'error', error: 'Title is required' });
        continue;
      }
      updateQueueItem(item.key, { status: 'uploading', error: undefined });
      try {
        const fd = new FormData();
        fd.append('title', item.title.trim());
        if (item.price) fd.append('price', item.price);
        if (item.description) fd.append('description', item.description);
        fd.append('photo', item.file);
        await apiCall(`/shops/${shop.id}/garments`, { method: 'POST', body: fd });
        updateQueueItem(item.key, { status: 'done' });
      } catch (err) {
        updateQueueItem(item.key, { status: 'error', error: err.message });
      }
    }
    setUploading(false);
    setQueue((cur) => cur.filter((q) => q.status !== 'done'));
    if (shop) reloadGarments(shop.id);
  }

  return (
    <section className="screen shop-lux-screen">
      <NavBar active="settings" />
      <main className="settings-main shop-lux">
        {loading ? (
          <p className="muted">{t('boot.loading')}</p>
        ) : (
          <>
          <div className="shop-lux-hero">
            <div className="shop-lux-hero-eyebrow">{t('shop.eyebrowStudio')}</div>
            <div className="shop-lux-hero-row">
              <h1 className="shop-lux-hero-title">{t('shop.shopDetailsTitle')} <span className="shop-lux-hero-grad">{t('shop.shopDetailsTitleGrad')}</span></h1>
              {shop && (
                <button
                  type="button"
                  className="shop-lux-hero-share"
                  onClick={shareShop}
                  title="Share shop link"
                >
                  {linkCopied ? t('shop.copied') : t('shop.share')}
                </button>
              )}
            </div>
          </div>
          <form className="settings-card shop-lux-card" onSubmit={saveShop}>
            <div className="settings-section shop-banner-section">
              <label className="field-label">{t('shop.banner')}</label>
              {!shop ? (
                <div className="shop-banner-uploader is-locked" aria-disabled="true">
                  <div className="shop-banner-empty">
                    <span className="shop-banner-icon">📷</span>
                    <strong>{t('shop.bannerLockedTitle')}</strong>
                    <small>{t('shop.bannerLockedHint')}</small>
                  </div>
                </div>
              ) : (
                <div
                  className={`shop-banner-uploader${shop.has_banner ? ' has-banner' : ''}`}
                  style={shop.has_banner ? { backgroundImage: `url(${API}/shops/${shop.id}/banner?v=${bannerVersion})` } : undefined}
                  onClick={() => {
                    if (bannerBusy) return;
                    if (hasNativePicker()) { pickBannerNative(); return; }
                    if (bannerInputRef.current) bannerInputRef.current.click();
                  }}
                  role="button"
                  tabIndex={0}
                >
                  <input
                    ref={bannerInputRef}
                    type="file"
                    accept="image/*"
                    hidden
                    onChange={onBannerPick}
                  />
                  {!shop.has_banner && (
                    <div className="shop-banner-empty">
                      <span className="shop-banner-icon">📷</span>
                      <strong>{t('shop.uploadBanner')}</strong>
                      <small>{t('shop.bannerSubtitle')}</small>
                    </div>
                  )}
                  {shop.has_banner && (
                    <div className="shop-banner-overlay">
                      <button
                        type="button"
                        className="shop-banner-btn"
                        onClick={(ev) => {
                          ev.stopPropagation();
                          if (bannerBusy) return;
                          if (hasNativePicker()) { pickBannerNative(); return; }
                          if (bannerInputRef.current) bannerInputRef.current.click();
                        }}
                        disabled={bannerBusy}
                      >{bannerBusy ? t('shop.working') : t('shop.change')}</button>
                      <button
                        type="button"
                        className="shop-banner-btn danger"
                        onClick={(ev) => { ev.stopPropagation(); removeBanner(); }}
                        disabled={bannerBusy}
                      >{t('shop.remove')}</button>
                    </div>
                  )}
                </div>
              )}
            </div>
            <div className="settings-section">
              <label className="field-label">{t('shop.address')}</label>
              <div className="location-row">
                <div className="shop-icon-input-wrap shop-address-input-wrap">
                  <svg className="shop-input-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                    <path d="M20 10c0 5-8 11-8 11S4 15 4 10a8 8 0 1 1 16 0Z" />
                    <circle cx="12" cy="10" r="3" />
                  </svg>
                  <input
                    ref={locationInputRef}
                    type="text"
                    value={location}
                    onChange={(e) => setLocation(e.target.value)}
                    onKeyDown={(e) => { if (e.key === 'Enter') e.preventDefault(); }}
                    placeholder={placesLoading ? t('shop.placesLoading') : t('shop.searchMapsFull')}
                    autoComplete="off"
                  />
                </div>
                <button
                  type="button"
                  className="btn btn-ghost small locate-btn"
                  onClick={locateMe}
                  disabled={locating}
                  title={t('shop.useMyLocation')}
                >
                  {locating ? t('shop.locating') : ('📍 ' + t('shop.locate'))}
                </button>
              </div>
              {placesError && (
                <small className="muted" style={{ display: 'block', marginTop: 4 }}>
                  {t('shop.placeUnavailable')} {placesError}
                </small>
              )}
              <label className="field-label" style={{ marginTop: 12 }}>{t('shop.shopName')}</label>
              <div className="shop-icon-input-wrap shop-name-input-wrap">
                <svg className="shop-input-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                  <path d="M3 9l1.3-4A2 2 0 0 1 6.2 3h11.6a2 2 0 0 1 1.9 2L21 9" />
                  <path d="M4 9h16" />
                  <path d="M5 9v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9" />
                  <path d="M9 21v-6h6v6" />
                </svg>
                <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder={t('shop.namePlaceholder')} />
              </div>
              <label className="field-label" style={{ marginTop: 12 }}>{t('shop.phone')}</label>
              <div className="phone-input-wrap">
                <svg className="phone-input-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                  <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.68 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.32 1.85.55 2.81.68A2 2 0 0 1 22 16.92Z"/>
                </svg>
                <input
                  type="tel"
                  value={phone}
                  onChange={(e) => setPhone(e.target.value)}
                  placeholder="+91 98765 43210"
                  autoComplete="tel"
                  inputMode="tel"
                  pattern="[+0-9\s\-()]*"
                />
              </div>
            </div>

            <div className="shop-lux-row2">
            <div className="settings-section">
              <div className="shop-lux-type-row">
                <label className="field-label" style={{ margin: 0 }}>{t('shop.shopType')}</label>
                <select
                  className="shop-lux-select"
                  value={category}
                  aria-required={isPublic ? 'true' : 'false'}
                  onChange={(e) => {
                    const nextCategory = e.target.value;
                    setCategory(nextCategory);
                    if (nextCategory !== 'tailor') {
                      setShopGender('');
                      setTailorPieces([]);
                    }
                  }}
                >
                  <option value="">{t('shop.chooseShopType')}</option>
                  {categories.map((c) => (
                    <option key={c} value={c}>{shopCategoryLabel(c)}</option>
                  ))}
                </select>
              </div>
            </div>

            {category === 'tailor' && (
            <div className="settings-section">
              <label className="field-label" style={{ marginBottom: 8 }}>{t('shop.genderFocus')}</label>
              <div className="sdv2-filter-chips shop-gender-choice-row" role="radiogroup" aria-label="Gender focus" style={{ marginBottom: 8 }}>
                {[{ value: 'men', label: t('shop.genderMen') }, { value: 'women', label: t('shop.genderWomen') }].map((g) => (
                  <button
                    key={g.value}
                    type="button"
                    role="radio"
                    aria-checked={shopGender === g.value}
                    className={`sdv2-fpill small shop-gender-choice${shopGender === g.value ? ' on' : ''}`}
                    onClick={() => {
                      setShopGender(g.value);
                      setTailorPieces((cur) => cur.filter((v) => {
                        const opt = TAILOR_PIECE_OPTIONS.find((o) => o.value === v);
                        return opt && (opt.group || 'men') === g.value;
                      }));
                    }}
                  >
                    <svg className="shop-gender-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                      <path d="M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" />
                      <path d="M4 21a8 8 0 0 1 16 0" />
                    </svg>
                    {g.label}
                  </button>
                ))}
              </div>
              <p className="muted" style={{ marginTop: 0, marginBottom: 0, fontSize: 13 }}>
                {t('shop.genderRequired')}
              </p>
            </div>
            )}

            <div className="settings-section">
              <div className="visibility-toggle shop-lux-vis" role="radiogroup" aria-label="Shop visibility">
                <label className={`vis-opt shop-lux-vis-card${isPublic ? ' active' : ''}`}>
                  <input type="radio" name="vis" checked={isPublic} onChange={() => setIsPublic(true)} />
                  <span className="vis-opt-icon" aria-hidden>🌍</span>
                  <span className="vis-opt-title">{t('shop.public')}</span>
                </label>
                <label className={`vis-opt shop-lux-vis-card${!isPublic ? ' active' : ''}`}>
                  <input type="radio" name="vis" checked={!isPublic} onChange={() => setIsPublic(false)} />
                  <span className="vis-opt-icon" aria-hidden>🔒</span>
                  <span className="vis-opt-title">{t('shop.private')}</span>
                </label>
              </div>
            </div>
            </div>

            <div className="settings-section">
              <label className="field-label">{t('shop.description')}</label>
              <textarea rows="3" value={description} onChange={(e) => setDescription(e.target.value)} placeholder="e.g. Hand-finished couture pieces inspired by Indian craftsmanship and modern silhouettes." />
            </div>

            {error && <p className="error">{error}</p>}

            <div className="settings-footer shop-lux-footer">
              <button type="submit" className="shop-lux-cta" disabled={saving}>
                {saving ? t('shop.saving') : (shop ? t('shop.save') : t('shop.publish'))}
              </button>
              {savedFlash && <span className="muted small">{t('shop.saved')}</span>}
            </div>
          </form>
          </>
        )}
      </main>
    </section>
  );
}

// ===== Manage Shop page (shopkeeper) =====
// Tailor-specific constants & helpers live in web/shops/tailor.js. The local
// aliases below keep the rest of this file readable without touching every
// call site.
const _TAILOR_REG = (_ShopCategories && _ShopCategories.byKey && _ShopCategories.byKey.tailor) || {};
const TAILOR_KIND_OPTIONS = _TAILOR_REG.KIND_OPTIONS || [];
const TAILOR_PIECE_OPTIONS = _TAILOR_REG.PIECE_OPTIONS || [];
const TAILOR_PIECE_SLOTS = _TAILOR_REG.PIECE_SLOTS || {};
const TAILOR_SLOT_LABELS = _TAILOR_REG.SLOT_LABELS || {};
const TAILOR_PRIMARY_SLOT_KEYS = _TAILOR_REG.PRIMARY_SLOT_KEYS || [];
const tailorSlotsForOption = _TAILOR_REG.slotsForOption || (() => []);
const tailorPresetForSlots = _TAILOR_REG.presetForSlots || (() => '');

function TailorPieceIcon({ piece }) {
  if (piece === 'pant' || piece === 'pajama') {
    return <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M7 3h10l-1.2 18h-3.2L12 10.5 11.4 21H8.2z"/><path d="M9 3v5"/><path d="M15 3v5"/></svg>;
  }
  if (piece === 'coat' || piece === 'blazer' || piece === 'sherwani') {
    return <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M8 3l4 3 4-3 4 4-2.5 3V21h-11V10L4 7z"/><path d="M12 6v15"/><path d="M9.5 8.5L12 11l2.5-2.5"/><path d="M8.5 14h2"/><path d="M13.5 14h2"/></svg>;
  }
  if (piece === 'waistcoat') {
    return <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M8 4l4 3 4-3 2 3-2 14H8L6 7z"/><path d="M12 7v14"/><path d="M9 10l3 3 3-3"/><path d="M9.5 15h1"/><path d="M13.5 15h1"/></svg>;
  }
  if (piece === 'kurta') {
    return <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M8 4l4 2 4-2 3 4-2 2v11H7V10L5 8z"/><path d="M12 6v6"/><path d="M9 13h6"/></svg>;
  }
  return <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M7 4l3 2 2-1 2 1 3-2 3 4-3 2v10H7V10L4 8z"/><path d="M10 6v14"/><path d="M14 6v14"/></svg>;
}

function AssetKindToggle({ value = 'design', onChange, disabled = false, options = TAILOR_KIND_OPTIONS, ariaLabel = 'Item type' }) {
  return (
    <div className="asset-kind-toggle" role="group" aria-label={ariaLabel}>
      {options.map((opt) => (
        <button
          key={opt.value}
          type="button"
          className={`asset-kind-btn${value === opt.value ? ' on' : ''}`}
          onClick={() => onChange(opt.value)}
          disabled={disabled}
          aria-pressed={value === opt.value}
        >
          {opt.label}
        </button>
      ))}
    </div>
  );
}

// Three cascading <select>s for gender → category_group → subcategory backed
// by the shared `taxonomy` map. Used by the bulk re-categorise picker and
// every inline item-edit form so the dropdown logic lives in ONE place.
//
// `value` shape: { gender, group, sub }. `onChange` is fired with the FULL
// new shape (parent merges into whatever its state happens to be).
function TaxonomySelect({ taxonomy, gender, group, sub, onChange, labels, disabled = false }) {
  const tax = taxonomy || {};
  const L = labels || { gender: 'Gender…', group: 'Group…', sub: 'Subcategory…' };
  return (
    <>
      <select
        value={gender || ''}
        onChange={(e) => onChange({ gender: e.target.value, group: '', sub: '' })}
        disabled={disabled}
      >
        <option value="">{L.gender}</option>
        {Object.keys(tax).map((g) => <option key={g} value={g}>{g}</option>)}
      </select>
      <select
        value={group || ''}
        onChange={(e) => onChange({ gender, group: e.target.value, sub: '' })}
        disabled={disabled || !gender}
      >
        <option value="">{L.group}</option>
        {gender && Object.keys(tax[gender] || {}).map((grp) => <option key={grp} value={grp}>{grp}</option>)}
      </select>
      <select
        value={sub || ''}
        onChange={(e) => onChange({ gender, group, sub: e.target.value })}
        disabled={disabled || !group}
      >
        <option value="">{L.sub}</option>
        {gender && group && (tax[gender]?.[group] || []).map((s) => <option key={s} value={s}>{s}</option>)}
      </select>
    </>
  );
}

// ---------------------------------------------------------------------------
// Category-aware wrappers — render nothing for shops that don't customise the
// item type / have no extra fields, so call sites stay branch-free.
// ---------------------------------------------------------------------------
function CategoryItemTypeToggle({ shop, value, onChange, disabled = false, labeled = false }) {
  const meta = shopMeta(shop);
  if (!meta.kindOptions) return null;
  const resolvedValue = value || meta.defaultAssetKind;
  const useSelectPicker = meta.kindOptions.length > 3 || meta.kindOptions.some((opt) => opt.label.length > 10);
  const toggle = useSelectPicker ? (
    <div className="asset-kind-select-wrap">
      <select
        className="asset-kind-select"
        value={resolvedValue}
        onChange={(e) => onChange(e.target.value)}
        disabled={disabled}
        aria-label={meta.toggleAriaLabel || 'Item type'}
      >
        {meta.kindOptions.map((opt) => (
          <option key={opt.value} value={opt.value}>{opt.label}</option>
        ))}
      </select>
      <span className="asset-kind-select-icon" aria-hidden="true">▾</span>
    </div>
  ) : (
    <AssetKindToggle
      value={resolvedValue}
      onChange={onChange}
      disabled={disabled}
      options={meta.kindOptions}
      ariaLabel={meta.toggleAriaLabel || 'Item type'}
    />
  );
  if (!labeled) return toggle;
  return (
    <div className="ge-stack-field">
      <span className="ge-stack-label">Item type</span>
      {toggle}
    </div>
  );
}

function CategoryExtraFields({ shop, values, onChange, disabled = false, variant = 'inline' }) {
  const meta = shopMeta(shop);
  if (!meta.extraFields.length) return null;
  if (variant === 'labeled') {
    return (
      <div className="ge-grid-2">
        {meta.extraFields.map((f) => (
          <label key={f.name} className="ge-input">
            <span>{f.label}</span>
            <input
              type="text"
              value={(values && values[f.name]) || ''}
              onChange={(e) => onChange(f.name, e.target.value)}
              placeholder={f.placeholder}
              disabled={disabled}
            />
          </label>
        ))}
      </div>
    );
  }
  return (
    <>
      {meta.extraFields.map((f) => (
        <input
          key={f.name}
          type="text"
          placeholder={`${f.label} (e.g. ${f.placeholder})`}
          value={(values && values[f.name]) || ''}
          onChange={(e) => onChange(f.name, e.target.value)}
          disabled={disabled}
        />
      ))}
    </>
  );
}

// Per-category detail fields persisted to the matching detail table and sent
// as the `details_json` form part. Values live under `values.details`.
// `variant="labeled"` renders each field as a full uppercase-labelled control
// (matching the seller item-detail edit card); the default `inline` variant
// renders bare placeholder-only controls for compact queue rows.
function CategoryDetailFields({ shop, values, onChange, disabled = false, variant = 'inline' }) {
  const meta = shopMeta(shop);
  const fields = meta.detailFields || [];
  if (!fields.length) return null;
  const details = (values && values.details) || {};
  const renderControl = (f) => (
    f.type === 'select' && Array.isArray(f.options) ? (
      <select
        value={details[f.name] == null ? '' : details[f.name]}
        onChange={(e) => onChange(f.name, e.target.value)}
        disabled={disabled}
      >
        {f.options.map((opt) => (
          <option key={opt.value} value={opt.value}>{opt.label}</option>
        ))}
      </select>
    ) : (
      <input
        type={f.type || 'text'}
        placeholder={variant === 'labeled' ? (f.placeholder || '') : (f.placeholder ? `${f.label} (${f.placeholder})` : f.label)}
        value={details[f.name] == null ? '' : details[f.name]}
        onChange={(e) => onChange(f.name, e.target.value)}
        disabled={disabled}
      />
    )
  );
  if (variant === 'labeled') {
    return (
      <>
        {fields.map((f) => (
          <label key={f.name} className="ge-input">
            <span>{f.label}{f.optional ? ' (Optional)' : ''}</span>
            {renderControl(f)}
          </label>
        ))}
      </>
    );
  }
  return (
    <>
      {fields.map((f) => (
        <React.Fragment key={f.name}>{renderControl(f)}</React.Fragment>
      ))}
    </>
  );
}

function ManageShopPage() {
  const { apiCall, t } = useAuth();
  const confirmAction = useConfirmDialog();
  const navigate = useNavigate();
  const [shop, setShop] = useState(null);
  const [loading, setLoading] = useState(true);
  const [garmentsLoading, setGarmentsLoading] = useState(true);
  const [garments, setGarments] = useState([]);
  const [error, setError] = useState('');
  const [saving, setSaving] = useState(false);

  const [name, setName] = useState('');
  const [description, setDescription] = useState('');
  const [location, setLocation] = useState('');
  const [phone, setPhone] = useState('');
  const [category, setCategory] = useState('');
  const [isPublic, setIsPublic] = useState(true);
  const [categories, setCategories] = useState([]);

  // Inventory view state
  const [search, setSearch] = useState('');
  const [view, setView] = useState('grid'); // 'grid' | 'list'
  const [linkCopied, setLinkCopied] = useState(false);
  const [filterGender, setFilterGender] = useState('');
  const [filterGroup, setFilterGroup] = useState('');
  const [filterSub, setFilterSub] = useState('');
  const [filterColor, setFilterColor] = useState('');
  const [filterAssetKind, setFilterAssetKind] = useState('');
  const [filterVisibility, setFilterVisibility] = useState('all'); // all | public | hidden | uncategorized
  const [filtersOpen, setFiltersOpen] = useState(false);
  useEffect(() => {
    document.body.classList.toggle('filters-open', filtersOpen);
    return () => document.body.classList.remove('filters-open');
  }, [filtersOpen]);

  // Bulk upload queue: [{ key, file, preview, title, price, description, status, error? }]
  const bulkInputRef = useRef(null);
  const [queue, setQueue] = useState([]);
  const [uploading, setUploading] = useState(false);

  // Google Places Autocomplete on the Location input (first-time onboarding form).
  const locationInputRef = useRef(null);
  const autocompleteRef = useRef(null);
  const autocompleteNodeRef = useRef(null);
  const nameRef = useRef('');
  const descriptionRef = useRef('');
  const phoneRef = useRef('');
  const categoryRef = useRef('');
  const [placesLoading, setPlacesLoading] = useState(false);
  const [placesError, setPlacesError] = useState('');
  useEffect(() => { nameRef.current = name; }, [name]);
  useEffect(() => { descriptionRef.current = description; }, [description]);
  useEffect(() => { phoneRef.current = phone; }, [phone]);
  useEffect(() => { categoryRef.current = category; }, [category]);

  // Inline editing
  const [editingId, setEditingId] = useState(null);
  const [editForm, setEditForm] = useState({ title: '', price: '', description: '', color: '', item_code: '', asset_kind: 'garment', photo: null, preview: '', gender: '', category_group: '', subcategory: '' });
  const editFileRef = useRef(null);
  const [savingEdit, setSavingEdit] = useState(false);

  // Backfill auto-categorization
  const [backfilling, setBackfilling] = useState(false);
  const [backfillMsg, setBackfillMsg] = useState('');

  // Taxonomy (gender → group → subcategory) for the manual category dropdowns
  const [taxonomy, setTaxonomy] = useState({});

  // Bulk selection of inventory items for re-categorisation
  const [selectedIds, setSelectedIds] = useState(() => new Set());
  const [zoomImage, setZoomImage] = useState(null);
  const [bulkBusy, setBulkBusy] = useState(false);
  const [bulkMsg, setBulkMsg] = useState('');
  const [bulkPicker, setBulkPicker] = useState({ open: false, gender: '', group: '', sub: '' });
  // Sequential per-item Status share queue. Each entry is { url, caption, title }.
  // WhatsApp keeps only ONE caption per share, so to give every status its own
  // image + its own try-on link we share items one at a time and let the user
  // post each before tapping "Share next item".
  const [statusShareQueue, setStatusShareQueue] = useState([]);

  const shopCat = shopMeta(shop);
  const isTailorShop = shopCat.key === 'tailor';
  const usesGarmentTaxonomy = shopUsesGarmentTaxonomy(shop);

  function queueExtraDetailCount(item) {
    let count = 0;
    if ((item.color || '').trim()) count += 1;
    if ((item.item_code || '').trim()) count += 1;
    if ((item.description || '').trim()) count += 1;
    for (const field of (shopCat.extraFields || [])) {
      if (((item[field.name] || '')).trim()) count += 1;
    }
    for (const field of (shopCat.detailFields || [])) {
      const raw = item.details ? item.details[field.name] : '';
      if ((raw == null ? '' : String(raw)).trim()) count += 1;
    }
    return count;
  }

  // Attach Google Places Autocomplete to the onboarding location input.
  // Only fires when the first-time form is mounted (shop === null after load).
  useEffect(() => {
    if (loading) return;
    if (shop) return; // edit form has its own autocomplete in ShopSettingsPage
    if (!locationInputRef.current) return;
    if (autocompleteRef.current && autocompleteNodeRef.current === locationInputRef.current) return;
    if (autocompleteRef.current) {
      try { window.google?.maps?.event?.clearInstanceListeners?.(autocompleteRef.current); } catch {}
      autocompleteRef.current = null;
      autocompleteNodeRef.current = null;
    }
    let cancelled = false;
    setPlacesLoading(true);
    loadGoogleMaps()
      .then((google) => {
        if (cancelled || !locationInputRef.current) return;
        const ac = new google.maps.places.Autocomplete(locationInputRef.current, {
          fields: [
            'name',
            'formatted_address',
            'address_components',
            'geometry',
            'editorial_summary',
            'types',
            'website',
            'international_phone_number',
          ],
          types: ['establishment'],
        });
        autocompleteNodeRef.current = locationInputRef.current;
        autocompleteRef.current = ac;
        ac.addListener('place_changed', () => {
          const place = ac.getPlace();
          if (!place) return;
          const addr = place.formatted_address || place.name || '';
          if (addr) setLocation(addr);
          if (place.name && !nameRef.current.trim()) setName(place.name);
          const summary = place.editorial_summary && place.editorial_summary.overview;
          if (summary && !descriptionRef.current.trim()) setDescription(summary);
          if (place.international_phone_number && !phoneRef.current.trim()) {
            setPhone(place.international_phone_number);
          }
          if (!categoryRef.current && Array.isArray(place.types)) {
            const guess = placeTypesToShopCategory(place.types);
            if (guess) setCategory(guess);
          }
        });
      })
      .catch((err) => {
        if (!cancelled) setPlacesError(err.message || 'Could not load location autocomplete.');
      })
      .finally(() => { if (!cancelled) setPlacesLoading(false); });
    return () => { cancelled = true; };
  }, [loading, shop]);

  const loadShop = useCallback(async () => {
    setLoading(true);
    setGarmentsLoading(true);
    // Fire the three independent lookups in parallel. /shops/me drives the
    // form shell, /prompts/categories and /garments/taxonomy hydrate dropdowns.
    const shopP = apiCall('/shops/me');
    const catsP = apiCall('/prompts/categories').catch(() => null);
    const taxP  = apiCall('/garments/taxonomy').catch(() => null);
    try {
      const s = await shopP;
      setShop(s);
      if (s) {
        setName(s.name); setDescription(s.description || ''); setLocation(s.location || ''); setCategory(s.category || '');
        setPhone(s.phone || '');
        setIsPublic(s.is_public !== false);
        // Render the shell immediately; garments stream in separately.
        setLoading(false);
        try {
          const g = await apiCall(`/shops/${s.id}/garments`);
          setGarments(sortByCreatedDesc(g || []));
        } catch (e) {
          setError(e.message);
        } finally {
          setGarmentsLoading(false);
        }
      } else {
        setGarmentsLoading(false);
      }
      const [cats, tax] = await Promise.all([catsP, taxP]);
      if (cats?.categories) setCategories(cats.categories);
      if (tax && typeof tax === 'object') setTaxonomy(tax);
    } catch (e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  }, [apiCall]);

  useEffect(() => { loadShop(); }, [loadShop]);

  // If a categorisation job was in flight when the user refreshed, pick it
  // back up so the UI shows progress / final state instead of forgetting.
  useEffect(() => {
    if (!shop) return;
    const backfillKey = `stylekro_backfill_job_${shop.id}`;
    const bulkKey = `stylekro_bulkcat_job_${shop.id}`;
    let cancelled = false;
    (async () => {
      const backfillJob = localStorage.getItem(backfillKey);
      if (backfillJob) {
        setBackfilling(true);
        setBackfillMsg('Resuming categorization…');
        try {
          const res = await pollJob(apiCall, backfillJob);
          if (cancelled) return;
          const g = await apiCall(`/shops/${shop.id}/garments`);
          setGarments(sortByCreatedDesc(g || []));
          setBackfillMsg(`Categorized ${res?.updated || 0} item${(res?.updated || 0) === 1 ? '' : 's'}.`);
        } catch (e) {
          if (!cancelled) setBackfillMsg(`Failed: ${e.message}`);
        } finally {
          localStorage.removeItem(backfillKey);
          if (!cancelled) setBackfilling(false);
        }
      }
      const bulkJob = localStorage.getItem(bulkKey);
      if (bulkJob) {
        setBulkBusy(true);
        setBulkMsg('Resuming categorization…');
        try {
          const res = await pollJob(apiCall, bulkJob);
          if (cancelled) return;
          const g = await apiCall(`/shops/${shop.id}/garments`);
          setGarments(sortByCreatedDesc(g || []));
          setBulkMsg(`Updated ${res?.updated || 0} of ${res?.processed || 0}.`);
        } catch (e) {
          if (!cancelled) setBulkMsg(`Failed: ${e.message}`);
        } finally {
          localStorage.removeItem(bulkKey);
          if (!cancelled) setBulkBusy(false);
        }
      }
    })();
    return () => { cancelled = true; };
  }, [shop, apiCall]);

  async function saveShop(e) {
    e.preventDefault();
    setError('');
    if (!name.trim()) return setError(t('shop.nameRequired'));
    setSaving(true);
    try {
      const path = shop ? `/shops/${shop.id}` : '/shops';
      const method = shop ? 'PUT' : 'POST';
      const s = await apiCall(path, {
        method,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name: name.trim(),
          description: description.trim() || null,
          location: location.trim() || null,
          category: category || null,
          phone: phone.trim() || null,
          is_public: isPublic,
        }),
      });
      setShop(s);
      if (s) setIsPublic(s.is_public !== false);
    } catch (e) {
      setError(e.message);
    } finally {
      setSaving(false);
    }
  }

  // ----- Bulk upload -----
  function addFilesToQueue(files) {
    const arr = Array.from(files || []).filter((f) => f.type.startsWith('image/'));
    if (arr.length === 0) return;
    const items = arr.map((f) => {
      // Title = the original file name (extension removed), shown as-is.
      const base = (f.name || '').replace(/\.[^.]+$/, '').trim();
      return {
        key: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
        file: f,
        preview: URL.createObjectURL(f),
        title: base || 'Untitled',
        price: '',
        description: '',
        color: '',
        item_code: '',
        tile_size: '',
        tile_finish: '',
        details: Object.fromEntries((shopCat.detailFields || []).map((d) => [d.name, ''])),
        detailsOpen: false,
        asset_kind: shopCat.defaultAssetKind,
        status: 'ready',
      };
    });
    setQueue((cur) => [...cur, ...items]);
  }

  function onBulkPick(e) {
    addFilesToQueue(e.target.files);
    if (bulkInputRef.current) bulkInputRef.current.value = '';
  }

  function onDropFiles(e) {
    e.preventDefault();
    addFilesToQueue(e.dataTransfer?.files);
  }

  function updateQueueItem(key, patch) {
    setQueue((cur) => cur.map((q) => (q.key === key ? { ...q, ...patch } : q)));
  }

  function removeQueueItem(key) {
    setQueue((cur) => cur.filter((q) => q.key !== key));
  }

  function clearQueue() {
    setQueue([]);
  }

  async function uploadAll() {
    if (!shop) return setError(t('shop.saveFirst'));
    if (queue.length === 0) return;
    setError('');
    setUploading(true);
    for (const item of queue) {
      if (item.status === 'done') continue;
      if (!item.title.trim()) {
        updateQueueItem(item.key, { status: 'error', error: 'Title is required' });
        continue;
      }
      updateQueueItem(item.key, { status: 'uploading', error: undefined });
      try {
        const fd = new FormData();
        fd.append('title', item.title.trim());
        if (item.description.trim()) fd.append('description', item.description.trim());
        if (item.price.trim()) fd.append('price', item.price.trim());
        if (item.color.trim()) fd.append('color', item.color.trim());
        if ((item.item_code || '').trim()) fd.append('item_code', item.item_code.trim());
        for (const f of shopCat.extraFields) {
          const v = ((item[f.name] || '')).trim();
          if (v) fd.append(f.name, v);
        }
        const detailsPayload = {};
        for (const f of (shopCat.detailFields || [])) {
          const raw = item.details ? item.details[f.name] : '';
          const v = (raw == null ? '' : String(raw)).trim();
          if (v) detailsPayload[f.name] = v;
        }
        if (Object.keys(detailsPayload).length > 0) {
          fd.append('details_json', JSON.stringify(detailsPayload));
        }
        fd.append('asset_kind', item.asset_kind || shopCat.defaultAssetKind);
        fd.append('photo', item.file);
        const g = await apiCall(`/shops/${shop.id}/garments`, { method: 'POST', body: fd });
        setGarments((cur) => [g, ...cur]);
        updateQueueItem(item.key, { status: 'done' });
      } catch (e) {
        updateQueueItem(item.key, { status: 'error', error: e.message });
      }
    }
    setUploading(false);
    // Auto-prune successful items
    setQueue((cur) => cur.filter((q) => q.status !== 'done'));
  }

  // ----- Inline edit existing -----
  function startEdit(g) {
    setEditingId(g.id);
    setEditForm({
      title: g.title || '',
      price: g.price || '',
      description: g.description || '',
      color: g.color || '',
      item_code: g.item_code || '',
      tile_size: g.tile_size || '',
      tile_finish: g.tile_finish || '',
      details: { ...(g.details || {}) },
      asset_kind: g.asset_kind || shopCat.defaultAssetKind,
      photo: null,
      preview: '',
      gender: usesGarmentTaxonomy ? (g.gender || '') : '',
      category_group: usesGarmentTaxonomy ? (g.category_group || '') : '',
      subcategory: usesGarmentTaxonomy ? (g.subcategory || '') : '',
    });
  }

  function cancelEdit() {
    setEditingId(null);
    setEditForm({ title: '', price: '', description: '', color: '', item_code: '', asset_kind: 'garment', photo: null, preview: '', gender: '', category_group: '', subcategory: '' });
  }

  function onEditPhoto(e) {
    const f = e.target.files[0];
    if (!f) return;
    setEditForm((cur) => ({ ...cur, photo: f, preview: URL.createObjectURL(f) }));
  }

  async function saveEdit(g) {
    if (!editForm.title.trim()) return setError('Title cannot be empty.');
    setSavingEdit(true);
    setError('');
    try {
      const fd = new FormData();
      fd.append('title', editForm.title.trim());
      fd.append('description', editForm.description.trim());
      fd.append('price', editForm.price.trim());
      if (editForm.photo) fd.append('photo', editForm.photo);
      if ((editForm.color || '').trim() !== (g.color || '').trim()) {
        fd.append('color', editForm.color.trim());
      }
      if ((editForm.item_code || '').trim() !== (g.item_code || '').trim()) {
        fd.append('item_code', editForm.item_code.trim());
      }
      for (const f of shopCat.extraFields) {
        const next = ((editForm[f.name] || '')).trim();
        const prev = ((g[f.name] || '')).trim();
        if (next !== prev) fd.append(f.name, next);
      }
      const detailsPayload = {};
      let detailsChanged = false;
      for (const f of (shopCat.detailFields || [])) {
        const nextRaw = editForm.details ? editForm.details[f.name] : '';
        const next = (nextRaw == null ? '' : String(nextRaw)).trim();
        const prevRaw = g.details ? g.details[f.name] : '';
        const prev = (prevRaw == null ? '' : String(prevRaw)).trim();
        detailsPayload[f.name] = next || null;
        if (next !== prev) detailsChanged = true;
      }
      if (detailsChanged) {
        fd.append('details_json', JSON.stringify(detailsPayload));
      }
      fd.append('asset_kind', editForm.asset_kind || shopCat.defaultAssetKind);
      // Always send category fields so the user can clear or override them.
      fd.append('gender', usesGarmentTaxonomy ? (editForm.gender || '') : '');
      fd.append('category_group', usesGarmentTaxonomy ? (editForm.category_group || '') : '');
      fd.append('subcategory', usesGarmentTaxonomy ? (editForm.subcategory || '') : '');
      const updated = await apiCall(`/garments/${g.id}`, { method: 'PATCH', body: fd });
      // Append cache-buster for photo if it was changed
      if (editForm.photo) updated.photo_url = `${updated.photo_url}?v=${Date.now()}`;
      setGarments((cur) => cur.map((x) => (x.id === g.id ? updated : x)));
      cancelEdit();
    } catch (e) {
      setError(e.message);
    } finally {
      setSavingEdit(false);
    }
  }

  async function removeGarment(id) {
    if (!(await confirmAction(t('shop.confirmRemovePiece'), { confirmLabel: t('common.delete'), tone: 'danger' }))) return;
    try {
      await apiCall(`/garments/${id}`, { method: 'DELETE' });
      setGarments((cur) => cur.filter((g) => g.id !== id));
      setZoomImage((cur) => (cur?.id === id ? null : cur));
    } catch (e) {
      setError(e.message);
    }
  }

  async function toggleGarmentVisibility(g) {
    const next = !(g.is_public !== false);
    try {
      const fd = new FormData();
      fd.append('is_public', next ? 'true' : 'false');
      const updated = await apiCall(`/garments/${g.id}`, { method: 'PATCH', body: fd });
      setGarments((cur) => cur.map((x) => (x.id === g.id ? { ...x, ...updated } : x)));
    } catch (e) {
      setError(e.message);
    }
  }

  async function runBackfill() {
    if (!shop) return;
    setBackfilling(true);
    setBackfillMsg('Categorizing… this may take a moment.');
    const storageKey = `stylekro_backfill_job_${shop.id}`;
    try {
      let totalUpdated = 0;
      // Loop a few times so up to ~200 items get processed in one click.
      for (let i = 0; i < 5; i++) {
        const { job_id } = await apiCall(
          `/garments/categorize-missing?shop_id=${shop.id}&limit=50`,
          { method: 'POST' },
        );
        if (!job_id) break;
        localStorage.setItem(storageKey, job_id);
        const res = await pollJob(apiCall, job_id);
        localStorage.removeItem(storageKey);
        totalUpdated += res?.updated || 0;
        if (!res || res.processed === 0) break;
      }
      const g = await apiCall(`/shops/${shop.id}/garments`);
      setGarments(sortByCreatedDesc(g || []));
      setBackfillMsg(totalUpdated > 0
        ? `Categorized ${totalUpdated} item${totalUpdated === 1 ? '' : 's'}.`
        : 'No items needed categorizing.');
    } catch (e) {
      setBackfillMsg(`Failed: ${e.message}`);
    } finally {
      localStorage.removeItem(storageKey);
      setBackfilling(false);
    }
  }

  // ----- Bulk selection helpers -----
  function toggleSelected(id) {
    setSelectedIds((cur) => {
      const next = new Set(cur);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  }
  function clearSelectedIds() { setSelectedIds(new Set()); }
  function selectAllVisible(ids) { setSelectedIds(new Set(ids)); }

  async function deleteSelectedGarments() {
    const ids = Array.from(selectedIds);
    if (!shop || ids.length === 0) return;
    if (!(await confirmAction(`Delete ${ids.length} selected item${ids.length === 1 ? '' : 's'} from your shop?`, { confirmLabel: t('common.delete'), tone: 'danger' }))) return;
    setBulkBusy(true);
    setBulkMsg('Deleting selected…');
    try {
      const results = await Promise.allSettled(
        ids.map((id) => apiCall(`/garments/${id}`, { method: 'DELETE' }))
      );
      const failed = new Set();
      results.forEach((result, index) => {
        if (result.status === 'rejected') failed.add(ids[index]);
      });
      setGarments((cur) => cur.filter((g) => !ids.includes(g.id) || failed.has(g.id)));
      setSelectedIds(failed);
      setZoomImage((cur) => (cur && ids.includes(cur.id) && !failed.has(cur.id) ? null : cur));
      if (failed.size > 0) {
        setBulkMsg(`Could not delete ${failed.size} item${failed.size === 1 ? '' : 's'}.`);
      } else {
        setBulkMsg(`Deleted ${ids.length} item${ids.length === 1 ? '' : 's'}.`);
        clearSelectedIds();
      }
    } catch (e) {
      setBulkMsg(`Failed: ${e.message}`);
    } finally {
      setBulkBusy(false);
    }
  }

  // Public shop link + one shared caption used on EVERY posted status/story.
  // WhatsApp keeps a single caption per share, so we put just ONE clean,
  // tappable shop link on every photo (listing per-item links duplicated them
  // on each photo). Buyers open the shop and try any item on with AI.
  function shopShareCaption() {
    const { protocol, host, pathname } = window.location;
    const shopLink = `${protocol}//${host}${pathname}#/shops/${shop?.id || ''}`;
    return `Try with StyleKro\n${shopLink}`;
  }

  // Post the selected items' photos to WhatsApp Status via the native Android
  // shell. Multiple photos go out in ONE tap as back-to-back status updates,
  // each carrying the shop link. Web/older shells are no-ops.
  function shareSelectedToStatus() {
    const sel = garments.filter((g) => selectedIds.has(g.id) && g.photo_url);
    if (sel.length === 0) { setBulkMsg('No photos to share.'); return; }
    const shell = (typeof window !== 'undefined') ? window.AndroidShell : null;
    const canImages = shell && typeof shell.shareImages === 'function';
    const canSingle = shell && typeof shell.shareImage === 'function';
    if (!canImages && !canSingle) {
      setBulkMsg('Update the app to share to Status.');
      return;
    }
    const MAX = 10;
    const chosen = sel.slice(0, MAX);
    const absUrl = (g) => {
      try { return new URL(imgUrl(g.photo_url), window.location.href).href; }
      catch { return imgUrl(g.photo_url); }
    };
    const caption = shopShareCaption();
    setStatusShareQueue([]);
    try {
      if (chosen.length === 1 && canSingle) {
        shell.shareImage(absUrl(chosen[0]), caption);
        setBulkMsg('Opening share…');
      } else if (canImages) {
        shell.shareImages(JSON.stringify(chosen.map(absUrl)), caption);
        setBulkMsg(sel.length > MAX
          ? `Sharing first ${MAX} of ${sel.length} photos…`
          : `Sharing ${chosen.length} photo${chosen.length === 1 ? '' : 's'}…`);
      } else {
        // Old shell without multi-image share: post the first item only.
        shell.shareImage(absUrl(chosen[0]), caption);
        setBulkMsg('Opening share…');
      }
    } catch (e) {
      setBulkMsg(`Share failed: ${e.message}`);
    }
  }

  // Post the next queued item to Status (after the user returns from WhatsApp).
  function shareNextStatusItem() {
    setStatusShareQueue((q) => {
      if (!q || q.length === 0) return [];
      const shell = (typeof window !== 'undefined') ? window.AndroidShell : null;
      const next = q[0];
      try {
        if (shell && typeof shell.shareImage === 'function') shell.shareImage(next.url, next.caption);
        else if (shell && typeof shell.shareImages === 'function') shell.shareImages(JSON.stringify([next.url]), next.caption);
      } catch (e) {
        setBulkMsg(`Share failed: ${e.message}`);
        return q;
      }
      const rest = q.slice(1);
      setBulkMsg(rest.length > 0
        ? `${rest.length} item${rest.length === 1 ? '' : 's'} left. Post it, come back, then tap “Share next item”.`
        : 'All items shared to Status. 🎉');
      return rest;
    });
  }

  async function bulkAiCategorize() {
    if (!shop || selectedIds.size === 0) return;
    setBulkBusy(true); setBulkMsg('Categorizing selected…');
    const storageKey = `stylekro_bulkcat_job_${shop.id}`;
    try {
      const { job_id } = await apiCall('/garments/bulk-categorize', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ garment_ids: Array.from(selectedIds) }),
      });
      localStorage.setItem(storageKey, job_id);
      const res = await pollJob(apiCall, job_id);
      localStorage.removeItem(storageKey);
      const g = await apiCall(`/shops/${shop.id}/garments`);
      setGarments(sortByCreatedDesc(g || []));
      setBulkMsg(`Updated ${res?.updated || 0} of ${res?.processed || 0}.`);
      clearSelectedIds();
    } catch (e) {
      setBulkMsg(`Failed: ${e.message}`);
    } finally {
      localStorage.removeItem(storageKey);
      setBulkBusy(false);
    }
  }

  async function bulkManualCategorize() {
    if (!shop || selectedIds.size === 0) return;
    const { gender, group, sub } = bulkPicker;
    if (!gender || !group || !sub) {
      setBulkMsg('Pick gender, group and subcategory first.');
      return;
    }
    setBulkBusy(true); setBulkMsg('Applying category to selected…');
    const storageKey = `stylekro_bulkcat_job_${shop.id}`;
    try {
      const { job_id } = await apiCall('/garments/bulk-categorize', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          garment_ids: Array.from(selectedIds),
          gender, category_group: group, subcategory: sub,
        }),
      });
      localStorage.setItem(storageKey, job_id);
      const res = await pollJob(apiCall, job_id);
      localStorage.removeItem(storageKey);
      const g = await apiCall(`/shops/${shop.id}/garments`);
      setGarments(sortByCreatedDesc(g || []));
      setBulkMsg(`Set ${res?.updated || 0} item${(res?.updated || 0) === 1 ? '' : 's'} to ${gender} › ${group} › ${sub}.`);
      clearSelectedIds();
      setBulkPicker({ open: false, gender: '', group: '', sub: '' });
    } catch (e) {
      setBulkMsg(`Failed: ${e.message}`);
    } finally {
      localStorage.removeItem(storageKey);
      setBulkBusy(false);
    }
  }

  return (
    <section className="screen shop-lux-screen">
      <NavBar active="manage" />
      <main className="page-wrap shop-dash shop-lux">
        {error && <p className="error">{error}</p>}
        {loading ? <p className="muted">{t('shop.loadingDots')}</p> : !shop ? (
          // First-time setup: centered single form
          <div className="shop-onboard">
            <div className="onboard-hero">
              <h2 className="page-title">{t('shop.openYour')} <em>{t('shop.shopWord')}</em></h2>
            </div>
            <form className="card-form onboard-form" onSubmit={saveShop}>
              <label className="field">
                <span className="field-label">{t('shop.location')} <em className="req">*</em></span>
                <input
                  ref={locationInputRef}
                  type="text"
                  placeholder={placesLoading ? t('shop.placesLoading') : t('shop.searchMapsShort')}
                  value={location}
                  onChange={(e) => setLocation(e.target.value)}
                  onKeyDown={(e) => { if (e.key === 'Enter') e.preventDefault(); }}
                  autoComplete="off"
                  required
                />
                <span className="field-hint">{t('shop.googleHint')}</span>
                {placesError && <span className="field-error">{placesError}</span>}
              </label>

              <label className="field">
                <span className="field-label">{t('shop.shopName')} <em className="req">*</em></span>
                <input type="text" placeholder={t('shop.namePlaceholder')} value={name} onChange={(e) => setName(e.target.value)} required />
              </label>

              <div className="field-row">
                <label className="field">
                  <span className="field-label">{t('shop.phone')}</span>
                  <input type="tel" placeholder="+91 98765 43210" value={phone} onChange={(e) => setPhone(e.target.value)} autoComplete="tel" />
                </label>
                <label className="field">
                  <span className="field-label">{t('shop.whatSell')} <em className="req">*</em></span>
                  <select value={category} onChange={(e) => setCategory(e.target.value)} required>
                    <option value="">{t('shop.chooseCategory')}</option>
                    {categories.map((c) => (
                      <option key={c} value={c}>{shopCategoryLabel(c)}</option>
                    ))}
                  </select>
                </label>
              </div>

              <label className="field">
                <span className="field-label">{t('shop.aboutShop')}</span>
                <textarea rows="3" placeholder={t('shop.descPlaceholder')} value={description} onChange={(e) => setDescription(e.target.value)} />
              </label>

              <button type="submit" disabled={saving} className="btn btn-primary onboard-cta">
                {saving ? t('shop.opening') : t('shop.openShop')}
              </button>
            </form>
          </div>
        ) : (() => {
          const filtered = garments.filter((g) => {
            if (filterGender && (g.gender || '') !== filterGender) return false;
            if (filterGroup && (g.category_group || '') !== filterGroup) return false;
            if (filterSub && (g.subcategory || '') !== filterSub) return false;
            if (filterColor && (g.color || '') !== filterColor) return false;
            if (filterAssetKind && (g.asset_kind || '') !== filterAssetKind) return false;
            if (filterVisibility === 'public' && g.is_public === false) return false;
            if (filterVisibility === 'hidden' && g.is_public !== false) return false;
            if (filterVisibility === 'uncategorized' && g.category_group) return false;
            if (search.trim()) {
              const q = search.toLowerCase();
              if (!((g.title || '').toLowerCase().includes(q)
                || (g.description || '').toLowerCase().includes(q)
                || (g.price || '').toLowerCase().includes(q)
                || (g.item_code || '').toLowerCase().includes(q)
                || (g.color || '').toLowerCase().includes(q))) return false;
            }
            return true;
          });
          // Counts for chip badges (computed before active filter so user sees totals).
          const genderCounts = makeCountMap(garments, 'gender');
          // Groups & subs are dependent on the currently-selected gender so the
          // chip list stays focused & manageable.
          const groupCounts = makeCountMap(
            garments.filter((g) => !filterGender || g.gender === filterGender),
            'category_group'
          );
          const subCounts = makeCountMap(
            garments.filter((g) => (!filterGender || g.gender === filterGender) && (!filterGroup || g.category_group === filterGroup)),
            'subcategory'
          );
          const colorCounts = makeCountMap(
            garments.filter((g) => (!filterGender || g.gender === filterGender)
              && (!filterGroup || g.category_group === filterGroup)
              && (!filterSub || g.subcategory === filterSub)),
            'color'
          );
          const assetKindCounts = makeCountMap(garments, 'asset_kind');
          const assetKinds = Object.keys(assetKindCounts).filter((k) => k).sort();
          const hiddenCount = garments.filter((g) => g.is_public === false).length;
          const uncatCount = garments.filter((g) => !g.category_group).length;
          const anyFilter = filterGender || filterGroup || filterSub || filterColor || filterAssetKind || filterVisibility !== 'all' || search.trim();
          const publicLink = `${window.location.protocol}//${window.location.host}${window.location.pathname}#/shops/${shop.id}`;
          return (
            <>
            <div className="shop-dash-grid single">

              {/* ===== Main column ===== */}
              <section className="shop-main">
                <div className="inv-layout">
                  {garments.length > 0 && (
                    <aside className={`inv-rail${filtersOpen ? ' open' : ''}`}>
                      <div className="rail-head">
                        <span className="rail-title">FILTERS</span>
                        {anyFilter && (
                          <button type="button" className="rail-clear" onClick={() => { setFilterGender(''); setFilterGroup(''); setFilterSub(''); setFilterColor(''); setFilterAssetKind(''); setFilterVisibility('all'); setSearch(''); }}>CLEAR ALL</button>
                        )}
                      </div>

                      {assetKinds.length >= 2 && (
                        <details className="rail-section" open>
                          <summary className="rail-section-title">TYPE</summary>
                          <label className="rail-opt">
                            <input type="radio" name="f-kind" checked={!filterAssetKind} onChange={() => setFilterAssetKind('')} />
                            <span>All <span className="rail-count">({garments.length})</span></span>
                          </label>
                          {assetKinds.map((k) => (
                            <label key={k} className="rail-opt">
                              <input type="radio" name="f-kind" checked={filterAssetKind === k} onChange={() => setFilterAssetKind(filterAssetKind === k ? '' : k)} />
                              <span>{assetKindLabel(k) || k} <span className="rail-count">({assetKindCounts[k]})</span></span>
                            </label>
                          ))}
                        </details>
                      )}

                      {!isTailorShop && Object.keys(genderCounts).filter((k) => k).length > 0 && (
                        <details className="rail-section" open>
                          <summary className="rail-section-title">GENDER</summary>
                          <label className="rail-opt">
                            <input type="radio" name="f-gender" checked={!filterGender} onChange={() => { setFilterGender(''); setFilterGroup(''); setFilterSub(''); }} />
                            <span>All <span className="rail-count">({garments.length})</span></span>
                          </label>
                          {Object.keys(genderCounts).filter((k) => k).sort().map((k) => (
                            <label key={k} className="rail-opt">
                              <input type="radio" name="f-gender" checked={filterGender === k} onChange={() => { setFilterGender(k); setFilterGroup(''); setFilterSub(''); }} />
                              <span>{k} <span className="rail-count">({genderCounts[k]})</span></span>
                            </label>
                          ))}
                        </details>
                      )}

                      {!isTailorShop && Object.keys(groupCounts).filter((k) => k).length > 0 && (
                        <details className="rail-section" open>
                          <summary className="rail-section-title">CATEGORIES</summary>
                          {Object.keys(groupCounts).filter((k) => k).sort().map((k) => (
                            <label key={k} className="rail-opt">
                              <input type="checkbox" checked={filterGroup === k} onChange={() => { setFilterGroup(filterGroup === k ? '' : k); setFilterSub(''); }} />
                              <span>{k} <span className="rail-count">({groupCounts[k]})</span></span>
                            </label>
                          ))}
                        </details>
                      )}

                      {!isTailorShop && filterGroup && Object.keys(subCounts).filter((k) => k).length > 0 && (
                        <details className="rail-section" open>
                          <summary className="rail-section-title">SUBCATEGORIES</summary>
                          {Object.keys(subCounts).filter((k) => k).sort().map((k) => (
                            <label key={k} className="rail-opt">
                              <input type="checkbox" checked={filterSub === k} onChange={() => setFilterSub(filterSub === k ? '' : k)} />
                              <span>{k} <span className="rail-count">({subCounts[k]})</span></span>
                            </label>
                          ))}
                        </details>
                      )}

                      {Object.keys(colorCounts).filter((k) => k).length > 0 && (
                        <details className="rail-section" open>
                          <summary className="rail-section-title">COLOR</summary>
                          {Object.keys(colorCounts).filter((k) => k).sort().map((k) => (
                            <label key={k} className="rail-opt">
                              <input type="checkbox" checked={filterColor === k} onChange={() => setFilterColor(filterColor === k ? '' : k)} />
                              <span>{k} <span className="rail-count">({colorCounts[k]})</span></span>
                            </label>
                          ))}
                        </details>
                      )}

                      <details className="rail-section" open>
                        <summary className="rail-section-title">STATUS</summary>
                        <label className="rail-opt">
                          <input type="radio" name="f-vis" checked={filterVisibility === 'all'} onChange={() => setFilterVisibility('all')} />
                          <span>All <span className="rail-count">({garments.length})</span></span>
                        </label>
                        <label className="rail-opt">
                          <input type="radio" name="f-vis" checked={filterVisibility === 'public'} onChange={() => setFilterVisibility('public')} />
                          <span>🌐 Public <span className="rail-count">({garments.length - hiddenCount})</span></span>
                        </label>
                        {hiddenCount > 0 && (
                          <label className="rail-opt">
                            <input type="radio" name="f-vis" checked={filterVisibility === 'hidden'} onChange={() => setFilterVisibility('hidden')} />
                            <span>🔒 Hidden <span className="rail-count">({hiddenCount})</span></span>
                          </label>
                        )}
                        {uncatCount > 0 && (
                          <label className="rail-opt">
                            <input type="radio" name="f-vis" checked={filterVisibility === 'uncategorized'} onChange={() => setFilterVisibility('uncategorized')} />
                            <span>❓ Uncategorized <span className="rail-count">({uncatCount})</span></span>
                          </label>
                        )}
                      </details>
                      <div className="rail-footer">
                        <button type="button" className="btn-primary rail-apply-btn" onClick={() => setFiltersOpen(false)}>Apply</button>
                      </div>
                    </aside>
                  )}
                  {filtersOpen && <div className="filters-backdrop show" onClick={() => setFiltersOpen(false)} />}

                  <div className="inv-content">
                <div className="settings-card shop-lux-card" style={{ marginBottom: 16 }}>
                  <div className="settings-section">
                    <div
                      className="dropzone shop-lux-drop"
                      role="button"
                      tabIndex={0}
                      onClick={() => {
                        if (uploading) return;
                        if (hasNativePicker()) {
                          nativePickFiles(true)
                            .then((files) => {
                              if (files && files.length) addFilesToQueue(files);
                            })
                            .catch((err) => {
                              setError(`Could not read photo from device: ${errMsg(err)}`);
                            });
                          return;
                        }
                        if (bulkInputRef.current) bulkInputRef.current.click();
                      }}
                      onDragOver={(e) => { e.preventDefault(); e.currentTarget.classList.add('drag'); }}
                      onDragLeave={(e) => e.currentTarget.classList.remove('drag')}
                      onDrop={(e) => { e.currentTarget.classList.remove('drag'); onDropFiles(e); }}
                    >
                      <input ref={bulkInputRef} type="file" accept="image/*" multiple hidden onChange={onBulkPick} disabled={uploading} />
                      <div className="dz-inner">
                        <div className="shop-lux-drop-glow" />
                        <div className="dz-cta">
                          <span className="shop-lux-drop-icon">
                            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 19V5" /><path d="M5 12l7-7 7 7" /></svg>
                          </span>
                          <strong>{t('mng.dropPhotos')}</strong>
                          <small>{t('mng.multiSupported')}</small>
                        </div>
                      </div>
                    </div>

                    {isTailorShop && (
                      <div className="muted small" style={{ marginTop: 10 }}>
                        {t('mng.tailorUploadHint')}
                      </div>
                    )}

                    {queue.length > 0 && (
                      <div style={{ marginTop: 16 }}>
                        <div className="queue-bar">
                          <div className="queue-bar-copy">
                            <span className="queue-pill">{queue.length} {t('mng.pending')}</span>
                            <strong>{t('mng.readyUpload')}</strong>
                          </div>
                          <div className="select-bar-actions queue-bar-actions">
                              <button type="button" className="btn btn-ghost small queue-clear-btn" onClick={clearQueue} disabled={uploading}>{t('common.clear')}</button>
                              <button type="button" className="btn btn-primary small queue-upload-btn" onClick={uploadAll} disabled={uploading}>
                                {uploading ? t('mng.adding') : `${t('mng.addItem')} ${queue.length}`}
                              </button>
                          </div>
                        </div>
                        <div className="queue-list">
                          {queue.map((q) => (
                            <div key={q.key} className={`queue-row status-${q.status}`}>
                              {(() => {
                                const extraDetailCount = queueExtraDetailCount(q);
                                const detailsButtonLabel = q.detailsOpen
                                  ? t('mng.hideDetails')
                                  : extraDetailCount > 0
                                    ? `${t('mng.editDetails')} (${extraDetailCount})`
                                    : t('mng.addMoreDetails');
                                return (
                                  <>
                              <img src={q.preview} alt="" />
                              <div className="queue-fields">
                                <input
                                  type="text"
                                  placeholder={t('mng.title')}
                                  value={q.title}
                                  onChange={(e) => updateQueueItem(q.key, { title: e.target.value })}
                                  disabled={uploading || q.status === 'done'}
                                />
                                {isTailorShop && (
                                  <AssetKindToggle
                                    value={q.asset_kind || 'design'}
                                    onChange={(next) => updateQueueItem(q.key, { asset_kind: next })}
                                    disabled={uploading || q.status === 'done'}
                                  />
                                )}
                                <CategoryItemTypeToggle
                                  shop={shop}
                                  value={q.asset_kind}
                                  onChange={(next) => updateQueueItem(q.key, { asset_kind: next })}
                                  disabled={uploading || q.status === 'done'}
                                />
                                <div className="queue-fields-row">
                                  <input
                                    type="text"
                                    placeholder={t('mng.priceOptional')}
                                    value={q.price}
                                    onChange={(e) => updateQueueItem(q.key, { price: e.target.value })}
                                    disabled={uploading || q.status === 'done'}
                                  />
                                </div>
                                <button
                                  type="button"
                                  className={`queue-more-btn${q.detailsOpen ? ' open' : ''}${extraDetailCount > 0 ? ' has-values' : ''}`}
                                  onClick={() => updateQueueItem(q.key, { detailsOpen: !q.detailsOpen })}
                                  disabled={uploading || q.status === 'done'}
                                  aria-expanded={q.detailsOpen ? 'true' : 'false'}
                                >
                                  <span>{detailsButtonLabel}</span>
                                  <span className="queue-more-btn-icon" aria-hidden="true">{q.detailsOpen ? '−' : '+'}</span>
                                </button>
                                {q.detailsOpen && (
                                  <div className="queue-fields-row queue-fields-more">
                                    <ColorPaletteField
                                      label={t('mng.color')}
                                      value={q.color}
                                      onChange={(next) => updateQueueItem(q.key, { color: next })}
                                      disabled={uploading || q.status === 'done'}
                                      compact
                                    />
                                    <input
                                      type="text"
                                      placeholder={t('mng.itemCodeOptional')}
                                      value={q.item_code || ''}
                                      onChange={(e) => updateQueueItem(q.key, { item_code: e.target.value })}
                                      disabled={uploading || q.status === 'done'}
                                    />
                                    <CategoryExtraFields
                                      shop={shop}
                                      values={q}
                                      onChange={(name, value) => updateQueueItem(q.key, { [name]: value })}
                                      disabled={uploading || q.status === 'done'}
                                    />
                                    <CategoryDetailFields
                                      shop={shop}
                                      values={q}
                                      onChange={(name, value) => updateQueueItem(q.key, { details: { ...(q.details || {}), [name]: value } })}
                                      disabled={uploading || q.status === 'done'}
                                    />
                                    <input
                                      type="text"
                                      placeholder={t('mng.shortDescription')}
                                      value={q.description}
                                      onChange={(e) => updateQueueItem(q.key, { description: e.target.value })}
                                      disabled={uploading || q.status === 'done'}
                                    />
                                  </div>
                                )}
                                {q.status === 'error' && <span className="queue-error">⚠ {q.error}</span>}
                                {q.status === 'uploading' && <span className="muted small">{t('mng.uploading')}</span>}
                                {q.status === 'done' && <span className="queue-ok">✓ {t('mng.uploaded')}</span>}
                              </div>
                              <button
                                type="button"
                                className="icon-btn icon-btn-danger queue-remove"
                                onClick={() => removeQueueItem(q.key)}
                                disabled={uploading}
                                title="Remove"
                                aria-label="Remove"
                              >
                                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg>
                              </button>
                                  </>
                                );
                              })()}
                            </div>
                          ))}
                        </div>
                      </div>
                    )}
                  </div>
                </div>

                <div className="inv-toolbar">
                  <div>
                    <div className="eyebrow">{t('mng.inventory')}</div>
                    <h3 className="page-title small" style={{margin: 0}}>
                      {garments.length} {garments.length === 1 ? t('mng.itemLabel') : t('mng.itemsLabel')}
                      {anyFilter && ` · ${filtered.length} ${t('mng.shown')}`}
                    </h3>
                  </div>
                  <div className="view-toggle" role="tablist">
                    <button
                      type="button"
                      className={`vt-btn${view === 'grid' ? ' on' : ''}`}
                      onClick={() => setView('grid')}
                      aria-label="Grid view"
                      title="Grid view"
                    >
                      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                        <rect x="4" y="4" width="6" height="6" rx="1.5" />
                        <rect x="14" y="4" width="6" height="6" rx="1.5" />
                        <rect x="4" y="14" width="6" height="6" rx="1.5" />
                        <rect x="14" y="14" width="6" height="6" rx="1.5" />
                      </svg>
                      <span className="vt-btn-label">{t('mng.grid')}</span>
                    </button>
                    <button
                      type="button"
                      className={`vt-btn${view === 'list' ? ' on' : ''}`}
                      onClick={() => setView('list')}
                      aria-label="List view"
                      title="List view"
                    >
                      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                        <line x1="6" y1="7" x2="18" y2="7" />
                        <line x1="6" y1="12" x2="18" y2="12" />
                        <line x1="6" y1="17" x2="18" y2="17" />
                      </svg>
                      <span className="vt-btn-label">{t('mng.list')}</span>
                    </button>
                  </div>
                </div>

                {garments.length > 0 && (
                  <div className="top-actions">
                    <div className="search-wrapper">
                      <svg className="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                        <circle cx="11" cy="11" r="7" />
                        <path d="M21 21l-4.3-4.3" />
                      </svg>
                      <input
                        type="search"
                        className="search-input"
                        placeholder={t('mng.searchInventory')}
                        value={search}
                        onChange={(e) => setSearch(e.target.value)}
                      />
                    </div>
                    <button
                      type="button"
                      className={`filter-btn${anyFilter ? ' has-active' : ''}`}
                      onClick={() => setFiltersOpen(true)}
                      aria-label="Open filters"
                    >
                      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                        <line x1="4" y1="6" x2="20" y2="6" />
                        <line x1="7" y1="12" x2="17" y2="12" />
                        <line x1="10" y1="18" x2="14" y2="18" />
                      </svg>
                      <span className="filter-btn-label">{t('common.filters')}</span>
                    </button>
                  </div>
                )}

                {garments.length > 0 && (
                  <div className="filters">
                    {!isTailorShop && Object.keys(genderCounts).filter((k) => k).sort().map((k) => (
                      <button key={`g-${k}`} type="button" className={`chip${filterGender === k ? ' active' : ''}`} onClick={() => { setFilterGender(filterGender === k ? '' : k); setFilterGroup(''); setFilterSub(''); }}>{k}</button>
                    ))}
                    {shopCat.kindOptions ? (
                      shopCat.kindOptions.map((opt) => (
                        <button key={`ak-${opt.value}`} type="button" className={`chip${filterAssetKind === opt.value ? ' active' : ''}`} onClick={() => setFilterAssetKind(filterAssetKind === opt.value ? '' : opt.value)}>{opt.label}</button>
                      ))
                    ) : assetKinds.length >= 2 ? (
                      assetKinds.map((k) => (
                        <button key={`ak-${k}`} type="button" className={`chip${filterAssetKind === k ? ' active' : ''}`} onClick={() => setFilterAssetKind(filterAssetKind === k ? '' : k)}>{assetKindLabel(k) || k}</button>
                      ))
                    ) : (
                      Object.keys(groupCounts).filter((k) => k).sort().map((k) => (
                        <button key={`gr-${k}`} type="button" className={`chip${filterGroup === k ? ' active' : ''}`} onClick={() => { setFilterGroup(filterGroup === k ? '' : k); setFilterSub(''); }}>{k}</button>
                      ))
                    )}
                    {hiddenCount > 0 && (
                      <button type="button" className={`chip${filterVisibility === 'hidden' ? ' active' : ''}`} onClick={() => setFilterVisibility(filterVisibility === 'hidden' ? 'all' : 'hidden')}>🔒 {t('mng.hidden')}</button>
                    )}
                  </div>
                )}

                {/* Filters now live in the left rail (Flipkart-style) */}

                {/* Bulk selection / re-categorisation bar */}
                {garments.length > 0 && selectedIds.size > 0 && ReactDOM.createPortal(
                  <div className="bulk-bar has-selection shop-lux-bulk-portal">
                    <div className="bulk-bar-row">
                      <span className="bulk-count-pill">
                        <span className="bulk-count-num">{selectedIds.size}</span>
                        <span className="bulk-count-word">{t('mng.selected')}</span>
                      </span>
                      <div className="bulk-bar-actions">
                        <button
                          type="button"
                          className="btn btn-ghost small"
                          onClick={() => selectAllVisible(filtered.map((g) => g.id))}
                          disabled={filtered.length === 0}
                          title="Select all"
                          aria-label="Select all"
                        >
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                            <rect x="3" y="3" width="18" height="18" rx="4" />
                            <path d="m8 12 3 3 5-6" />
                          </svg>
                          <span className="bulk-btn-label">{t('mng.selectAll')}</span>
                        </button>
                        <button
                          type="button"
                          className="btn btn-ghost small"
                          onClick={clearSelectedIds}
                          title="Clear selection"
                          aria-label="Clear selection"
                        >
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                            <path d="M18 6 6 18" />
                            <path d="m6 6 12 12" />
                          </svg>
                          <span className="bulk-btn-label">{t('common.clear')}</span>
                        </button>
                        {typeof window !== 'undefined' && window.AndroidShell && typeof window.AndroidShell.shareImages === 'function' && (
                          <button
                            type="button"
                            className="btn btn-ghost small"
                            onClick={shareSelectedToStatus}
                            disabled={bulkBusy}
                            title="Share to Status"
                            aria-label="Share to Status"
                          >
                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                              <circle cx="18" cy="5" r="3" />
                              <circle cx="6" cy="12" r="3" />
                              <circle cx="18" cy="19" r="3" />
                              <line x1="8.59" y1="13.51" x2="15.42" y2="17.49" />
                              <line x1="15.41" y1="6.51" x2="8.59" y2="10.49" />
                            </svg>
                            <span className="bulk-btn-label">{t('mng.share')}</span>
                          </button>
                        )}
                        <button
                          type="button"
                          className="btn btn-ghost small danger"
                          onClick={deleteSelectedGarments}
                          disabled={bulkBusy}
                          title="Delete selected"
                          aria-label="Delete selected"
                        >
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                            <path d="M3 6h18" />
                            <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
                            <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
                            <path d="M10 11v6" />
                            <path d="M14 11v6" />
                          </svg>
                          <span className="bulk-btn-label">{t('common.delete')}</span>
                        </button>
                      </div>
                    </div>
                    {bulkPicker.open && selectedIds.size > 0 && (
                      <div className="bulk-bar-row" style={{ flexWrap: 'wrap', gap: 8 }}>
                        <TaxonomySelect
                          taxonomy={taxonomy}
                          gender={bulkPicker.gender}
                          group={bulkPicker.group}
                          sub={bulkPicker.sub}
                          labels={{ gender: t('mng.genderDots'), group: t('mng.groupDots'), sub: t('mng.subDots') }}
                          onChange={(next) => setBulkPicker({ open: true, ...next })}
                        />
                        <button
                          type="button"
                          className="btn btn-primary small"
                          onClick={bulkManualCategorize}
                          disabled={bulkBusy || !bulkPicker.sub}
                        >{t('mng.applyTo')} {selectedIds.size}</button>
                      </div>
                    )}
                    {bulkMsg && <div className="muted small">{bulkMsg}</div>}
                    {statusShareQueue.length > 0 && (
                      <button
                        type="button"
                        className="btn btn-primary small"
                        onClick={shareNextStatusItem}
                        style={{ marginTop: 6 }}
                      >
                        {t('mng.shareNext')} ({statusShareQueue.length})
                      </button>
                    )}
                  </div>,
                  document.body
                )}

                {garmentsLoading ? (
                  <div className="garment-grid">
                    {[0,1,2,3,4,5].map((i) => (
                      <div key={i} className="garment-card skeleton">
                        <div className="skel-photo" />
                        <div className="skel-line" style={{ width: '70%' }} />
                        <div className="skel-line" style={{ width: '40%' }} />
                      </div>
                    ))}
                  </div>
                ) : garments.length === 0 ? (
                  <div className="empty"><p>{t('shop.noPiecesUpload')}</p></div>
                ) : filtered.length === 0 ? (
                  <div className="empty"><p>{t('mng.noMatch')} "{search}".</p></div>
                ) : view === 'grid' ? (
                  <div className="garment-grid">
                    {filtered.map((g) => {
                      const isEditing = editingId === g.id;
                      const isSel = selectedIds.has(g.id);
                      return (
                        <div
                          key={g.id}
                          className={`garment-card${isEditing ? ' editing' : ''}${isSel ? ' selected' : ''}`}
                          onClick={(e) => {
                            if (isEditing) return;
                            if (e.target.closest('button, input, textarea, select, a')) return;
                            navigate(`/shop/manage/items/${g.id}`);
                          }}
                          role="button"
                          tabIndex={0}
                          onKeyDown={(e) => {
                            if (isEditing) return;
                            if (e.key === 'Enter') navigate(`/shop/manage/items/${g.id}`);
                          }}
                        >
                          <div className="garment-photo" style={{position: 'relative'}}>
                            <img src={isEditing && editForm.preview ? editForm.preview : imgUrl(g.photo_url)} alt={g.title} loading="lazy" decoding="async" />
                            {!isEditing && (
                              <button
                                type="button"
                                className={`select-tick inline${isSel ? ' on' : ''}`}
                                onClick={(e) => { e.stopPropagation(); toggleSelected(g.id); }}
                                aria-pressed={isSel}
                                aria-label={isSel ? 'Deselect item' : 'Select item'}
                                style={{ position: 'absolute', top: 8, left: 8 }}
                              >
                                <span className="select-tick-mark" aria-hidden="true">{isSel ? '✓' : '+'}</span>
                              </button>
                            )}
                            {!isEditing && (
                              <button
                                type="button"
                                className="photo-zoom-btn"
                                onClick={(e) => {
                                  e.stopPropagation();
                                  setZoomImage({ id: g.id, src: imgUrl(g.photo_url), title: g.title || 'Item photo' });
                                }}
                                title="Zoom image"
                                aria-label="Zoom image"
                              >
                                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/><path d="M11 8v6"/><path d="M8 11h6"/></svg>
                              </button>
                            )}
                            {isEditing && (
                              <button
                                type="button"
                                className="btn btn-ghost small change-photo-btn"
                                onClick={() => editFileRef.current?.click()}
                              >{t('mng.changePhoto')}</button>
                            )}
                            {isEditing && (
                              <input ref={editFileRef} type="file" accept="image/*" hidden onChange={onEditPhoto} />
                            )}
                          </div>
                          <div className="garment-body">
                            {isEditing ? (
                              <>
                                <input
                                  type="text"
                                  value={editForm.title}
                                  onChange={(e) => setEditForm((f) => ({ ...f, title: e.target.value }))}
                                  placeholder={t('mng.title')}
                                />
                                <input
                                  type="text"
                                  value={editForm.price}
                                  onChange={(e) => setEditForm((f) => ({ ...f, price: e.target.value }))}
                                  placeholder={t('mng.price')}
                                />
                                <textarea
                                  rows="2"
                                  value={editForm.description}
                                  onChange={(e) => setEditForm((f) => ({ ...f, description: e.target.value }))}
                                  placeholder={t('mng.description')}
                                />
                                <ColorPaletteField
                                  label={t('mng.color')}
                                  value={editForm.color}
                                  onChange={(next) => setEditForm((f) => ({ ...f, color: next }))}
                                  disabled={savingEdit}
                                  compact
                                />
                                <input
                                  type="text"
                                  value={editForm.item_code || ''}
                                  onChange={(e) => setEditForm((f) => ({ ...f, item_code: e.target.value }))}
                                  placeholder={t('mng.itemCodeOptional')}
                                />
                                {isTailorShop && (
                                  <AssetKindToggle
                                    value={editForm.asset_kind || 'design'}
                                    onChange={(next) => setEditForm((f) => ({ ...f, asset_kind: next }))}
                                  />
                                )}
                                <CategoryItemTypeToggle
                                  shop={shop}
                                  value={editForm.asset_kind}
                                  onChange={(next) => setEditForm((f) => ({ ...f, asset_kind: next }))}
                                />
                                <CategoryExtraFields
                                  shop={shop}
                                  values={editForm}
                                  onChange={(name, value) => setEditForm((f) => ({ ...f, [name]: value }))}
                                />
                                <CategoryDetailFields
                                  shop={shop}
                                  values={editForm}
                                  onChange={(name, value) => setEditForm((f) => ({ ...f, details: { ...(f.details || {}), [name]: value } }))}
                                />
                                {usesGarmentTaxonomy && <div className="cat-edit-row">
                                  <TaxonomySelect
                                    taxonomy={taxonomy}
                                    gender={editForm.gender}
                                    group={editForm.category_group}
                                    sub={editForm.subcategory}
                                    onChange={(next) => setEditForm((f) => ({ ...f, gender: next.gender, category_group: next.group, subcategory: next.sub }))}
                                  />
                                </div>}
                                <div style={{display: 'flex', gap: 8, flexWrap: 'wrap'}}>
                                  <button className="btn btn-primary small" disabled={savingEdit} onClick={() => saveEdit(g)}>
                                    {savingEdit ? 'Saving…' : 'Save'}
                                  </button>
                                  <button className="btn btn-ghost small" onClick={cancelEdit} disabled={savingEdit}>Cancel</button>
                                </div>
                              </>
                            ) : (
                              <>
                                <button
                                  type="button"
                                  className="garment-title-link"
                                  onClick={(e) => { e.stopPropagation(); navigate(`/shop/manage/items/${g.id}`); }}
                                  title="Open item details"
                                >
                                  <h4>{g.title}</h4>
                                </button>
                                {g.price && <div className="garment-price">{g.price}</div>}
                                {(g.asset_kind || g.color || (usesGarmentTaxonomy && (g.gender || g.subcategory || g.category_group))) && (
                                  <div className="garment-cat muted small">
                                    {[
                                      assetKindLabel(g.asset_kind),
                                      usesGarmentTaxonomy ? g.gender : null,
                                      usesGarmentTaxonomy ? g.category_group : null,
                                      usesGarmentTaxonomy ? g.subcategory : null,
                                      g.color,
                                    ].filter(Boolean).join(' · ')}
                                  </div>
                                )}
                                {g.is_public === false && <div className="garment-hidden-tag">🔒 Hidden</div>}
                              </>
                            )}
                          </div>
                        </div>
                      );
                    })}
                  </div>
                ) : (
                  <div className="inv-list">
                    {filtered.map((g) => {
                      const isEditing = editingId === g.id;
                      const isSel = selectedIds.has(g.id);
                      return (
                        <div key={g.id} className={`inv-row${isEditing ? ' editing' : ''}${isSel ? ' selected' : ''}`}>
                          {!isEditing && (
                            <button
                              type="button"
                              className={`select-tick${isSel ? ' on' : ''}`}
                              onClick={() => toggleSelected(g.id)}
                              aria-pressed={isSel}
                              aria-label={isSel ? 'Deselect' : 'Select'}
                            >{isSel ? '✓' : ''}</button>
                          )}
                          <div className="inv-thumb" style={{position: 'relative'}}>
                            <img src={isEditing && editForm.preview ? editForm.preview : imgUrl(g.photo_url)} alt={g.title} loading="lazy" decoding="async" />
                            {isEditing && (
                              <>
                                <button
                                  type="button"
                                  className="btn btn-ghost small change-photo-btn"
                                  onClick={() => editFileRef.current?.click()}
                                >Photo</button>
                                <input ref={editFileRef} type="file" accept="image/*" hidden onChange={onEditPhoto} />
                              </>
                            )}
                          </div>
                          {isEditing ? (
                            <>
                              <div className="inv-fields">
                                <input
                                  type="text"
                                  value={editForm.title}
                                  onChange={(e) => setEditForm((f) => ({ ...f, title: e.target.value }))}
                                  placeholder={t('mng.title')}
                                />
                                <div className="queue-fields-row">
                                  <input
                                    type="text"
                                    value={editForm.price}
                                    onChange={(e) => setEditForm((f) => ({ ...f, price: e.target.value }))}
                                    placeholder={t('mng.price')}
                                  />
                                  <input
                                    type="text"
                                    value={editForm.description}
                                    onChange={(e) => setEditForm((f) => ({ ...f, description: e.target.value }))}
                                    placeholder={t('mng.description')}
                                  />
                                </div>
                                <ColorPaletteField
                                  label={t('mng.color')}
                                  value={editForm.color}
                                  onChange={(next) => setEditForm((f) => ({ ...f, color: next }))}
                                  disabled={savingEdit}
                                  compact
                                />
                                <input
                                  type="text"
                                  value={editForm.item_code || ''}
                                  onChange={(e) => setEditForm((f) => ({ ...f, item_code: e.target.value }))}
                                  placeholder={t('mng.itemCodeOptional')}
                                />
                                {isTailorShop && (
                                  <AssetKindToggle
                                    value={editForm.asset_kind || 'design'}
                                    onChange={(next) => setEditForm((f) => ({ ...f, asset_kind: next }))}
                                  />
                                )}
                                <CategoryItemTypeToggle
                                  shop={shop}
                                  value={editForm.asset_kind}
                                  onChange={(next) => setEditForm((f) => ({ ...f, asset_kind: next }))}
                                />
                                <CategoryExtraFields
                                  shop={shop}
                                  values={editForm}
                                  onChange={(name, value) => setEditForm((f) => ({ ...f, [name]: value }))}
                                />
                                <CategoryDetailFields
                                  shop={shop}
                                  values={editForm}
                                  onChange={(name, value) => setEditForm((f) => ({ ...f, details: { ...(f.details || {}), [name]: value } }))}
                                />
                                {usesGarmentTaxonomy && <div className="cat-edit-row">
                                  <TaxonomySelect
                                    taxonomy={taxonomy}
                                    gender={editForm.gender}
                                    group={editForm.category_group}
                                    sub={editForm.subcategory}
                                    onChange={(next) => setEditForm((f) => ({ ...f, gender: next.gender, category_group: next.group, subcategory: next.sub }))}
                                  />
                                </div>}
                              </div>
                              <div className="inv-actions">
                                <button className="btn btn-primary small" disabled={savingEdit} onClick={() => saveEdit(g)}>
                                  {savingEdit ? 'Saving…' : 'Save'}
                                </button>
                                <button className="btn btn-ghost small" onClick={cancelEdit} disabled={savingEdit}>Cancel</button>
                              </div>
                            </>
                          ) : (
                            <>
                              <div className="inv-info">
                                <div className="inv-title">
                                  {g.title}
                                  {g.is_public === false && <span className="inv-hidden-tag">🔒</span>}
                                </div>
                                {(g.asset_kind || g.color || (usesGarmentTaxonomy && (g.gender || g.subcategory || g.category_group))) && (
                                  <div className="muted small">
                                    {[
                                      assetKindLabel(g.asset_kind),
                                      usesGarmentTaxonomy ? g.gender : null,
                                      usesGarmentTaxonomy ? g.category_group : null,
                                      usesGarmentTaxonomy ? g.subcategory : null,
                                      g.color,
                                    ].filter(Boolean).join(' › ')}
                                  </div>
                                )}
                                {g.description && <div className="muted small inv-desc">{g.description}</div>}
                              </div>
                              <div className="inv-price">{g.price || <span className="muted small">—</span>}</div>
                              <div className="inv-actions">
                                <button className="btn btn-ghost small" onClick={() => navigate(`/shop/manage/items/${g.id}`)}>Open</button>
                                <button className="btn btn-ghost small" onClick={() => setZoomImage({ id: g.id, src: imgUrl(g.photo_url), title: g.title || 'Item photo' })}>Zoom</button>
                              </div>
                            </>
                          )}
                        </div>
                      );
                    })}
                  </div>
                )}
                  </div>
                </div>
              </section>
            </div>
            </>
          );
        })()}
      </main>
      {zoomImage && (
        <div className="lightbox" onClick={() => setZoomImage(null)}>
          <button className="lightbox-close" title="Close" aria-label="Close zoom view">×</button>
          <img src={zoomImage.src} alt={zoomImage.title || ''} onClick={(e) => e.stopPropagation()} />
        </div>
      )}
    </section>
  );
}
function BuyerGarmentDetailPage() {
  const { apiCall, token, t } = useAuth();
  const ensureProfilePhoto = useRequireProfilePhoto();
  const navigate = useNavigate();
  const { shopId, garmentId } = useParams();

  const [shop, setShop] = useState(null);
  const [garment, setGarment] = useState(null);
  const [variants, setVariants] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');
  const [galleryIdx, setGalleryIdx] = useState(0);
  const [tryon, setTryon] = useState(null);
  const [shareCopied, setShareCopied] = useState(false);
  const [sheet360Open, setSheet360Open] = useState(false);
  // Tattoo try-on lives in the shop studio (it needs the buyer's own uploaded
  // photo). On a tattoo item page we just send the buyer to the studio.
  const isTattoo = (shop?.category || '').toLowerCase() === 'tattoo';
  const fetchItemSheet360 = useCallback(
    () => fetchSheet360(`/garments/${garmentId}/sheet360`),
    [garmentId],
  );

  // On real Android browsers (Chrome/Firefox/Samsung), try to deep-link into
  // the Stylekro app for shared item URLs. The intent URL carries a
  // browser_fallback_url back to the current web page, so if the app is
  // not installed the user stays on the website with no visible change.
  //
  // We intentionally SKIP in-app browsers (WhatsApp, Instagram, Facebook,
  // etc.) because those are Android WebViews that cannot handle the
  // `intent://` scheme and ignore browser_fallback_url — navigating to it
  // there just shows "Webpage not available". In those cases we keep the
  // user on the website, which loads the item normally.
  useEffect(() => {
    if (typeof navigator === 'undefined' || typeof window === 'undefined') return;
    const ua = navigator.userAgent || '';
    const isAndroid = /Android/i.test(ua);
    const inOwnWebView = /StyleYouMobile/.test(ua);
    // Generic Android WebView (in-app browsers) advertise "; wv" in the UA.
    const isGenericWebView = /;\s*wv\b/.test(ua);
    // Known in-app browser tokens (WhatsApp, Facebook, Instagram, Line, etc.).
    const isInAppBrowser = /\b(FBAN|FBAV|FB_IAB|Instagram|Line|WhatsApp|GSA|Snapchat|Twitter|Pinterest)\b/i.test(ua);
    if (!isAndroid || inOwnWebView || isGenericWebView || isInAppBrowser) return;
    const key = `stylekro-deeplink-tried:${shopId}/${garmentId}`;
    try { if (sessionStorage.getItem(key)) return; } catch {}
    try { sessionStorage.setItem(key, '1'); } catch {}
    const webUrl = window.location.href;
    const intent =
      `intent://shops/${encodeURIComponent(shopId)}/items/${encodeURIComponent(garmentId)}` +
      `#Intent;scheme=stylekro;package=com.stylekro.app;` +
      `S.browser_fallback_url=${encodeURIComponent(webUrl)};end`;
    window.location.href = intent;
  }, [shopId, garmentId]);

  // Pre-fetch the current hero image as a File so "Share to WhatsApp Status"
  // can invoke navigator.share() synchronously. The Web Share API requires a
  // live user activation; awaiting fetch() inside the click handler would drop
  // that activation and throw NotAllowedError on Android Chrome.
  useEffect(() => {
    let cancelled = false;
    (async () => {
      setLoading(true);
      setError('');
      try {
        const [s, list] = await Promise.all([
          apiCall(`/shops/${shopId}`).catch(() => null),
          apiCall(`/shops/${shopId}/garments`),
        ]);
        if (cancelled) return;
        const found = (list || []).find((x) => x.id === garmentId);
        if (!found) throw new Error('Item not found.');
        setShop(s);
        setGarment(found);
        setVariants(
          found.variant_group_id
            ? (list || []).filter((x) => x.variant_group_id === found.variant_group_id)
            : [],
        );
      } catch (e) {
        if (!cancelled) setError(e.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [apiCall, shopId, garmentId]);

  function applyTryonUpdate({ status, image_url, pending_text, error }) {
    setTryon((cur) => {
      if (status === 'error') return { status: 'error', error };
      const next = { ...(cur || {}), status };
      if (pending_text !== undefined) next.pending_text = pending_text;
      if (image_url !== undefined) next.image_url = imgUrl(image_url);
      return next;
    });
  }

  async function runTryon() {
    if (!garment || tryon?.status === 'pending') return;
    if (isTattoo) {
      // Tattoo try-on needs the buyer's own uploaded photo — that flow lives in
      // the shop studio, so send them there.
      navigate(`/shops/${shopId}`);
      return;
    }
    if (!(await ensureProfilePhoto())) return;
    setTryon({ status: 'pending' });
    await _StyleKroGen.runTryon({
      streamSSE,
      url: `/garments/${garment.id}/tryon/stream`,
      token,
      body: JSON.stringify({}),
      headers: { 'Content-Type': 'application/json' },
      onUpdate: applyTryonUpdate,
    });
  }

  async function shareItem() {
    const link = `${window.location.protocol}//${window.location.host}${window.location.pathname}#/shops/${shopId}/items/${garmentId}`;
    const data = {
      title: garment?.title ? `${garment.title} · StyleKro` : 'StyleKro',
      text: garment?.title
        ? `Try on ${garment.title} with AI on StyleKro — see how it looks on you before you buy.`
        : 'Try this on with AI on StyleKro — see how it looks on you before you buy.',
      url: link,
    };
    if (navigator.share) {
      try { await navigator.share(data); return; } catch { /* fall through */ }
    }
    try {
      await navigator.clipboard.writeText(link);
      setShareCopied(true);
      setTimeout(() => setShareCopied(false), 1600);
    } catch { /* ignore */ }
  }

  if (loading) {
    return <section className="page-wrap"><div className="boot-splash">{t('boot.loading') || 'Loading…'}</div></section>;
  }
  if (!garment) {
    return (
      <section className="page-wrap">
        {error && <p className="error">{error}</p>}
        <button type="button" className="btn btn-ghost" onClick={() => navigate(`/shops/${shopId}`)}>← Back to shop</button>
      </section>
    );
  }

  const extraUrls = (garment.extra_photo_urls || []).map((u) => imgUrl(u));
  const gallery = [imgUrl(garment.photo_url), ...extraUrls].filter(Boolean);
  const safeIdx = Math.min(Math.max(galleryIdx, 0), gallery.length - 1);
  const heroSrc = (tryon?.status === 'fast' || tryon?.status === 'done')
    ? tryon.image_url
    : gallery[safeIdx];
  const tryingOn = tryon?.status === 'pending';
  const showingTryon = tryon?.status === 'fast' || tryon?.status === 'done';

  const breadcrumb = [garment.gender, garment.category_group, garment.subcategory]
    .filter(Boolean)
    .join(' › ');

  return (
    <section className="page-wrap buyer-item-screen">
      <header className="bi-topbar">
        <button type="button" className="bi-icon-btn" onClick={() => navigate(`/shops/${shopId}`)} aria-label="Back">
          <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
        </button>
        <div className="bi-topbar-title">
          {shop?.name && <span className="bi-topbar-eyebrow">{shop.name}</span>}
          <span className="bi-topbar-name">{garment.title || 'Item'}</span>
        </div>
        <button type="button" className="bi-icon-btn" onClick={shareItem} aria-label="Share">
          {shareCopied ? (
            <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
          ) : (
            <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="M8.6 13.5l6.8 4M15.4 6.5l-6.8 4"/></svg>
          )}
        </button>
      </header>

      <div className="bi-hero">
        <div className="bi-hero-stage">
          {sheet360Open ? (
            <div className="bi-hero-360">
              <Sheet360Viewer
                active={sheet360Open}
                fetchSheet={fetchItemSheet360}
                onExit={() => setSheet360Open(false)}
                exitLabel="✕ Exit 360°"
              />
            </div>
          ) : heroSrc ? (
            <img
              key={`${showingTryon ? 'try' : safeIdx}`}
              src={heroSrc}
              alt={garment.title || 'Item'}
            />
          ) : (
            <div className="bi-hero-empty">{t('item.noImage')}</div>
          )}
          {!sheet360Open && showingTryon && (
            <button
              type="button"
              className="bi-hero-revert"
              onClick={() => setTryon(null)}
            >{t('item.showProduct')}</button>
          )}
          {!sheet360Open && tryingOn && (
            <div className="bi-hero-overlay">
              <span className="spinner" aria-hidden="true" />
              <span>{tryon?.pending_text || t('item.tryingOn')}</span>
            </div>
          )}
          {!sheet360Open && tryon?.status === 'fast' && (
            <span className="bi-hero-tag">{t('tailor.quickPreview')}</span>
          )}
          {!sheet360Open && tryon?.status === 'done' && (
            <span className="bi-hero-tag is-done">{t('item.onYou')}</span>
          )}
        </div>
        {gallery.length > 1 && (
          <div className="bi-thumb-strip">
            {gallery.map((src, i) => (
              <button
                type="button"
                key={i}
                className={`bi-thumb${i === safeIdx && !showingTryon ? ' is-active' : ''}`}
                onClick={() => { setTryon(null); setGalleryIdx(i); }}
                aria-label={`Photo ${i + 1}`}
              >
                <img src={src} alt="" />
              </button>
            ))}
          </div>
        )}
      </div>

      <div className="bi-headline">
        <h1>{garment.title}</h1>
        {garment.price && <div className="bi-price">{garment.price}</div>}
        {breadcrumb && <div className="bi-breadcrumb">{breadcrumb}</div>}
      </div>

      {variants.length > 1 && (
        <div className="bi-variants">
          <span className="bi-variants-label">{t('item.colour')}: <strong>{garment.color || '—'}</strong></span>
          <div className="bi-variants-row">
            {variants.map((v) => {
              const isCurrent = v.id === garmentId;
              return (
                <button
                  type="button"
                  key={v.id}
                  className={`bi-variant-swatch${isCurrent ? ' is-active' : ''}`}
                  onClick={() => { if (!isCurrent) navigate(`/shops/${shopId}/items/${v.id}`); }}
                  aria-label={v.color || 'Colour'}
                  aria-current={isCurrent ? 'true' : undefined}
                  title={v.color || ''}
                >
                  {imgUrl(v.photo_url)
                    ? <img src={imgUrl(v.photo_url)} alt={v.color || ''} />
                    : <span className="bi-variant-swatch-empty" aria-hidden="true" />}
                </button>
              );
            })}
          </div>
        </div>
      )}

      <div className="bi-cta-card">
        <div className="bi-cta-row">
          <StyleKroButton
            busy={tryingOn}
            busyLabel={t('item.tryingOnShort')}
            disabled={tryingOn}
            label={isTattoo ? 'Try in studio' : (showingTryon ? 'Style New' : undefined)}
            onClick={runTryon}
          />
          <button
            type="button"
            className="bi-cta-secondary"
            onClick={() => setSheet360Open(true)}
          >
            <span className="bi-cta-icon" aria-hidden="true">
                <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M4 11a8 8 0 0 1 13.2-5.9"/>
                  <path d="M20 13a8 8 0 0 1-13.2 5.9"/>
                  <path d="M17 3v4h-4"/>
                  <path d="M7 21v-4h4"/>
                  <circle cx="12" cy="12" r="2.5"/>
              </svg>
            </span>
            {t('item.view360')}
          </button>
        </div>
        {tryon?.status === 'error' && <p className="bi-cta-error">⚠ {tryon.error}</p>}
        <p className="bi-cta-hint">{isTattoo
          ? 'Tattoos are placed on your own uploaded photo — open the studio to try this on.'
          : t('item.usesProfile')}</p>
      </div>

      {(garment.color || garment.item_code || garment.asset_kind) && (
        <div className="bi-card">
          <div className="bi-card-head">
            <h2>{t('item.details')}</h2>
          </div>
          <dl className="bi-meta-grid">
            {garment.color && (
              <div className="bi-meta">
                <dt>{t('item.color')}</dt><dd>{garment.color}</dd>
              </div>
            )}
            {garment.item_code && (
              <div className="bi-meta">
                <dt>{t('item.code')}</dt><dd>{garment.item_code}</dd>
              </div>
            )}
            {garment.asset_kind && garment.asset_kind !== 'garment' && (
              <div className="bi-meta">
                <dt>{t('item.type')}</dt><dd>{assetKindLabel(garment.asset_kind) || t('item.garment')}</dd>
              </div>
            )}
            {garment.gender && (
              <div className="bi-meta">
                <dt>{t('item.gender')}</dt><dd>{garment.gender}</dd>
              </div>
            )}
          </dl>
        </div>
      )}

      {garment.description && (
        <div className="bi-card">
          <div className="bi-card-head">
            <h2>{t('item.about')}</h2>
          </div>
          <p className="bi-description">{garment.description}</p>
        </div>
      )}

      {shop && (
        <button
          type="button"
          className="bi-shop-link"
          onClick={() => navigate(`/shops/${shopId}`)}
        >
          <span>
            <strong>{shop.name || t('item.viewShop')}</strong>
            <small>{t('item.browseMore')}</small>
          </span>
          <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 6l6 6-6 6"/></svg>
        </button>
      )}

      <div className="bi-bottom-spacer" aria-hidden="true" />
    </section>
  );
}

function ManageGarmentDetailPage() {
  const { apiCall, t } = useAuth();
  const navigate = useNavigate();
  const confirmAction = useConfirmDialog();
  const { garmentId } = useParams();

  const [shop, setShop] = useState(null);
  const [garment, setGarment] = useState(null);
  const [taxonomy, setTaxonomy] = useState({});
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState('');
  const [savedMsg, setSavedMsg] = useState('');

  const [form, setForm] = useState({
    title: '', price: '', description: '', color: '', item_code: '',
    asset_kind: 'garment', gender: '', category_group: '', subcategory: '',
    is_public: true,
  });
  const [photo, setPhoto] = useState(null);
  const [photoPreview, setPhotoPreview] = useState('');
  const fileRef = useRef(null);
  const extraRefs = [useRef(null), useRef(null), useRef(null)];
  const [extraBusy, setExtraBusy] = useState({});
  const [galleryIdx, setGalleryIdx] = useState(0);
  const [menuOpen, setMenuOpen] = useState(false);
  const [allGarments, setAllGarments] = useState([]);
  const [variantOpen, setVariantOpen] = useState(false);
  const [variantColor, setVariantColor] = useState('');
  const [variantPrice, setVariantPrice] = useState('');
  const [variantPhoto, setVariantPhoto] = useState(null);
  const [variantPreview, setVariantPreview] = useState('');
  const [variantBusy, setVariantBusy] = useState(false);
  const [variantErr, setVariantErr] = useState('');
  const variantFileRef = useRef(null);
  const [lightbox, setLightbox] = useState(null);
  const [cutoutBusy, setCutoutBusy] = useState(false);
  const [cutoutErr, setCutoutErr] = useState('');

  useEffect(() => {
    let cancelled = false;
    (async () => {
      setLoading(true);
      setError('');
      try {
        const s = await apiCall('/shops/me');
        if (!s) throw new Error('No shop found.');
        if (cancelled) return;
        setShop(s);
        const [list, tax] = await Promise.all([
          apiCall(`/shops/${s.id}/garments`),
          apiCall('/garments/taxonomy').catch(() => ({})),
        ]);
        if (cancelled) return;
        const found = (list || []).find((x) => x.id === garmentId);
        if (!found) throw new Error('Item not found.');
        const usesGarmentTaxonomy = shopUsesGarmentTaxonomy(s);
        setGarment(found);
        setAllGarments(list || []);
        setForm({
          title: found.title || '',
          price: found.price || '',
          description: found.description || '',
          color: found.color || '',
          item_code: found.item_code || '',
          tile_size: found.tile_size || '',
          tile_finish: found.tile_finish || '',
          details: { ...(found.details || {}) },
          asset_kind: found.asset_kind || 'garment',
          gender: usesGarmentTaxonomy ? (found.gender || '') : '',
          category_group: usesGarmentTaxonomy ? (found.category_group || '') : '',
          subcategory: usesGarmentTaxonomy ? (found.subcategory || '') : '',
          is_public: found.is_public !== false,
        });
        setTaxonomy(tax && typeof tax === 'object' ? tax : {});
      } catch (e) {
        if (!cancelled) setError(e.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [apiCall, garmentId]);

  const shopCat = shopMeta(shop);
  const isTailorShop = shopCat.key === 'tailor';
  const isTattooShop = shopCat.key === 'tattoo';
  const usesGarmentTaxonomy = shopUsesGarmentTaxonomy(shop);

  function onPhoto(e) {
    const f = e.target.files[0];
    if (!f) return;
    setPhoto(f);
    setPhotoPreview(URL.createObjectURL(f));
  }

  // Tattoo cut-out: run FLUX background removal, then let the seller choose
  // whether buyers see the transparent cut-out or the original photo.
  async function generateCutout() {
    if (cutoutBusy) return;
    setCutoutBusy(true); setCutoutErr('');
    try {
      const updated = await apiCall(`/garments/${garmentId}/cutout`, { method: 'POST' });
      if (updated && updated.id) {
        const v = Date.now();
        setGarment((g) => ({ ...(g || {}), ...updated, cutout_url: updated.cutout_url ? `${updated.cutout_url}?v=${v}` : null }));
      }
    } catch (e) {
      setCutoutErr((e && e.message) || 'Background removal failed.');
    } finally {
      setCutoutBusy(false);
    }
  }

  async function toggleCutout(active) {
    if (cutoutBusy) return;
    setCutoutBusy(true); setCutoutErr('');
    try {
      const updated = await apiCall(`/garments/${garmentId}/cutout/toggle`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ active }),
      });
      if (updated && updated.id) {
        setGarment((g) => ({
          ...(g || {}),
          ...updated,
          cutout_url: updated.cutout_url ? `${updated.cutout_url}?v=${Date.now()}` : (g && g.cutout_url),
        }));
      }
    } catch (e) {
      setCutoutErr((e && e.message) || 'Could not update.');
    } finally {
      setCutoutBusy(false);
    }
  }

  // Rotate the currently shown hero photo 90° clockwise. Slot 1 (main photo)
  // is staged like a normal photo change and persisted on Save; extra slots
  // are uploaded immediately to match the existing per-slot behaviour.
  async function rotateHero() {
    const slot = heroSlot?.slot;
    const src = heroSlot?.url;
    if (!slot || !src) return;
    setExtraBusy((b) => ({ ...b, [slot]: true }));
    setError('');
    try {
      const resp = await fetch(src);
      const blob = await resp.blob();
      const objUrl = URL.createObjectURL(blob);
      const img = await new Promise((res, rej) => {
        const im = new Image();
        im.onload = () => res(im);
        im.onerror = () => rej(new Error('Could not load image'));
        im.src = objUrl;
      });
      const canvas = document.createElement('canvas');
      canvas.width = img.naturalHeight;
      canvas.height = img.naturalWidth;
      const ctx = canvas.getContext('2d');
      ctx.translate(canvas.width / 2, canvas.height / 2);
      ctx.rotate(Math.PI / 2);
      ctx.drawImage(img, -img.naturalWidth / 2, -img.naturalHeight / 2);
      URL.revokeObjectURL(objUrl);
      const outBlob = await new Promise((res, rej) => {
        canvas.toBlob((bb) => (bb ? res(bb) : rej(new Error('Rotate failed'))), 'image/jpeg', 0.92);
      });
      const file = new File([outBlob], `rotated-${Date.now()}.jpg`, { type: 'image/jpeg' });
      if (slot === 1) {
        setPhoto(file);
        setPhotoPreview(URL.createObjectURL(outBlob));
      } else {
        await uploadExtraPhoto(slot, file);
      }
    } catch (err) {
      setError((err && err.message) || 'Could not rotate image.');
    } finally {
      setExtraBusy((b) => ({ ...b, [slot]: false }));
    }
  }

  async function onSave(e) {
    e.preventDefault();
    if (!form.title.trim()) { setError(t('shop.titleRequired') || 'Title cannot be empty.'); return; }
    setSaving(true);
    setError('');
    setSavedMsg('');
    try {
      const fd = new FormData();
      fd.append('title', form.title.trim());
      fd.append('description', form.description.trim());
      fd.append('price', form.price.trim());
      fd.append('color', form.color.trim());
      fd.append('item_code', form.item_code.trim());
      for (const f of shopCat.extraFields) {
        fd.append(f.name, (form[f.name] || '').trim());
      }
      const detailsPayload = {};
      for (const f of (shopCat.detailFields || [])) {
        const raw = form.details ? form.details[f.name] : '';
        const v = (raw == null ? '' : String(raw)).trim();
        detailsPayload[f.name] = v || null;
      }
      if (Object.keys(detailsPayload).length > 0) {
        fd.append('details_json', JSON.stringify(detailsPayload));
      }
      fd.append('asset_kind', form.asset_kind || shopCat.defaultAssetKind);
      fd.append('gender', usesGarmentTaxonomy ? (form.gender || '') : '');
      fd.append('category_group', usesGarmentTaxonomy ? (form.category_group || '') : '');
      fd.append('subcategory', usesGarmentTaxonomy ? (form.subcategory || '') : '');
      fd.append('is_public', form.is_public ? 'true' : 'false');
      if (photo) fd.append('photo', photo);
      const updated = await apiCall(`/garments/${garmentId}`, { method: 'PATCH', body: fd });
      if (photo) updated.photo_url = `${updated.photo_url}?v=${Date.now()}`;
      setGarment(updated);
      setPhoto(null);
      setPhotoPreview('');
      setSavedMsg(t('shop.saved') || '✓ Saved');
      setTimeout(() => setSavedMsg(''), 2000);
    } catch (e) {
      setError(e.message);
    } finally {
      setSaving(false);
    }
  }

  async function onDelete() {
    const ok = await confirmAction(
      t('shop.confirmRemovePiece') || 'Remove this item?',
      { confirmLabel: t('common.delete') || 'Delete', tone: 'danger' },
    );
    if (!ok) return;
    try {
      await apiCall(`/garments/${garmentId}`, { method: 'DELETE' });
      navigate('/shop/manage', { replace: true });
    } catch (e) {
      setError(e.message);
    }
  }

  async function uploadExtraPhoto(slot, file) {
    if (!file) return;
    setExtraBusy((b) => ({ ...b, [slot]: true }));
    setError('');
    try {
      const fd = new FormData();
      fd.append('photo', file);
      const updated = await apiCall(`/garments/${garmentId}/photos/${slot}`, { method: 'PUT', body: fd });
      const urls = (updated.extra_photo_urls || []).map((u) => `${u}?v=${Date.now()}`);
      setGarment({ ...updated, extra_photo_urls: urls });
    } catch (e) {
      setError(e.message);
    } finally {
      setExtraBusy((b) => ({ ...b, [slot]: false }));
    }
  }

  async function removeExtraPhoto(slot) {
    setExtraBusy((b) => ({ ...b, [slot]: true }));
    setError('');
    try {
      const updated = await apiCall(`/garments/${garmentId}/photos/${slot}`, { method: 'DELETE' });
      setGarment(updated);
    } catch (e) {
      setError(e.message);
    } finally {
      setExtraBusy((b) => ({ ...b, [slot]: false }));
    }
  }

  async function refreshGarments() {
    if (!shop) return;
    try {
      const list = await apiCall(`/shops/${shop.id}/garments`);
      setAllGarments(list || []);
      const found = (list || []).find((x) => x.id === garmentId);
      if (found) setGarment((g) => ({ ...g, variant_group_id: found.variant_group_id }));
    } catch { /* ignore refresh errors */ }
  }

  function onVariantPhoto(e) {
    const f = e.target.files?.[0];
    e.target.value = '';
    if (!f) return;
    setVariantPhoto(f);
    setVariantPreview(URL.createObjectURL(f));
  }

  function resetVariantForm() {
    setVariantOpen(false);
    setVariantColor('');
    setVariantPrice('');
    setVariantPhoto(null);
    setVariantPreview('');
    setVariantErr('');
  }

  async function addColorVariant(e) {
    e?.preventDefault?.();
    if (!variantPhoto) { setVariantErr('Add a photo for this colour.'); return; }
    if (!variantColor.trim()) { setVariantErr('Pick a colour.'); return; }
    setVariantBusy(true);
    setVariantErr('');
    try {
      const fd = new FormData();
      fd.append('color', variantColor.trim());
      if (variantPrice.trim()) fd.append('price', variantPrice.trim());
      fd.append('photo', variantPhoto);
      await apiCall(`/garments/${garmentId}/variants`, { method: 'POST', body: fd });
      await refreshGarments();
      resetVariantForm();
    } catch (err) {
      setVariantErr(err.message);
    } finally {
      setVariantBusy(false);
    }
  }

  async function unlinkVariant(id) {
    const ok = await confirmAction(
      'Remove this colour from the group? The item itself is not deleted.',
      { confirmLabel: 'Unlink', tone: 'danger' },
    );
    if (!ok) return;
    try {
      await apiCall(`/garments/${id}/unlink-variant`, { method: 'POST' });
      await refreshGarments();
    } catch (e) {
      setError(e.message);
    }
  }

  if (loading) {
    return <section className="page-wrap"><div className="boot-splash">Loading…</div></section>;
  }
  if (!garment) {
    return (
      <section className="page-wrap">
        {error && <p className="error">{error}</p>}
        <button type="button" className="btn btn-ghost" onClick={() => navigate('/shop/manage')}>← Back to shop</button>
      </section>
    );
  }

  const extraGallery = [2, 3, 4].map((slot) => {
    const url = (garment.extra_photo_urls || []).find((u) => u.endsWith(`/photos/${slot}`)
      || u.includes(`/photos/${slot}?`));
    return { slot, url: url || '' };
  });
  const gallery = [
    { slot: 1, url: photoPreview || imgUrl(garment.photo_url) },
    ...extraGallery.map((s) => ({ slot: s.slot, url: s.url ? imgUrl(s.url) : '' })),
  ];
  const safeIdx = Math.min(Math.max(galleryIdx, 0), gallery.length - 1);
  const heroSlot = gallery[safeIdx] || gallery[0];

  const hasWebShare = typeof navigator !== 'undefined' && typeof navigator.share === 'function';

  const canShareItem = typeof window !== 'undefined'
    && ((window.AndroidShell && typeof window.AndroidShell.shareImage === 'function')
      || hasWebShare);

  // Build the share caption for this single item.
  function itemShareCaption() {
    const { protocol, host, pathname } = window.location;
    const itemLink = `${protocol}//${host}${pathname}#/shops/${shop?.id || ''}/items/${garment.id}`;
    return `✨ Try it on yourself with StyleKro ${itemLink}`;
  }

  // Resolve the current hero photo to an absolute URL, or null.
  function currentShareImageUrl() {
    const rel = (heroSlot && heroSlot.url) || imgUrl(garment.photo_url);
    if (!rel) return null;
    try { return new URL(rel, window.location.href).href; }
    catch { return rel; }
  }

  // Browser fallback: share the actual image file via the Web Share API
  // (lets the user pick Instagram/WhatsApp on mobile). Returns true if shared.
  async function webShareImage(absUrl, caption) {
    if (!hasWebShare) return false;
    try {
      const resp = await fetch(absUrl);
      const blob = await resp.blob();
      const file = new File([blob], 'stylekro.jpg', { type: blob.type || 'image/jpeg' });
      if (navigator.canShare && navigator.canShare({ files: [file] })) {
        await navigator.share({ files: [file], text: caption });
        return true;
      }
      await navigator.share({ text: caption });
      return true;
    } catch (e) { return false; }
  }

  // Share this single item's photo via the system share sheet (WhatsApp,
  // Instagram, etc.). Uses the native Android shell's generic share when
  // available, otherwise the browser Web Share sheet, then a link fallback.
  // The caption carries this item's deep link.
  async function shareItem() {
    const abs = currentShareImageUrl();
    if (!abs) return;
    const caption = itemShareCaption();
    if (window.AndroidShell && typeof window.AndroidShell.shareImage === 'function') {
      try { window.AndroidShell.shareImage(abs, caption); } catch (e) { /* no-op */ }
      return;
    }
    const shared = await webShareImage(abs, caption);
    if (!shared) {
      try { window.open(`https://wa.me/?text=${encodeURIComponent(caption)}`, '_blank'); }
      catch (e) { /* no-op */ }
    }
  }

  function openSlotPicker(slot) {
    if (slot === 1) fileRef.current?.click();
    else {
      const idx = slot - 2;
      extraRefs[idx]?.current?.click();
    }
  }

  const genders = Object.keys(taxonomy);
  const groups = form.gender ? Object.keys(taxonomy[form.gender] || {}) : [];
  const subs = (form.gender && form.category_group)
    ? (taxonomy[form.gender]?.[form.category_group] || [])
    : [];

  const variantGroupId = garment.variant_group_id || null;
  const variants = variantGroupId
    ? allGarments.filter((g) => g.variant_group_id === variantGroupId)
    : [];

  return (
    <section className="page-wrap garment-edit-screen">
      <header className="ge-topbar">
        <button type="button" className="ge-icon-btn" onClick={() => navigate('/shop/manage')} aria-label="Back">
          <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
        </button>
        <div className="ge-topbar-title">
          <span className="ge-topbar-eyebrow">{t('edit.editItem')}</span>
          <span className="ge-topbar-name">{form.title || t('edit.untitledItem')}</span>
        </div>
        <div className="ge-topbar-actions">
          {savedMsg && <span className="ge-toast">{savedMsg}</span>}
          <button
            type="button"
            className="ge-icon-btn"
            onClick={() => setMenuOpen((v) => !v)}
            aria-label="More options"
            aria-expanded={menuOpen}
          >
            <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="5" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="12" cy="19" r="1"/></svg>
          </button>
          {menuOpen && (
            <div className="ge-menu" role="menu" onMouseLeave={() => setMenuOpen(false)}>
              <button type="button" className="ge-menu-item ge-menu-danger" onClick={() => { setMenuOpen(false); onDelete(); }}>
                <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg>
                {t('edit.deleteItem')}
              </button>
            </div>
          )}
        </div>
      </header>

      <form onSubmit={onSave} className="ge-form ge-form-compact" noValidate>
        <div className="ge-hero ge-hero-compact">
          <div className="ge-hero-stage">
            {heroSlot?.url ? (
              <img key={heroSlot.slot} src={heroSlot.url} alt={form.title || 'Item'} />
            ) : (
              <div className="ge-hero-empty">
                <span>{t('edit.addPhotoSlot')} {heroSlot?.slot}</span>
              </div>
            )}
            <button
              type="button"
              className="ge-hero-fab"
              onClick={() => openSlotPicker(heroSlot?.slot || 1)}
              disabled={saving || (heroSlot?.slot && !!extraBusy[heroSlot.slot])}
              aria-label={heroSlot?.url ? t('edit.change') : t('edit.addPhoto')}
              title={heroSlot?.url ? t('edit.change') : t('edit.addPhoto')}
            >
              <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 1 1 3 3L7 19l-4 1 1-4z"/></svg>
            </button>
            {heroSlot?.url && (
              <button
                type="button"
                className="ge-hero-rotate"
                onClick={rotateHero}
                disabled={saving || (heroSlot?.slot && !!extraBusy[heroSlot.slot])}
                aria-label="Rotate photo"
                title="Rotate 90°"
              >
                <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M3 22v-6h6"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/></svg>
              </button>
            )}
            {canShareItem && heroSlot?.url && (
              <button
                type="button"
                className="ge-hero-share"
                onClick={shareItem}
                disabled={saving}
                aria-label="Share item"
                title="Share this item"
              >
                <svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.6" y1="13.5" x2="15.4" y2="17.5"/><line x1="15.4" y1="6.5" x2="8.6" y2="10.5"/></svg>
              </button>
            )}
            {heroSlot?.url && (
              <button
                type="button"
                className="ge-hero-zoom"
                onClick={() => setLightbox(heroSlot.url)}
                aria-label="Zoom photo"
                title="Zoom"
              >
                <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
              </button>
            )}
            <span className={`ge-hero-pill ${form.is_public ? 'is-live' : 'is-hidden'}`}>
              {form.is_public ? t('edit.visible') : t('edit.hidden')}
            </span>
          </div>
          <div className="ge-thumb-strip" role="tablist" aria-label="Product photos">
            {gallery.map((g, i) => {
              const busy = g.slot !== 1 && !!extraBusy[g.slot];
              const active = i === safeIdx;
              return (
                <button
                  type="button"
                  key={g.slot}
                  className={`ge-thumb${active ? ' is-active' : ''}${g.url ? '' : ' is-empty'}${busy ? ' is-busy' : ''}`}
                  onClick={() => {
                    if (!g.url) openSlotPicker(g.slot);
                    else setGalleryIdx(i);
                  }}
                  role="tab"
                  aria-selected={active}
                  aria-label={g.url ? `Show photo ${i + 1}` : `Add photo ${i + 1}`}
                >
                  {g.url ? (
                    <>
                      <img src={g.url} alt="" />
                      {g.slot !== 1 && (
                        <span
                          className="ge-thumb-remove"
                          role="button"
                          tabIndex={0}
                          onClick={(e) => { e.stopPropagation(); removeExtraPhoto(g.slot); }}
                          onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); removeExtraPhoto(g.slot); } }}
                          aria-label="Remove photo"
                        >×</span>
                      )}
                    </>
                  ) : (
                    <span className="ge-thumb-plus" aria-hidden="true">+</span>
                  )}
                  {busy && <span className="ge-thumb-spin" aria-hidden="true"><span className="spinner" /></span>}
                </button>
              );
            })}
          </div>
          <input ref={fileRef} type="file" accept="image/*" hidden onChange={onPhoto} />
          {[0, 1, 2].map((idx) => (
            <input
              key={idx}
              ref={extraRefs[idx]}
              type="file"
              accept="image/*"
              hidden
              onChange={(e) => {
                const f = e.target.files?.[0];
                e.target.value = '';
                if (f) uploadExtraPhoto(idx + 2, f);
              }}
            />
          ))}
        </div>

        {isTattooShop && (
          <div className="ge-card ge-card-compact tat-cutout-card">
            <div className="tat-cutout-head">
              {garment?.cutout_url && (
                <label className="tat-cutout-toggle" title="Choose what buyers see">
                  <input
                    type="checkbox"
                    checked={!!garment?.cutout_active}
                    disabled={cutoutBusy}
                    onChange={(e) => toggleCutout(e.target.checked)}
                  />
                  <span>{garment?.cutout_active ? 'Buyers see the cut-out' : 'Buyers see the original'}</span>
                </label>
              )}
            </div>
            <div className="tat-cutout-body">
              {garment?.cutout_url && (
                <div className="tat-cutout-preview" aria-label="Tattoo cut-out preview">
                  <img src={imgUrl(garment.cutout_url)} alt="Tattoo cut-out" />
                </div>
              )}
              <button
                type="button"
                className="tat-cutout-btn"
                onClick={generateCutout}
                disabled={cutoutBusy}
              >
                {cutoutBusy ? (
                  <><span className="spinner" /> Removing background…</>
                ) : (
                  <>
                    <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                      <circle cx="6" cy="6" r="3" />
                      <circle cx="6" cy="18" r="3" />
                      <line x1="20" y1="4" x2="8.12" y2="15.88" />
                      <line x1="14.47" y1="14.48" x2="20" y2="20" />
                      <line x1="8.12" y1="8.12" x2="12" y2="12" />
                    </svg>
                    Remove background
                  </>
                )}
              </button>
            </div>
            {cutoutErr && <p className="tat-cutout-err">{cutoutErr}</p>}
          </div>
        )}

        <div className="ge-card ge-card-compact">
          <div className="ge-section-grid">
            <label className="ge-input ge-input-lg ge-span-full">
              <span>{t('edit.product')}</span>
              <input
                type="text"
                value={form.title}
                onChange={(e) => setForm((f) => ({ ...f, title: e.target.value }))}
                placeholder={t('edit.enterTitle')}
                required
              />
            </label>
            <div className="ge-stack-field ge-span-full">
              <span className="ge-stack-label">{t('edit.productCategory')}</span>
              {isTailorShop && (
                <div className="ge-stack-field">
                  <AssetKindToggle
                    value={form.asset_kind || 'design'}
                    onChange={(next) => setForm((f) => ({ ...f, asset_kind: next }))}
                  />
                </div>
              )}
              <CategoryItemTypeToggle
                shop={shop}
                value={form.asset_kind}
                onChange={(next) => setForm((f) => ({ ...f, asset_kind: next }))}
              />
              <CategoryExtraFields
                shop={shop}
                values={form}
                onChange={(name, value) => setForm((f) => ({ ...f, [name]: value }))}
                variant="labeled"
              />
              <CategoryDetailFields
                shop={shop}
                values={form}
                onChange={(name, value) => setForm((f) => ({ ...f, details: { ...(f.details || {}), [name]: value } }))}
                variant="labeled"
              />
              {usesGarmentTaxonomy && <>
                <div className="ge-stack-field">
                  <span className="ge-stack-label">{t('edit.gender')}</span>
                  <div className="ge-chip-row">
                    {genders.length === 0 && <span className="ge-muted">{t('edit.noTaxonomy')}</span>}
                    {genders.map((g) => (
                      <button
                        type="button"
                        key={g}
                        className={`ge-chip${form.gender === g ? ' is-active' : ''}`}
                        onClick={() => setForm((f) => ({ ...f, gender: g, category_group: '', subcategory: '' }))}
                      >{g}</button>
                    ))}
                  </div>
                </div>
                <div className="ge-stack-field">
                  <span className="ge-stack-label">{t('edit.group')}</span>
                  <div className="ge-chip-row ge-chip-row-scroll">
                    {groups.length === 0 && <span className="ge-muted">{t('edit.pickGenderFirst')}</span>}
                    {groups.map((grp) => (
                      <button
                        type="button"
                        key={grp}
                        className={`ge-chip${form.category_group === grp ? ' is-active' : ''}`}
                        onClick={() => setForm((f) => ({ ...f, category_group: grp, subcategory: '' }))}
                      >{grp}</button>
                    ))}
                  </div>
                </div>
                <label className="ge-input">
                  <span>{t('edit.subcategory')}</span>
                  <select
                    value={form.subcategory}
                    onChange={(e) => setForm((f) => ({ ...f, subcategory: e.target.value }))}
                    disabled={!form.category_group}
                  >
                    <option value="">{form.category_group ? t('edit.chooseSubcategory') : t('edit.pickGroupFirst')}</option>
                    {subs.map((s) => <option key={s} value={s}>{s}</option>)}
                  </select>
                </label>
              </>}
            </div>
            <label className="ge-input ge-span-full">
              <span>{t('edit.price')}</span>
              <input
                type="text"
                inputMode="decimal"
                value={form.price}
                onChange={(e) => setForm((f) => ({ ...f, price: e.target.value }))}
                placeholder="₹1,499"
              />
            </label>
            <ColorPaletteField
              label={t('edit.color')}
              value={form.color}
              onChange={(next) => setForm((f) => ({ ...f, color: next }))}
            />
            <label className="ge-input ge-input-area ge-span-full">
              <span>{t('edit.aboutItem')}</span>
              <textarea
                rows="3"
                value={form.description}
                onChange={(e) => setForm((f) => ({ ...f, description: e.target.value }))}
                placeholder={t('edit.shortDescription')}
              />
            </label>
            <label className="ge-input ge-span-full">
              <span>{t('edit.codeSku')}</span>
              <input
                type="text"
                value={form.item_code}
                onChange={(e) => setForm((f) => ({ ...f, item_code: e.target.value }))}
                placeholder="SKU-001"
              />
            </label>
            <label className="ge-toggle-row ge-toggle-row-compact ge-span-full">
              <div className="ge-toggle-text">
                <strong>{t('edit.visibleToBuyers')}</strong>
              </div>
              <span className={`ge-switch${form.is_public ? ' is-on' : ''}`}>
                <input
                  type="checkbox"
                  checked={form.is_public}
                  onChange={(e) => setForm((f) => ({ ...f, is_public: e.target.checked }))}
                />
                <span className="ge-switch-knob" aria-hidden="true" />
              </span>
            </label>
          </div>
        </div>

        <div className="ge-card ge-card-compact">
          <div className="ge-card-head ge-card-head-compact">
            <h2>{t('edit.colorVariants')}</h2>
          </div>
          <div className="ge-section-grid">
            <p className="ge-muted ge-span-full" style={{ margin: '0 0 4px' }}>
              {t('edit.sameItemColour')}
            </p>
            <div className="ge-variant-row ge-span-full">
              {variants.map((v) => {
                const isCurrent = v.id === garmentId;
                const colorLabel = isCurrent ? (form.color || v.color || '—') : (v.color || '—');
                return (
                  <div key={v.id} className={`ge-variant${isCurrent ? ' is-current' : ''}`}>
                    <button
                      type="button"
                      className="ge-variant-thumb"
                      onClick={() => { if (!isCurrent) navigate(`/shop/manage/items/${v.id}`); }}
                      aria-label={isCurrent ? `Current colour ${colorLabel}` : `Edit colour ${colorLabel}`}
                      disabled={isCurrent}
                    >
                      {imgUrl(v.photo_url) ? <img src={imgUrl(v.photo_url)} alt={colorLabel} /> : <span className="ge-thumb-plus">?</span>}
                    </button>
                    <span className="ge-variant-color">{colorLabel}</span>
                    {!isCurrent && (
                      <button
                        type="button"
                        className="ge-variant-unlink"
                        onClick={() => unlinkVariant(v.id)}
                        aria-label="Unlink colour"
                      >×</button>
                    )}
                  </div>
                );
              })}
              {!variantOpen && (
                <button
                  type="button"
                  className="ge-variant ge-variant-add"
                  onClick={() => { setVariantOpen(true); setVariantErr(''); }}
                >
                  <span className="ge-variant-thumb ge-variant-add-thumb" aria-hidden="true">+</span>
                  <span className="ge-variant-color">{t('edit.addColour')}</span>
                </button>
              )}
            </div>

            {variantOpen && (
              <div className="ge-variant-form ge-span-full">
                <div className="ge-variant-form-row">
                  <button
                    type="button"
                    className="ge-variant-thumb ge-variant-pick"
                    onClick={() => variantFileRef.current?.click()}
                    aria-label="Choose colour photo"
                  >
                    {variantPreview ? <img src={variantPreview} alt="New colour" /> : <span className="ge-thumb-plus">+</span>}
                  </button>
                  <input ref={variantFileRef} type="file" accept="image/*" hidden onChange={onVariantPhoto} />
                  <div className="ge-variant-form-fields">
                    <ColorPaletteField
                      label={t('edit.colour')}
                      value={variantColor}
                      onChange={setVariantColor}
                      compact
                    />
                    <label className="ge-input">
                      <span>{t('mng.priceOptional')}</span>
                      <input
                        type="text"
                        inputMode="decimal"
                        value={variantPrice}
                        onChange={(e) => setVariantPrice(e.target.value)}
                        placeholder={t('edit.sameAsBase')}
                      />
                    </label>
                  </div>
                </div>
                {variantErr && <p className="ge-error" style={{ margin: '4px 0 0' }}>{variantErr}</p>}
                <div className="ge-variant-form-cta">
                  <button type="button" className="ge-cta-secondary" onClick={resetVariantForm} disabled={variantBusy}>{t('common.cancel')}</button>
                  <button type="button" className="ge-cta-primary" onClick={addColorVariant} disabled={variantBusy}>
                    {variantBusy ? (<><span className="spinner" aria-hidden="true" /> {t('edit.adding')}</>) : t('edit.addColour')}
                  </button>
                </div>
              </div>
            )}
          </div>
        </div>

        {error && <p className="ge-error">{error}</p>}

        <div className="ge-bottom-spacer" aria-hidden="true" />

        <div className="ge-sticky-cta">
          <button
            type="button"
            className="ge-cta-secondary"
            onClick={() => navigate('/shop/manage')}
            disabled={saving}
          >{t('common.cancel')}</button>
          <button type="submit" className="ge-cta-primary" disabled={saving}>
            {saving ? (<><span className="spinner" aria-hidden="true" /> {t('edit.saving')}</>) : t('edit.saveChanges')}
          </button>
        </div>
      </form>
      {lightbox && (
        <div className="lightbox" onClick={() => setLightbox(null)}>
          <button className="lightbox-close" title="Close">×</button>
          <ZoomableImage src={lightbox} />
        </div>
      )}
    </section>
  );
}
