// Shops Tab — per-city shops & stalls, DM-customisable inventory, AI bartering
const SHOP_CATEGORY_META = {
  weapons:    { label: 'Weapons',    color: '#c53030', icon: '⚔' },
  armor:      { label: 'Armor',      color: '#7a5a2a', icon: '🛡' },
  blacksmith: { label: 'Blacksmith', color: '#a14a1a', icon: '🔨' },
  herbal:     { label: 'Apothecary', color: '#1e6b3c', icon: '🌿' },
  materials:  { label: 'Materials',  color: '#6b6966', icon: '⛏' },
  general:    { label: 'General',    color: '#9a8a4a', icon: '🛒' },
  magic:      { label: 'Curiosities',color: '#7a3aa8', icon: '✦' },
  arcane:     { label: 'Arcane',     color: '#3a5fc5', icon: '✦' },
  tavern:     { label: 'Tavern',     color: '#b45309', icon: '🍺' },
};
const SHOP_RARITY_COLOR = {
  common:      '#6b6966',
  uncommon:    '#22c55e',
  rare:        '#3a8fd9',
  'very-rare': '#a05fd9',
  legendary:   '#c9a227',
};
const SHOP_RARITY_ORDER = ['common','uncommon','rare','very-rare','legendary'];

const ShopsTab = ({ campaign, isDM, user }) => {
  const [shops, setShops] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [allCities, setAllCities] = React.useState([]);
  const cityById = React.useCallback((id) => allCities.find(c => c.id === id), [allCities]);
  const cityName = (id) => cityById(id)?.name || '—';
  const cityIds = React.useMemo(() => Array.from(new Set(shops.map(s => s.cityId))), [shops]);
  const [activeCity, setActiveCity] = React.useState('');
  const [activeShopId, setActiveShopId] = React.useState(null);
  const [bartering, setBartering] = React.useState(null); // { shop, item }
  const [editingItem, setEditingItem] = React.useState(null); // { shopId, itemId }
  const [addingItemFor, setAddingItemFor] = React.useState(null); // shopId
  const [addingShop, setAddingShop] = React.useState(false);
  const [editingShopMeta, setEditingShopMeta] = React.useState(null); // shopId

  const refresh = React.useCallback(async () => {
    setLoading(true);
    try {
      const [shopsList, citiesList] = await Promise.all([
        api.shops.list(campaign.id),
        api.cities.list({ campaignId: campaign.id }),
      ]);
      // Parse barteredJson into the legacy `bartered` object the render code expects.
      const augmented = (shopsList || []).map(s => ({
        ...s,
        bartered: (() => { try { return JSON.parse(s.barteredJson || '{}'); } catch { return {}; } })(),
      }));
      setShops(augmented);
      setAllCities(citiesList || []);
    } catch (err) {
      console.error('[ShopsTab] load failed:', err);
    } finally {
      setLoading(false);
    }
  }, [campaign.id]);

  React.useEffect(() => { refresh(); }, [refresh]);
  React.useEffect(() => { if (!activeCity && cityIds[0]) setActiveCity(cityIds[0]); }, [cityIds, activeCity]);

  // Local-state-only mutator (kept so existing handlers compile). For ops that should
  // persist, they explicitly call `api.shops.*` then `refresh()`. Unmigrated handlers
  // (stall reroll, bartering, item edit) currently mutate state in-memory only — they'll
  // come back to mock-style behaviour until each handler routes through the API.
  const persist = (next) => setShops(next);

  const cityShops = shops.filter(s => s.cityId === activeCity);
  const activeShop = cityShops.find(s => s.id === activeShopId) || cityShops[0];

  React.useEffect(() => {
    if (activeShop && activeShop.id !== activeShopId) setActiveShopId(activeShop.id);
  }, [activeCity]);

  // ── DM ops ─────────────────────────────────────────────
  // Reroll: keep ~half of existing items (with re-randomised quantities), drop the rest,
  // and pick fresh from the REPLACEMENT_POOL. All operations route through the API so
  // the new stall state persists across reloads. Bartered prices are reset by clearing
  // BarteredJson on the parent shop.
  const rerollStall = async (shop) => {
    const REPLACEMENT_POOL = [
      { name: 'Mystery Trinket',    description: 'A small carved bone token.',    basePrice: 2,  currency: 'gp', rarity: 'common' },
      { name: 'Half-Burnt Map',     description: 'Of somewhere — possibly real.', basePrice: 5,  currency: 'gp', rarity: 'uncommon' },
      { name: 'Glass Eye',          description: 'Just a glass eye.',             basePrice: 8,  currency: 'sp', rarity: 'common' },
      { name: 'Bundle of Sage',     description: 'Smudge stick. Five uses.',      basePrice: 4,  currency: 'gp', rarity: 'common' },
      { name: 'Cracked Coin',       description: 'Foreign mint. Possibly cursed.',basePrice: 3,  currency: 'gp', rarity: 'uncommon' },
      { name: 'Faded Tapestry',     description: 'Depicts an unknown battle.',    basePrice: 12, currency: 'gp', rarity: 'common' },
      { name: 'Trapper\'s Snare',   description: 'Single-use. Restrains.',        basePrice: 6,  currency: 'gp', rarity: 'common' },
      { name: 'Bottle of Vinegar',  description: 'Quality is questionable.',      basePrice: 1,  currency: 'sp', rarity: 'common' },
      { name: 'Iron Manacles',      description: 'Functional. Slightly rusty.',   basePrice: 2,  currency: 'gp', rarity: 'common' },
      { name: 'Cracked Spyglass',   description: 'Visible distortion in the lens.', basePrice: 25, currency: 'gp', rarity: 'uncommon' },
    ];
    try {
      // Step 1: decide which existing items to drop (random half) and adjust quantities on keepers
      const toDrop = shop.items.filter(() => Math.random() > 0.5);
      const toKeep = shop.items.filter(it => !toDrop.includes(it));
      // Step 2: drop unwanted items, update remaining quantities (in parallel)
      await Promise.all([
        ...toDrop.map(it => api.shops.items.delete(campaign.id, shop.id, it.id)),
        ...toKeep.map(it => api.shops.items.update(campaign.id, shop.id, it.id, { quantity: 1 + Math.floor(Math.random()*5) })),
      ]);
      // Step 3: add fresh picks from the replacement pool
      const pickN = Math.max(2, 6 - toKeep.length);
      const fresh = [...REPLACEMENT_POOL].sort(() => Math.random()-0.5).slice(0, pickN);
      for (const f of fresh) {
        await api.shops.items.add(campaign.id, shop.id, { ...f, quantity: 1 + Math.floor(Math.random()*4) });
      }
      // Step 4: reset bartered prices on the parent shop
      await api.shops.update(campaign.id, shop.id, { barteredJson: '{}' });
      await refresh();
    } catch (err) {
      console.error('[ShopsTab] rerollStall failed:', err);
      window.dialog.alert('Failed to reroll stall: ' + (err.message || 'unknown error'));
    }
  };

  const updateItem = async (shopId, itemId, patch) => {
    try { await api.shops.items.update(campaign.id, shopId, itemId, patch); await refresh(); }
    catch (err) { console.error('[ShopsTab] updateItem failed:', err); window.dialog.alert('Failed to update item: ' + (err.message || 'unknown error')); }
  };
  const removeItem = async (shopId, itemId) => {
    try { await api.shops.items.delete(campaign.id, shopId, itemId); await refresh(); }
    catch (err) { console.error('[ShopsTab] removeItem failed:', err); window.dialog.alert('Failed to remove item: ' + (err.message || 'unknown error')); }
  };
  const addItem = async (shopId, item) => {
    try {
      await api.shops.items.add(campaign.id, shopId, {
        name: item.name, description: item.description || '',
        basePrice: Number(item.basePrice) || 0, currency: item.currency || 'gp',
        quantity: Number(item.quantity) || 1, rarity: item.rarity || 'common',
        materialKind: item.materialKind || null,
      });
      await refresh();
    } catch (err) { console.error('[ShopsTab] addItem failed:', err); window.dialog.alert('Failed to add item: ' + (err.message || 'unknown error')); }
  };
  const updateShopMeta = async (shopId, patch) => {
    try { await api.shops.update(campaign.id, shopId, patch); await refresh(); }
    catch (err) { console.error('[ShopsTab] updateShopMeta failed:', err); window.dialog.alert('Failed to save shop: ' + (err.message || 'unknown error')); }
  };
  const removeShop = async (shopId) => {
    if (!(await window.dialog.confirm({ title:'Delete shop?', message:'Delete this shop entirely?', danger:true, confirmLabel:'Delete' }))) return;
    try {
      await api.shops.delete(campaign.id, shopId);
      if (activeShopId === shopId) setActiveShopId(null);
      await refresh();
    } catch (err) { console.error('[ShopsTab] removeShop failed:', err); window.dialog.alert('Failed to delete shop: ' + (err.message || 'unknown error')); }
  };
  const addShop = async (data) => {
    try {
      const newId = await api.shops.create(campaign.id, {
        cityId: data.cityId || activeCity,
        kind: data.kind || 'shop',
        category: data.category || 'general',
        name: data.name || 'New Shop',
        keeper: data.keeper || 'Unnamed Keeper',
        keeperRace: data.keeperRace || 'Human',
        personality: data.personality || 'Pleasant. Standard markup.',
        flavor: data.flavor || '',
      });
      await refresh();
      if (!cityIds.includes(data.cityId || activeCity)) setActiveCity(data.cityId || activeCity);
      setActiveShopId(newId);
    } catch (err) { console.error('[ShopsTab] addShop failed:', err); window.dialog.alert('Failed to add shop: ' + (err.message || 'unknown error')); }
  };

  // ── Shop list (left rail) ────────────────────────────────
  return (
    <div style={shopStyles.page}>
      {/* City tabs */}
      <div style={shopStyles.cityRow}>
        {cityIds.map(cid => {
          const c = cityById(cid);
          return (
            <button key={cid} style={{ ...shopStyles.cityTab, ...(activeCity === cid ? shopStyles.cityTabActive : {}) }} onClick={() => setActiveCity(cid)}>
              <span style={{ fontSize: 11, opacity: 0.7, fontWeight: 700, letterSpacing: '0.08em' }}>{c?.size?.toUpperCase() || 'CITY'}</span>
              <span style={{ fontFamily: "'Cinzel',serif", fontSize: 16 }}>{c?.name || cid}</span>
            </button>
          );
        })}
        {isDM && (
          <button style={shopStyles.addCityBtn} onClick={() => setAddingShop(true)}>+ New Shop</button>
        )}
      </div>

      {addingShop && <NewShopForm cities={allCities} defaultCity={activeCity} onSave={(d) => { addShop(d); setAddingShop(false); }} onCancel={() => setAddingShop(false)} />}

      <div style={shopStyles.body}>
        {/* Shop rail */}
        <div style={shopStyles.rail}>
          <div style={shopStyles.railHeader}>{cityShops.length} establishment{cityShops.length===1?'':'s'} in {cityName(activeCity)}</div>
          {cityShops.map(s => {
            const meta = SHOP_CATEGORY_META[s.category] || SHOP_CATEGORY_META.general;
            const isActive = activeShop && activeShop.id === s.id;
            return (
              <button key={s.id} style={{ ...shopStyles.shopCard, ...(isActive ? shopStyles.shopCardActive : {}) }} onClick={() => setActiveShopId(s.id)}>
                <div style={{ ...shopStyles.shopIcon, background: meta.color + '22', color: meta.color, borderColor: meta.color + '55' }}>{meta.icon}</div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={shopStyles.shopName}>{s.name}</div>
                  <div style={shopStyles.shopMeta}>
                    <span style={{ color: meta.color, fontWeight: 700 }}>{meta.label}</span>
                    <span style={{ color: '#3a3a42' }}>·</span>
                    <span>{s.keeper}</span>
                  </div>
                </div>
                {s.kind === 'stall' && <span style={shopStyles.stallTag}>Stall</span>}
              </button>
            );
          })}
          {cityShops.length === 0 && <div style={{ color: '#6b6966', fontSize: 13, padding: 14 }}>No shops in this city yet.</div>}
        </div>

        {/* Shop detail */}
        {activeShop ? (
          <ShopDetail
            key={activeShop.id}
            shop={activeShop}
            isDM={isDM}
            onReroll={() => rerollStall(activeShop)}
            onUpdateItem={(itemId, patch) => updateItem(activeShop.id, itemId, patch)}
            onRemoveItem={(itemId) => removeItem(activeShop.id, itemId)}
            onAddItem={() => setAddingItemFor(activeShop.id)}
            onEditMeta={() => setEditingShopMeta(activeShop.id)}
            onDeleteShop={() => removeShop(activeShop.id)}
            onBarter={(item) => setBartering({ shop: activeShop, item })}
            editingItem={editingItem}
            setEditingItem={setEditingItem}
          />
        ) : (
          <div style={{ flex:1, display:'flex', alignItems:'center', justifyContent:'center', color:'#6b6966' }}>
            Select a shop to inspect its inventory.
          </div>
        )}
      </div>

      {/* Add item modal */}
      {addingItemFor && (
        <ItemEditModal
          mode="add"
          onSave={(it) => { addItem(addingItemFor, it); setAddingItemFor(null); }}
          onCancel={() => setAddingItemFor(null)}
        />
      )}

      {/* Edit shop meta modal */}
      {editingShopMeta && (
        <ShopMetaModal
          shop={shops.find(s=>s.id===editingShopMeta)}
          cities={allCities}
          onSave={(patch) => { updateShopMeta(editingShopMeta, patch); setEditingShopMeta(null); }}
          onCancel={() => setEditingShopMeta(null)}
        />
      )}

      {/* Barter overlay */}
      {bartering && (
        <BarterOverlay
          shop={bartering.shop}
          item={bartering.item}
          campaign={campaign}
          onClose={() => setBartering(null)}
          onSettle={async (price) => {
            const shop = bartering.shop;
            const item = bartering.item;
            try {
              // 1. Look up the buyer's character (first character belonging to this user in this campaign)
              const chars = (await api.characters.list({ campaignId: campaign.id })) || [];
              const buyer = chars.find(c => c.playerId === user?.id) || chars[0];
              if (!buyer) { window.dialog.alert('No character found to make this purchase.'); return; }
              if ((buyer.gold||0) < price) {
                if (!(await window.dialog.confirm({ title:'Not enough gold', message:`${buyer.name} only has ${buyer.gold||0} gp — not enough for ${price} gp. Settle anyway? (DM override)`, confirmLabel:'Settle anyway' }))) return;
              }
              // 2. Deduct gold via PATCH character
              await api.characters.update(buyer.id, { gold: Math.max(0, (buyer.gold||0) - price) });
              // 3. Decrement shop stock (delete if last)
              const newQty = Math.max(0, (item.quantity||1) - 1);
              if (newQty === 0) await api.shops.items.delete(campaign.id, shop.id, item.id);
              else              await api.shops.items.update(campaign.id, shop.id, item.id, { quantity: newQty });
              // 4. Clear the bartered price for this item
              const bartered = { ...(shop.bartered||{}) }; delete bartered[item.id];
              await api.shops.update(campaign.id, shop.id, { barteredJson: JSON.stringify(bartered) });
              // 5. Add to party inventory via API
              await api.campaigns.inventory.create(campaign.id, {
                name: item.name,
                description: item.description + ` — Purchased from ${shop.name} for ${price} ${item.currency}.`,
                quantity: 1,
                value: `${price} ${item.currency}`,
              });
              await refresh();
              window.dialog.alert(`Deal settled. ${buyer.name} paid ${price} ${item.currency} for ${item.name}. Added to party inventory.`);
            } catch (err) {
              console.error('[ShopsTab] settle failed:', err);
              window.dialog.alert('Failed to settle: ' + (err.message || 'unknown error'));
            }
            setBartering(null);
          }}
        />
      )}
    </div>
  );
};

