// screens-singles.jsx — the singles storefront (SPEC-006, Option A)
//
// The customer-facing side of the real inventory. Every listing is one SKU out
// of INVENTORY: a specific physical card, in a specific condition, that Rook is
// holding. That is why condition sits in the title rather than in a footnote —
// an NM and an LP copy of the same card are different products at different
// prices, and the buyer is choosing between them.
//
// TWO RULES THIS FILE MUST NOT BREAK
//   1. Price comes from skuAsk() and nowhere else. If a customer and Ops ever
//      see different numbers for the same SKU, the shelf is lying.
//   2. A card with no market price has no ask, so it cannot be bought. It is
//      listed as not-yet-priced rather than guessed at or hidden.
//
// No card artwork: per SPEC-006 the publisher's images are not ours to
// reproduce, and the answer is a photograph of our own stock taken at intake.
// Until those photos exist the listing shows a typographic plate, which is an
// honest placeholder — it never pretends to show the card you are buying.

/* ───────────── condition, in plain English ─────────────
   "LP" means nothing to a parent buying a birthday present. The ladder and its
   multipliers live in data-cards.jsx; this is only how we say it out loud. */
const CONDITION_NOTES = {
  NM:  'Looks new. Sharp corners, clean edges, no scratches you would notice.',
  LP:  'Played with, looked after. Maybe light edge wear — still sleeve-ready.',
  MP:  'Clearly handled. Visible edge wear or a small surface mark. Plays fine.',
  HP:  'Well loved. Real wear — creasing, whitening or scuffs you can see.',
  DMG: 'Damaged. Bends, tears or water marks. Priced accordingly.',
};

/* ───────────── typographic listing plate ─────────────
   Stands in for the intake photograph. Set colours come from the era so the
   grid doesn't read as a wall of identical grey boxes. */
const ERA_TINT = {
  Vintage: ['#B98A3E', '#5A3D18'],
  Modern:  ['#4F7DC4', '#1B2E52'],
};
function SinglePlate({ card, set, w = 132 }) {
  const h = Math.round(w * 1.4);
  const px = (n) => Math.max(1, Math.round(n * (w / 132)));
  const [c1, c2] = ERA_TINT[set.era] || ERA_TINT.Modern;
  return (
    <div style={{ width: w, height: h, borderRadius: px(10), flexShrink: 0, position: 'relative', overflow: 'hidden',
      background: `linear-gradient(150deg, ${c1}, ${c2})`, boxShadow: `inset 0 0 0 ${px(2)}px rgba(255,255,255,0.14)`,
      display: 'flex', flexDirection: 'column', justifyContent: 'space-between', padding: px(10), color: '#fff' }}>
      <div style={{ fontFamily: 'var(--ff-mono)', fontSize: px(8), letterSpacing: '0.12em', opacity: 0.8 }}>
        {set.code.toUpperCase()}
      </div>
      <div style={{ fontFamily: 'var(--ff-display)', fontWeight: 800, fontSize: px(15), lineHeight: 1.02, letterSpacing: '-0.02em' }}>
        {card.name}
      </div>
      <div>
        <div style={{ fontFamily: 'var(--ff-mono)', fontSize: px(9), fontWeight: 700 }}>
          {card.number}/{set.printed}
        </div>
        <div style={{ fontFamily: 'var(--ff-mono)', fontSize: px(7), opacity: 0.62, marginTop: px(3), letterSpacing: '0.06em' }}>
          PHOTO AT INTAKE
        </div>
      </div>
    </div>
  );
}

/* ───────────── one row of INVENTORY, resolved for display ───────────── */
function singleListing(row) {
  const card = cardById(row.cardId);
  const set = cardSet(card.setCode);
  const cond = condition(row.condition);
  const ask = skuAsk(row);
  const soldOut = row.qty <= 0;
  return { row, card, set, cond, ask, soldOut, sellable: ask != null && !soldOut };
}
// The same physical card in every condition we hold it in, cheapest first —
// this is the comparison a singles buyer is actually making.
function otherConditions(row) {
  return INVENTORY
    .filter(r => r.cardId === row.cardId && r.sku !== row.sku)
    .map(singleListing)
    .sort((a, b) => (a.ask == null ? Infinity : a.ask) - (b.ask == null ? Infinity : b.ask));
}
function allListings() { return INVENTORY.map(singleListing); }