// ── Shop detail panel ─────────────────────────────────────
const ShopDetail = ({ shop, isDM, onReroll, onUpdateItem, onRemoveItem, onAddItem, onEditMeta, onDeleteShop, onBarter, editingItem, setEditingItem }) => {
  const meta = SHOP_CATEGORY_META[shop.category] || SHOP_CATEGORY_META.general;
  return (
    <div style={shopStyles.detail}>
      <div style={{ ...shopStyles.detailHeader, borderTop: `3px solid ${meta.color}` }}>
        <div style={{ flex: 1 }}>
          <div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:6 }}>
            <span style={{ fontSize: 20, color: meta.color }}>{meta.icon}</span>
            <h2 style={shopStyles.detailTitle}>{shop.name}</h2>
            {shop.kind === 'stall' && <span style={shopStyles.stallTag}>Stall · rerolls daily</span>}
          </div>
          <div style={shopStyles.detailMeta}>
            <span><strong style={{color:'#c5c3c0'}}>{shop.keeper}</strong> — {shop.keeperRace}</span>
            <span style={{ color:'#3a3a42' }}>·</span>
            <span style={{ color: meta.color, fontWeight:700 }}>{meta.label}</span>
            {shop.lastRerolled && <><span style={{ color:'#3a3a42' }}>·</span><span>last rerolled {shop.lastRerolled}</span></>}
          </div>
        </div>
        {isDM && (
          <div style={{ display:'flex', gap:8 }}>
            {shop.kind === 'stall' && <button style={shopStyles.rerollBtn} onClick={onReroll}>↻ Reroll Inventory</button>}
            <button style={shopStyles.editIconBtn} onClick={onEditMeta}>Edit shop</button>
            <button style={{ ...shopStyles.editIconBtn, borderColor:'#5a2a2a', color:'#c53030' }} onClick={onDeleteShop}>Delete</button>
          </div>
        )}
      </div>

      <div style={shopStyles.flavorBox}>
        <div style={shopStyles.flavorLabel}>The Place</div>
        <p style={shopStyles.flavorText}>{shop.flavor || '—'}</p>
        <div style={{ ...shopStyles.flavorLabel, marginTop:10 }}>The Keeper</div>
        <p style={shopStyles.flavorText}>{shop.personality}</p>
      </div>

      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginTop:18, marginBottom:10 }}>
        <div style={{ fontSize:11, fontWeight:800, color:'#6b6966', textTransform:'uppercase', letterSpacing:'0.15em' }}>Inventory</div>
        {isDM && <button style={shopStyles.addItemBtn} onClick={onAddItem}>+ Add item</button>}
      </div>

      <div style={shopStyles.itemGrid}>
        {[...shop.items].sort((a,b)=>SHOP_RARITY_ORDER.indexOf(a.rarity)-SHOP_RARITY_ORDER.indexOf(b.rarity)).map(item => {
          const isEditing = isDM && editingItem && editingItem.shopId === shop.id && editingItem.itemId === item.id;
          if (isEditing) {
            return <InlineItemEditor key={item.id} item={item} onSave={(patch) => { onUpdateItem(item.id, patch); setEditingItem(null); }} onCancel={() => setEditingItem(null)} onRemove={async () => { if (await window.dialog.confirm({ title:'Remove item?', message:'Remove this item?', danger:true, confirmLabel:'Remove' })) { onRemoveItem(item.id); setEditingItem(null); } }} />;
          }
          const bartered = (shop.bartered||{})[item.id];
          const rarityCol = SHOP_RARITY_COLOR[item.rarity] || '#6b6966';
          return (
            <div key={item.id} style={{ ...shopStyles.itemCard, borderLeftColor: rarityCol }}>
              <div style={{ display:'flex', justifyContent:'space-between', gap:10 }}>
                <div style={{ flex:1, minWidth:0 }}>
                  <div style={shopStyles.itemName}>{item.name}</div>
                  <div style={shopStyles.itemDesc}>{item.description}</div>
                </div>
                <div style={{ textAlign:'right', flexShrink:0 }}>
                  {bartered != null ? (
                    <>
                      <div style={shopStyles.priceStruck}>{item.basePrice} {item.currency}</div>
                      <div style={shopStyles.priceBartered}>{bartered} {item.currency}</div>
                    </>
                  ) : (
                    <div style={shopStyles.price}>{item.basePrice} {item.currency}</div>
                  )}
                  <div style={shopStyles.qty}>×{item.quantity}</div>
                </div>
              </div>
              <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginTop:10, paddingTop:10, borderTop:'1px solid #2a2a32' }}>
                <span style={{ ...shopStyles.rarityChip, color: rarityCol, borderColor: rarityCol+'55', background: rarityCol+'15' }}>{item.rarity}</span>
                <div style={{ display:'flex', gap:6 }}>
                  <button style={shopStyles.barterBtn} onClick={() => onBarter(item)}>{bartered != null ? 'Renegotiate' : 'Barter'}</button>
                  {isDM && <button style={shopStyles.itemEditBtn} onClick={() => setEditingItem({ shopId: shop.id, itemId: item.id })}>edit</button>}
                </div>
              </div>
            </div>
          );
        })}
        {shop.items.length === 0 && <div style={{ color:'#6b6966', fontSize:13, padding:14 }}>No items in stock.</div>}
      </div>
    </div>
  );
};

// ── Inline item editor ────────────────────────────────────
const InlineItemEditor = ({ item, onSave, onCancel, onRemove }) => {
  const [f, setF] = React.useState({ ...item });
  return (
    <div style={{ ...shopStyles.itemCard, borderLeftColor:'#c9a227', background:'#1c1c22' }}>
      <input style={shopStyles.editInput} value={f.name} onChange={e=>setF({...f,name:e.target.value})} placeholder="Name *" />
      <textarea style={{ ...shopStyles.editInput, marginTop:6, minHeight:50, resize:'vertical', fontFamily:"'Nunito',sans-serif" }} value={f.description} onChange={e=>setF({...f,description:e.target.value})} placeholder="Description" />
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:6, marginTop:6 }}>
        <input style={shopStyles.editInput} type="number" value={f.basePrice} onChange={e=>setF({...f,basePrice:Number(e.target.value)})} placeholder="Price" />
        <select style={shopStyles.editInput} value={f.currency} onChange={e=>setF({...f,currency:e.target.value})}>
          <option value="cp">cp</option><option value="sp">sp</option><option value="gp">gp</option><option value="pp">pp</option>
        </select>
        <input style={shopStyles.editInput} type="number" value={f.quantity} onChange={e=>setF({...f,quantity:Number(e.target.value)})} placeholder="Qty" />
      </div>
      <select style={{ ...shopStyles.editInput, marginTop:6 }} value={f.rarity} onChange={e=>setF({...f,rarity:e.target.value})}>
        {SHOP_RARITY_ORDER.map(r => <option key={r} value={r}>{r}</option>)}
      </select>
      <div style={{ display:'flex', justifyContent:'space-between', marginTop:10 }}>
        <button style={{ ...shopStyles.editIconBtn, color:'#c53030', borderColor:'#5a2a2a' }} onClick={onRemove}>Remove</button>
        <div style={{ display:'flex', gap:6 }}>
          <button style={shopStyles.editIconBtn} onClick={onCancel}>Cancel</button>
          <button style={{ ...shopStyles.editIconBtn, color:'#c9a227', borderColor:'#c9a22755' }} onClick={() => onSave(f)}>Save</button>
        </div>
      </div>
    </div>
  );
};