function SinglesPrice({ ask, size = 'md' }) {
  if (ask == null) {
    return <span className="badge badge-low" style={{ fontSize: size === 'lg' ? 12 : 10.5 }}>Not yet priced</span>;
  }
  return (
    <span style={{ fontFamily: 'var(--ff-display)', fontWeight: 800, fontSize: size === 'lg' ? 30 : 18, letterSpacing: '-0.02em' }}>
      {money(ask)}
    </span>
  );
}

/* ───────────── BROWSE ───────────── */
function SinglesScreen({ t, openSingle, cartCount, onCart, onBell, goPacks, cutoff, lens, openScan, onCategoryQueued }) {
  const [q, setQ] = React.useState('');
  const [setFilter, setSetFilter] = React.useState('all');
  const [rarityFilter, setRarityFilter] = React.useState('all');
  const [condFilter, setCondFilter] = React.useState('all');
  const [sort, setSort] = React.useState('price-desc');

  const all = React.useMemo(allListings, []);
  // Only offer filters that can actually return something.
  const sets = CARD_SETS.filter(s => all.some(x => x.set.code === s.code));
  const rarities = Object.keys(CARD_RARITY).filter(r => all.some(x => x.card.rarity === r));
  const conds = CONDITIONS.filter(c => all.some(x => x.cond.code === c.code));

  const rows = React.useMemo(() => {
    const needle = q.trim().toLowerCase();
    const list = all.filter(x => {
      if (setFilter !== 'all' && x.set.code !== setFilter) return false;
      if (rarityFilter !== 'all' && x.card.rarity !== rarityFilter) return false;
      if (condFilter !== 'all' && x.cond.code !== condFilter) return false;
      if (!needle) return true;
      // Collector numbers get matched three ways, because people type all three:
      // "161", "161/131", and the internal "sv8pt5-161".
      const hay = [x.card.name, x.card.nickname || '', x.card.number,
                   x.card.number + '/' + x.set.printed, x.row.cardId,
                   x.set.name, x.set.code, x.card.rarity, x.cond.label]
        .join(' ').toLowerCase();
      return hay.includes(needle);
    });
    const key = {
      // unpriced sinks to the bottom of both price sorts — it isn't buyable
      'price-desc': (x) => -(x.ask == null ? -1 : x.ask),
      'price-asc':  (x) => (x.ask == null ? Infinity : x.ask),
      'name':       (x) => x.card.name + ' ' + x.cond.code,
      'newest':     (x) => x.set.released,
    }[sort];
    return [...list].sort((a, b) => {
      const ka = key(a), kb = key(b);
      if (typeof ka === 'string') return sort === 'newest' ? kb.localeCompare(ka) : ka.localeCompare(kb);
      return ka - kb;
    });
  }, [q, setFilter, rarityFilter, condFilter, sort, all]);

  const sellable = rows.filter(x => x.sellable).length;
  const filtering = setFilter !== 'all' || rarityFilter !== 'all' || condFilter !== 'all' || q.trim() !== '';
  function clearFilters() { setQ(''); setSetFilter('all'); setRarityFilter('all'); setCondFilter('all'); }

  return (
    <div className="fade-up" style={{ paddingBottom: 18 }}>
      <BrandBar t={t} onBell={onBell} cartCount={cartCount} onCart={onCart} tagline={BRAND.place + ' · Real cards'} />

      {/* The shelves belong on the front door, and this is the front door now.
          Pokémon is the one that's open; the rest say when they land. */}
      <CategoryRail onQueued={onCategoryQueued} />

      <div style={{ padding: '4px 20px 10px' }}>
        <div className="kicker" style={{ marginBottom: 8 }}>Every card, one of one</div>
        <div className="t-display" style={{ fontSize: 32 }}>Singles</div>
        <p className="muted" style={{ fontSize: 13.5, margin: '9px 0 0', lineHeight: 1.4 }}>
          Real cards off Rook's shelf, priced at market — not scalped. Condition is
          listed on every one, because it is what you are paying for.
        </p>
      </div>

      <div style={{ padding: '6px 20px 0' }}>
        <InvSearchBox value={q} onChange={setQ} placeholder="Card name or number…" />
      </div>

      {/* Lens belongs on the front door, and this is now the front door. It is
          also more at home here than it was on the pack shop: a shopper holding
          a card they can't read is looking at a single, not a sealed pack. */}
      {lens && <div style={{ marginTop: 14 }}><ScanEntry onOpen={openScan} /></div>}

      {/* Singles is the front door now, but the weekly pack drop is the reason a
          family on post picks RGS over TCGplayer. Keep it one tap away. */}
      {goPacks && (
        <button onClick={goPacks} style={{ width: 'calc(100% - 40px)', margin: '14px 20px 0', padding: '12px 14px', borderRadius: 16, background: 'var(--ink)', color: 'var(--paper)', display: 'flex', alignItems: 'center', gap: 12, border: 'none', cursor: 'pointer', textAlign: 'left' }}>
          <div style={{ width: 34, height: 34, borderRadius: 10, background: 'color-mix(in oklab, var(--paper) 16%, transparent)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
            <Icon name="truck" size={19} color="var(--paper)" />
          </div>
          <div style={{ flex: 1, lineHeight: 1.25, minWidth: 0 }}>
            <div style={{ fontWeight: 700, fontSize: 14 }}>Sealed packs, delivered on post</div>
            <div style={{ fontFamily: 'var(--ff-mono)', fontSize: 10.5, opacity: 0.7 }}>
              Weekly drop{cutoff && !cutoff.closed ? ' · orders close in ' + cutoff.label : ''}
            </div>
          </div>
          <Icon name="chevR" size={18} color="var(--paper)" />
        </button>
      )}

      <div className="rail" style={{ marginTop: 14 }}>
        <button className={'chip' + (setFilter === 'all' ? ' on' : '')} onClick={() => setSetFilter('all')}>All sets</button>
        {sets.map(s => (
          <button key={s.code} className={'chip' + (setFilter === s.code ? ' on' : '')} onClick={() => setSetFilter(s.code)}>{s.name}</button>
        ))}
      </div>
      <div className="rail" style={{ marginTop: 8 }}>
        <button className={'chip' + (rarityFilter === 'all' ? ' on' : '')} onClick={() => setRarityFilter('all')}>Any rarity</button>
        {rarities.map(r => (
          <button key={r} className={'chip' + (rarityFilter === r ? ' on' : '')} onClick={() => setRarityFilter(r)}>{r}</button>
        ))}
      </div>
      {/* Condition is a filter, not just a label — plenty of buyers are shopping
          for the cheapest playable copy rather than the best one. */}
      <div className="rail" style={{ marginTop: 8 }}>
        <button className={'chip' + (condFilter === 'all' ? ' on' : '')} onClick={() => setCondFilter('all')}>Any condition</button>
        {conds.map(c => (
          <button key={c.code} className={'chip' + (condFilter === c.code ? ' on' : '')} onClick={() => setCondFilter(c.code)}>{c.label}</button>
        ))}
      </div>

      <div className="singles-bar">
        <span className="kicker">
          {filtering ? rows.length + ' of ' + all.length + ' listings' : all.length + ' listings'}
          {sellable < rows.length ? ' · ' + (rows.length - sellable) + ' not yet priced' : ''}
        </span>
        <label className="inv-sort">
          <span className="kicker">Sort</span>
          <select value={sort} onChange={(e) => setSort(e.target.value)} aria-label="Sort listings">
            <option value="price-desc">Price, high to low</option>
            <option value="price-asc">Price, low to high</option>
            <option value="name">Card name</option>
            <option value="newest">Newest set</option>
          </select>
        </label>
      </div>

      {rows.length === 0 ? (
        <div style={{ padding: '30px 20px', textAlign: 'center' }}>
          <div style={{ fontFamily: 'var(--ff-display)', fontWeight: 800, fontSize: 19 }}>Nothing matches that</div>
          <p className="muted" style={{ fontSize: 13.5, marginTop: 6 }}>Try a different set, or clear the search.</p>
          <button className="btn btn-soft" style={{ marginTop: 14 }} onClick={clearFilters}>Clear filters</button>
        </div>
      ) : (
        <div className="singles-grid">
          {rows.map(x => (
            <button key={x.row.sku} className="card hairline single-cell" onClick={() => openSingle(x.row.sku)}>
              <SinglePlate card={x.card} set={x.set} w={104} />
              <div className="single-cell-body">
                <div style={{ display: 'flex', gap: 6, marginBottom: 7, flexWrap: 'wrap' }}>
                  <InvRarityTag rarity={x.card.rarity} />
                  <span className="badge" style={{ background: 'var(--paper-2)', color: 'var(--ink-2)' }}>{x.cond.code}</span>
                  {x.soldOut && <span className="badge" style={{ background: 'var(--ink)', color: 'var(--paper)' }}>Sold</span>}
                  {x.row.qty === 1 && x.sellable && <span className="badge badge-accent">Last one</span>}
                </div>
                <div style={{ fontFamily: 'var(--ff-display)', fontWeight: 800, fontSize: 17, letterSpacing: '-0.01em', lineHeight: 1.05 }}>
                  {x.card.name}
                </div>
                <div style={{ fontFamily: 'var(--ff-mono)', fontSize: 10.5, color: 'var(--ink-3)', marginTop: 3 }}>
                  {x.set.name} · {x.card.number}/{x.set.printed}
                </div>
                {/* price and condition stack rather than sit side by side —
                    in a narrow cell "Near Mint" wraps mid-phrase next to a price */}
                <div style={{ marginTop: 10 }}>
                  <SinglesPrice ask={x.ask} />
                  <div style={{ fontFamily: 'var(--ff-mono)', fontSize: 10.5, color: 'var(--ink-3)', marginTop: 2 }}>{x.cond.label}</div>
                </div>
              </div>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

/* ───────────── DETAIL (pushed) ───────────── */
function SingleDetail({ sku, onBack, openSingle }) {
  const row = INVENTORY.find(r => r.sku === sku);
  if (!row) return null;
  const { card, set, cond, ask, sellable, soldOut } = singleListing(row);
  const others = otherConditions(row);

  return (
    <div className="screen-push anim-in">
      <PushHeader title={card.name} onBack={onBack} />
      <div className="scroll">
        <div className="col">
          <div style={{ padding: '16px 20px 0', display: 'flex', justifyContent: 'center' }}>
            <SinglePlate card={card} set={set} w={186} />
          </div>

          <div style={{ padding: '20px 20px 0' }}>
            <div style={{ display: 'flex', gap: 6, marginBottom: 10, flexWrap: 'wrap' }}>
              <InvRarityTag rarity={card.rarity} />
              {card.variant && <span className="badge" style={{ background: 'var(--paper-2)', color: 'var(--ink-2)' }}>{card.variant}</span>}
              {card.nickname && <span className="badge badge-gold">"{card.nickname}"</span>}
            </div>
            <h1 className="t-display" style={{ fontSize: 27, margin: 0 }}>{card.name}</h1>
            <div style={{ fontFamily: 'var(--ff-mono)', fontSize: 12, color: 'var(--ink-3)', marginTop: 6 }}>
              {set.name} · {card.number}/{set.printed} · {set.series}
            </div>

            <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginTop: 18 }}>
              <SinglesPrice ask={ask} size="lg" />
              {sellable && <span style={{ fontFamily: 'var(--ff-mono)', fontSize: 11.5, color: 'var(--ink-3)' }}>
                {row.qty === 1 ? 'one available' : row.qty + ' available'}
              </span>}
              {soldOut && <span className="badge" style={{ background: 'var(--ink)', color: 'var(--paper)' }}>Sold</span>}
            </div>

            {/* Condition is the product, so it gets explained rather than badged. */}
            <div className="card hairline" style={{ padding: 15, marginTop: 18 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 9, marginBottom: 7 }}>
                <span className="badge" style={{ background: 'var(--ink)', color: 'var(--paper)' }}>{cond.code}</span>
                <span style={{ fontWeight: 700, fontSize: 15 }}>{cond.label}</span>
              </div>
              <p className="muted" style={{ fontSize: 13.5, lineHeight: 1.45, margin: 0 }}>{CONDITION_NOTES[cond.code]}</p>
            </div>

            {/* An unpriced row is a gap in our own data, and saying so is better
                than quoting a number nobody stands behind. */}
            {!sellable && (
              <div className="card hairline" style={{ padding: 15, marginTop: 12, background: 'color-mix(in oklab, var(--gold) 10%, var(--surface))' }}>
                <div style={{ fontWeight: 700, fontSize: 14.5, marginBottom: 5 }}>Not for sale yet</div>
                <p className="muted" style={{ fontSize: 13, lineHeight: 1.45, margin: 0 }}>
                  We have this card in hand but haven't set a price on it. It goes on
                  the shelf once it's valued.
                </p>
              </div>
            )}

            {/* The other copies we hold. Same card, different condition, different
                price — without this the buyer has to go back and hunt the grid. */}
            {others.length > 0 && (
              <div style={{ marginTop: 22 }}>
                <div className="kicker" style={{ marginBottom: 10 }}>
                  Same card, other condition{others.length > 1 ? 's' : ''}
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                  {others.map(o => (
                    <button key={o.row.sku} className="card hairline cond-swap" onClick={() => openSingle(o.row.sku)}>
                      <span className="badge" style={{ background: 'var(--paper-2)', color: 'var(--ink-2)', flexShrink: 0 }}>{o.cond.code}</span>
                      <span style={{ flex: 1, minWidth: 0, textAlign: 'left' }}>
                        <span style={{ display: 'block', fontWeight: 700, fontSize: 14 }}>{o.cond.label}</span>
                        <span style={{ display: 'block', fontFamily: 'var(--ff-mono)', fontSize: 10.5, color: 'var(--ink-3)', marginTop: 2 }}>
                          {o.soldOut ? 'sold' : o.row.qty === 1 ? 'one available' : o.row.qty + ' available'}
                          {o.ask != null && ask != null && o.ask < ask ? ' · ' + money(ask - o.ask) + ' less' : ''}
                        </span>
                      </span>
                      <SinglesPrice ask={o.ask} />
                      <Icon name="chevR" size={17} color="var(--ink-3)" />
                    </button>
                  ))}
                </div>
              </div>
            )}

            <div style={{ marginTop: 18, paddingBottom: 26 }}>
              <div className="kicker" style={{ marginBottom: 8 }}>How this is priced</div>
              <p className="muted" style={{ fontSize: 13, lineHeight: 1.5, margin: 0 }}>
                {ask == null
                  ? 'Market price for this print hasn\'t been recorded yet.'
                  : <>Near Mint market {money(card.market.rawNM)}{cond.mult < 1 ? <> × {Math.round(cond.mult * 100)}% for {cond.label.toLowerCase()}</> : ''}. Observed {card.market.asOf} — {card.market.basis}.</>}
              </p>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { SinglesScreen, SingleDetail, SinglePlate, CONDITION_NOTES, singleListing, otherConditions });