// ── Add-item modal ────────────────────────────────────────
const ItemEditModal = ({ onSave, onCancel }) => {
  const [f, setF] = React.useState({ name:'', description:'', basePrice:10, currency:'gp', quantity:1, rarity:'common' });
  return (
    <div style={shopStyles.modalScrim} {...scrimDismiss(onCancel)}>
      <div style={shopStyles.modalBox} onClick={e=>e.stopPropagation()}>
        <h3 style={{ fontFamily:"'Cinzel',serif", color:'#e8e6e3', marginBottom:14 }}>Add Item</h3>
        <input style={shopStyles.editInput} value={f.name} onChange={e=>setF({...f,name:e.target.value})} placeholder="Item name *" />
        <textarea style={{ ...shopStyles.editInput, marginTop:8, minHeight:60, resize:'vertical', fontFamily:"'Nunito',sans-serif" }} value={f.description} onChange={e=>setF({...f,description:e.target.value})} placeholder="Description" />
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:8, marginTop:8 }}>
          <input style={shopStyles.editInput} type="number" value={f.basePrice} onChange={e=>setF({...f,basePrice:Number(e.target.value)})} placeholder="Price" />
          <select style={shopStyles.editInput} value={f.currency} onChange={e=>setF({...f,currency:e.target.value})}>
            <option value="cp">cp</option><option value="sp">sp</option><option value="gp">gp</option><option value="pp">pp</option>
          </select>
          <input style={shopStyles.editInput} type="number" value={f.quantity} onChange={e=>setF({...f,quantity:Number(e.target.value)})} placeholder="Qty" />
        </div>
        <select style={{ ...shopStyles.editInput, marginTop:8 }} value={f.rarity} onChange={e=>setF({...f,rarity:e.target.value})}>
          {SHOP_RARITY_ORDER.map(r => <option key={r} value={r}>{r}</option>)}
        </select>
        <div style={{ display:'flex', justifyContent:'flex-end', gap:8, marginTop:14 }}>
          <button style={shopStyles.editIconBtn} onClick={onCancel}>Cancel</button>
          <button style={{ ...shopStyles.editIconBtn, color:'#c9a227', borderColor:'#c9a22755' }} onClick={() => onSave(f)} disabled={!f.name.trim()}>Add</button>
        </div>
      </div>
    </div>
  );
};

// ── Edit shop metadata modal ──────────────────────────────
const ShopMetaModal = ({ shop, cities = [], onSave, onCancel }) => {
  const [f, setF] = React.useState({ ...shop });
  return (
    <div style={shopStyles.modalScrim} {...scrimDismiss(onCancel)}>
      <div style={shopStyles.modalBox} onClick={e=>e.stopPropagation()}>
        <h3 style={{ fontFamily:"'Cinzel',serif", color:'#e8e6e3', marginBottom:14 }}>Edit Shop</h3>
        <input style={shopStyles.editInput} value={f.name} onChange={e=>setF({...f,name:e.target.value})} placeholder="Shop name *" />
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:8, marginTop:8 }}>
          <input style={shopStyles.editInput} value={f.keeper} onChange={e=>setF({...f,keeper:e.target.value})} placeholder="Keeper name" />
          <input style={shopStyles.editInput} value={f.keeperRace} onChange={e=>setF({...f,keeperRace:e.target.value})} placeholder="Race" />
        </div>
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:8, marginTop:8 }}>
          <select style={shopStyles.editInput} value={f.cityId} onChange={e=>setF({...f,cityId:e.target.value})}>
            <option value="">— City —</option>
            {cities.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
          </select>
          <select style={shopStyles.editInput} value={f.kind} onChange={e=>setF({...f,kind:e.target.value})}>
            <option value="shop">Shop (permanent)</option>
            <option value="stall">Stall (rerolls)</option>
          </select>
          <select style={shopStyles.editInput} value={f.category} onChange={e=>setF({...f,category:e.target.value})}>
            {Object.entries(SHOP_CATEGORY_META).map(([k,v]) => <option key={k} value={k}>{v.label}</option>)}
          </select>
        </div>
        <textarea style={{ ...shopStyles.editInput, marginTop:8, minHeight:60, resize:'vertical', fontFamily:"'Nunito',sans-serif" }} value={f.flavor||''} onChange={e=>setF({...f,flavor:e.target.value})} placeholder="The place — atmosphere, location, sights, smells" />
        <textarea style={{ ...shopStyles.editInput, marginTop:8, minHeight:80, resize:'vertical', fontFamily:"'Nunito',sans-serif" }} value={f.personality} onChange={e=>setF({...f,personality:e.target.value})} placeholder="Keeper personality, haggling style, quirks" />
        <div style={{ display:'flex', justifyContent:'flex-end', gap:8, marginTop:14 }}>
          <button style={shopStyles.editIconBtn} onClick={onCancel}>Cancel</button>
          <button style={{ ...shopStyles.editIconBtn, color:'#c9a227', borderColor:'#c9a22755' }} onClick={() => onSave(f)}>Save</button>
        </div>
      </div>
    </div>
  );
};

// ── New shop form (top-level row) ─────────────────────────
const NewShopForm = ({ cities, defaultCity, onSave, onCancel }) => {
  const [f, setF] = React.useState({ name:'', keeper:'', keeperRace:'Human', cityId: defaultCity || '', kind:'shop', category:'general', personality:'', flavor:'' });
  return (
    <div style={shopStyles.newShopBar}>
      <input style={shopStyles.editInput} value={f.name} onChange={e=>setF({...f,name:e.target.value})} placeholder="Shop name *" />
      <input style={shopStyles.editInput} value={f.keeper} onChange={e=>setF({...f,keeper:e.target.value})} placeholder="Keeper" />
      <select style={shopStyles.editInput} value={f.cityId} onChange={e=>setF({...f,cityId:e.target.value})}>
        <option value="">— City * —</option>
        {cities.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
      </select>
      <select style={shopStyles.editInput} value={f.kind} onChange={e=>setF({...f,kind:e.target.value})}>
        <option value="shop">Shop</option><option value="stall">Stall</option>
      </select>
      <select style={shopStyles.editInput} value={f.category} onChange={e=>setF({...f,category:e.target.value})}>
        {Object.entries(SHOP_CATEGORY_META).map(([k,v]) => <option key={k} value={k}>{v.label}</option>)}
      </select>
      <button style={shopStyles.editIconBtn} onClick={onCancel}>Cancel</button>
      <button style={{ ...shopStyles.editIconBtn, color:'#c9a227', borderColor:'#c9a22755' }} onClick={() => onSave(f)} disabled={!f.name.trim() || !f.cityId}>Create</button>
    </div>
  );
};

// ── Barter overlay (Claude-powered) ───────────────────────
const BarterOverlay = ({ shop, item, campaign, onClose, onSettle }) => {
  const [messages, setMessages] = React.useState([]);
  const [input, setInput] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const [proposed, setProposed] = React.useState(null); // last detected price the keeper proposed
  const bottomRef = React.useRef(null);

  React.useEffect(() => {
    if (bottomRef.current) bottomRef.current.scrollTop = bottomRef.current.scrollHeight;
  }, [messages, loading]);

  const buildSystemPrompt = () => {
    return `You are ${shop.keeper}, a ${shop.keeperRace} ${SHOP_CATEGORY_META[shop.category]?.label?.toLowerCase()||'shop'} keeper running "${shop.name}" in the city of ${shop.cityName || shop.cityId}.
Setting: ${campaign.name} — ${campaign.setting}.

YOUR PERSONALITY: ${shop.personality}
YOUR SHOP: ${shop.flavor || 'A standard establishment.'}

A customer is trying to barter with you over a single item:
  ITEM: ${item.name}
  DESCRIPTION: ${item.description}
  RARITY: ${item.rarity}
  STOCK: ${item.quantity}
  YOUR ASKING PRICE: ${item.basePrice} ${item.currency}

YOUR FLOOR: ${Math.max(1, Math.round(item.basePrice * 0.65))} ${item.currency} — never go below this. Below floor, refuse firmly in character.
TYPICAL CEILING DISCOUNT: 10–25% off asking, more only if the customer makes a genuinely persuasive argument, brings a relevant favour, or trades something of equal worth.

RULES:
- Stay fully in character as ${shop.keeper}. Never break the fourth wall.
- Keep replies SHORT — 1-3 sentences, conversational. This is haggling, not a monologue.
- React naturally to flattery, threats, sob stories, and bargaining tactics — your personality determines whether they work.
- When you state a price you'd accept, write it on its own line at the end as: PRICE: <number> ${item.currency}
- If you absolutely refuse and the negotiation is over, write: PRICE: REFUSED on its own line.
- Otherwise no PRICE tag — keep talking.

Begin now. Respond only as ${shop.keeper}.`;
  };

  const parsePrice = (text) => {
    const m = text.match(/PRICE:\s*(\d+)\s*[a-z]*/i);
    if (m) return Number(m[1]);
    const r = text.match(/PRICE:\s*REFUSED/i);
    if (r) return 'REFUSED';
    return null;
  };
  const stripPrice = (text) => text.replace(/\s*PRICE:\s*(\d+\s*[a-z]*|REFUSED)\s*$/im, '').trim();

  const send = async () => {
    if (!input.trim() || loading) return;
    const userMsg = { role: 'user', content: input.trim() };
    const next = [...messages, userMsg];
    setMessages(next);
    setInput('');
    setLoading(true);
    try {
      const history = next.map(m => ({ role: m.role === 'npc' ? 'assistant' : 'user', content: m.content }));
      const response = await window.claude.complete({
        messages: [
          { role: 'user', content: buildSystemPrompt() },
          { role: 'assistant', content: `*${shop.keeper} looks up from the counter, gauging the customer.*` },
          ...history,
        ],
      });
      const price = parsePrice(response);
      const cleaned = stripPrice(response);
      setMessages(prev => [...prev, { role:'npc', content: cleaned, price }]);
      if (typeof price === 'number') setProposed(price);
      if (price === 'REFUSED') setProposed('REFUSED');
    } catch (e) {
      setMessages(prev => [...prev, { role:'npc', content: '*(The keeper grunts noncommittally — the connection is poor.)*' }]);
    }
    setLoading(false);
  };

  const onKey = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } };
  const meta = SHOP_CATEGORY_META[shop.category] || SHOP_CATEGORY_META.general;
  const rarityCol = SHOP_RARITY_COLOR[item.rarity] || '#6b6966';

  return (
    <div style={shopStyles.modalScrim} {...scrimDismiss(onClose)}>
      <div style={shopStyles.barterBox} onClick={e=>e.stopPropagation()}>
        <div style={{ ...shopStyles.barterHeader, borderTop:`3px solid ${meta.color}` }}>
          <div>
            <div style={{ fontSize:11, color:'#6b6966', fontWeight:800, letterSpacing:'0.1em', textTransform:'uppercase' }}>Bartering with {shop.keeper}</div>
            <div style={{ fontFamily:"'Cinzel',serif", fontSize:18, color:'#e8e6e3' }}>{item.name}</div>
            <div style={{ fontSize:12, color:'#9a9793' }}>{item.description}</div>
          </div>
          <button style={shopStyles.closeBtn} onClick={onClose}>✕</button>
        </div>

        <div style={shopStyles.barterPriceRow}>
          <span style={{ fontSize:11, color:'#6b6966', textTransform:'uppercase', letterSpacing:'0.1em' }}>Asking</span>
          <span style={{ ...shopStyles.price, fontSize:18 }}>{item.basePrice} {item.currency}</span>
          <span style={{ ...shopStyles.rarityChip, color:rarityCol, borderColor:rarityCol+'55', background:rarityCol+'15' }}>{item.rarity}</span>
          {typeof proposed === 'number' && (
            <span style={{ marginLeft:'auto', display:'flex', alignItems:'center', gap:8 }}>
              <span style={{ fontSize:11, color:'#6b6966', textTransform:'uppercase', letterSpacing:'0.1em' }}>Last offer</span>
              <span style={{ ...shopStyles.priceBartered, fontSize:18 }}>{proposed} {item.currency}</span>
              <button style={{ ...shopStyles.editIconBtn, color:'#c9a227', borderColor:'#c9a22755' }} onClick={() => onSettle(proposed)}>Accept · settle deal</button>
            </span>
          )}
          {proposed === 'REFUSED' && <span style={{ marginLeft:'auto', color:'#f87171', fontSize:13, fontWeight:700 }}>The keeper refused.</span>}
        </div>

        <div style={shopStyles.barterMessages} ref={bottomRef}>
          {messages.length === 0 && <div style={{ color:'#6b6966', fontSize:13, textAlign:'center', padding:'40px 20px' }}>Open the negotiation. Make your case.</div>}
          {messages.map((m,i) => (
            <div key={i} style={{ ...shopStyles.barterBubble, ...(m.role==='user' ? shopStyles.bubbleUser : shopStyles.bubbleNpc) }}>
              <div style={{ fontSize:10, fontWeight:800, color:'#6b6966', textTransform:'uppercase', letterSpacing:'0.1em', marginBottom:4 }}>{m.role==='user' ? 'You' : shop.keeper}</div>
              <div style={{ fontSize:14, color:'#e8e6e3', lineHeight:1.6, fontStyle: m.content.startsWith('*') ? 'italic' : 'normal' }}>{m.content}</div>
              {typeof m.price === 'number' && <div style={{ marginTop:6, ...shopStyles.priceBartered, fontSize:14 }}>Offer: {m.price} {item.currency}</div>}
            </div>
          ))}
          {loading && (
            <div style={{ ...shopStyles.barterBubble, ...shopStyles.bubbleNpc }}>
              <div style={{ fontSize:10, fontWeight:800, color:'#6b6966', textTransform:'uppercase', letterSpacing:'0.1em', marginBottom:4 }}>{shop.keeper}</div>
              <div style={{ display:'flex', gap:4 }}>
                <span className="typing-dot"></span><span className="typing-dot"></span><span className="typing-dot"></span>
              </div>
            </div>
          )}
        </div>

        <div style={shopStyles.barterInputRow}>
          <textarea style={shopStyles.barterTextarea} value={input} onChange={e=>setInput(e.target.value)} onKeyDown={onKey} placeholder={`Make your offer to ${shop.keeper}…`} rows={2} disabled={loading} />
          <button style={{ ...shopStyles.sendBtn, opacity: loading||!input.trim()?0.5:1 }} onClick={send} disabled={loading||!input.trim()}>Send ↵</button>
        </div>
      </div>
    </div>
  );
};

// ── Styles ────────────────────────────────────────────────
const shopStyles = {
  page: { display:'flex', flexDirection:'column', gap:14 },
  cityRow: { display:'flex', gap:8, flexWrap:'wrap', alignItems:'stretch' },
  cityTab: { background:'#1c1c22', border:'1px solid #2a2a32', borderRadius:8, padding:'10px 16px', cursor:'pointer', color:'#9a9793', display:'flex', flexDirection:'column', alignItems:'flex-start', gap:2, fontFamily:"'Nunito',sans-serif" },
  cityTabActive: { background:'#22222a', border:'1px solid #c9a22755', color:'#e8e6e3' },
  addCityBtn: { marginLeft:'auto', background:'rgba(201,162,39,0.08)', border:'1px solid rgba(201,162,39,0.3)', color:'#c9a227', borderRadius:8, padding:'0 18px', cursor:'pointer', fontWeight:700, fontFamily:"'Nunito',sans-serif", fontSize:13 },
  newShopBar: { display:'grid', gridTemplateColumns:'2fr 1.5fr 1fr 0.8fr 1fr auto auto', gap:8, padding:12, background:'#16161b', border:'1px solid #2a2a32', borderRadius:8 },
  body: { display:'flex', gap:14, alignItems:'flex-start' },
  rail: { width:280, flexShrink:0, display:'flex', flexDirection:'column', gap:6 },
  railHeader: { fontSize:10, fontWeight:800, color:'#6b6966', textTransform:'uppercase', letterSpacing:'0.15em', padding:'0 4px 4px' },
  shopCard: { display:'flex', alignItems:'center', gap:10, background:'#1c1c22', border:'1px solid #2a2a32', borderRadius:8, padding:'10px 12px', cursor:'pointer', textAlign:'left', fontFamily:"'Nunito',sans-serif" },
  shopCardActive: { background:'#22222a', border:'1px solid #c9a22755' },
  shopIcon: { width:36, height:36, borderRadius:6, display:'flex', alignItems:'center', justifyContent:'center', fontSize:16, border:'1px solid', flexShrink:0 },
  shopName: { fontSize:13, fontWeight:700, color:'#e8e6e3', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' },
  shopMeta: { fontSize:11, color:'#9a9793', display:'flex', gap:6, marginTop:2 },
  stallTag: { fontSize:10, fontWeight:800, color:'#c9a227', background:'rgba(201,162,39,0.1)', border:'1px solid rgba(201,162,39,0.3)', borderRadius:10, padding:'2px 8px', textTransform:'uppercase', letterSpacing:'0.1em', flexShrink:0 },

  detail: { flex:1, minWidth:0, background:'#16161b', border:'1px solid #2a2a32', borderRadius:10, padding:'18px 22px' },
  detailHeader: { display:'flex', justifyContent:'space-between', gap:14, paddingTop:8, marginTop:-8, marginBottom:14 },
  detailTitle: { fontFamily:"'Cinzel',serif", fontSize:22, color:'#e8e6e3', margin:0 },
  detailMeta: { fontSize:12, color:'#9a9793', display:'flex', gap:8, flexWrap:'wrap' },
  rerollBtn: { background:'rgba(201,162,39,0.08)', border:'1px solid rgba(201,162,39,0.3)', color:'#c9a227', borderRadius:6, padding:'6px 12px', cursor:'pointer', fontSize:12, fontWeight:700, fontFamily:"'Nunito',sans-serif" },
  editIconBtn: { background:'none', border:'1px solid #3a3a42', color:'#c5c3c0', borderRadius:5, padding:'6px 12px', cursor:'pointer', fontSize:12, fontFamily:"'Nunito',sans-serif" },
  flavorBox: { background:'#1c1c22', border:'1px solid #2a2a32', borderRadius:8, padding:'12px 16px' },
  flavorLabel: { fontSize:10, fontWeight:800, color:'#6b6966', textTransform:'uppercase', letterSpacing:'0.15em', marginBottom:4 },
  flavorText: { fontSize:13, color:'#c5c3c0', lineHeight:1.6, margin:0, fontStyle:'italic' },

  addItemBtn: { background:'none', border:'1px solid #3a3a42', color:'#9a9793', borderRadius:5, padding:'4px 10px', cursor:'pointer', fontSize:11, fontWeight:700, fontFamily:"'Nunito',sans-serif" },
  itemGrid: { display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(280px, 1fr))', gap:10 },
  itemCard: { background:'#1c1c22', border:'1px solid #2a2a32', borderLeft:'3px solid #6b6966', borderRadius:8, padding:'12px 14px' },
  itemName: { fontSize:14, fontWeight:700, color:'#e8e6e3', marginBottom:3 },
  itemDesc: { fontSize:12, color:'#9a9793', lineHeight:1.5 },
  price: { fontSize:14, fontWeight:800, color:'#c9a227', fontFamily:"'Cinzel',serif" },
  priceStruck: { fontSize:11, color:'#6b6966', textDecoration:'line-through', fontFamily:"'Cinzel',serif" },
  priceBartered: { fontSize:14, fontWeight:800, color:'#22c55e', fontFamily:"'Cinzel',serif" },
  qty: { fontSize:11, color:'#6b6966', marginTop:2 },
  rarityChip: { fontSize:10, fontWeight:700, padding:'2px 8px', borderRadius:10, border:'1px solid', textTransform:'uppercase', letterSpacing:'0.08em' },
  barterBtn: { background:'rgba(201,162,39,0.08)', border:'1px solid rgba(201,162,39,0.3)', color:'#c9a227', borderRadius:5, padding:'4px 10px', cursor:'pointer', fontSize:11, fontWeight:700, fontFamily:"'Nunito',sans-serif" },
  itemEditBtn: { background:'none', border:'1px solid #3a3a42', color:'#6b6966', borderRadius:5, padding:'4px 8px', cursor:'pointer', fontSize:11, fontFamily:"'Nunito',sans-serif" },

  editInput: { background:'#111116', border:'1px solid #2a2a32', borderRadius:6, color:'#e8e6e3', padding:'8px 10px', fontSize:13, outline:'none', fontFamily:"'Nunito',sans-serif", width:'100%', boxSizing:'border-box' },

  // Modals
  modalScrim: { position:'fixed', inset:0, background:'rgba(0,0,0,0.7)', display:'flex', alignItems:'center', justifyContent:'center', zIndex:1000, padding:20 },
  modalBox: { background:'#16161b', border:'1px solid #2a2a32', borderRadius:10, padding:24, width:'100%', maxWidth:480 },

  // Barter overlay
  barterBox: { background:'#16161b', border:'1px solid #2a2a32', borderRadius:10, width:'100%', maxWidth:780, height:'85vh', display:'flex', flexDirection:'column' },
  barterHeader: { display:'flex', justifyContent:'space-between', alignItems:'flex-start', padding:'18px 22px', borderBottom:'1px solid #2a2a32' },
  closeBtn: { background:'none', border:'none', color:'#6b6966', fontSize:18, cursor:'pointer', padding:4 },
  barterPriceRow: { display:'flex', alignItems:'center', gap:12, padding:'12px 22px', background:'#1c1c22', borderBottom:'1px solid #2a2a32' },
  barterMessages: { flex:1, overflowY:'auto', padding:'18px 22px', display:'flex', flexDirection:'column', gap:12 },
  barterBubble: { maxWidth:'78%', padding:'10px 14px', borderRadius:10 },
  bubbleUser: { alignSelf:'flex-end', background:'rgba(197,48,48,0.15)', border:'1px solid rgba(197,48,48,0.25)' },
  bubbleNpc: { alignSelf:'flex-start', background:'#1c1c22', border:'1px solid #2a2a32' },
  barterInputRow: { display:'flex', gap:10, padding:'14px 18px', borderTop:'1px solid #2a2a32', background:'#16161b' },
  barterTextarea: { flex:1, background:'#111116', border:'1px solid #3a3a42', borderRadius:8, color:'#e8e6e3', padding:'10px 14px', fontSize:14, resize:'none', outline:'none', fontFamily:"'Nunito',sans-serif" },
  sendBtn: { background:'#c53030', border:'none', color:'#fff', borderRadius:8, padding:'0 20px', fontWeight:700, fontSize:14, cursor:'pointer', fontFamily:"'Nunito',sans-serif" },
};

Object.assign(window, { ShopsTab, shopStyles });
