import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { Navigate, useParams } from 'react-router-dom';
import { api, formatCredits, compareCents, toCentsBigInt } from '../api';
import { useAuth } from '../AuthContext';
import CaseReel from '../components/CaseReel';
import ItemTile, { itemValueCents } from '../components/ItemTile';
import { notifyInventoryChanged } from '../inventoryEvents';
import { rarityColor } from '../rarities';
import { loadCaseOpenQty, resolveOpenQty, saveCaseOpenQty } from '../userPrefs';

const DEFAULT_MAX_OPENS = 10;
/** Cap simultaneous reel animations to avoid lag on large multi-opens. */
const MAX_VISIBLE_REELS = 5;

/** 1–5, then steps of 5 (10, 15…), plus max if not already listed. */
function buildOpenQuantityOptions(maxOpens) {
  const cap = Math.max(1, maxOpens);
  const options = [];
  for (let n = 1; n <= Math.min(5, cap); n += 1) options.push(n);
  for (let n = 10; n <= cap; n += 5) options.push(n);
  if (cap > 5 && !options.includes(cap)) options.push(cap);
  return options;
}

function dropValue(item) {
  return itemValueCents(item);
}

/** Best drops first (by instance value), capped for reel display. */
function pickShowcaseDrops(items, limit = MAX_VISIBLE_REELS) {
  return [...items]
    .sort((a, b) => compareCents(dropValue(b), dropValue(a)))
    .slice(0, Math.max(1, limit));
}

/** Best drop by valueCents (BigInt-safe). */
function pickBestDrop(items) {
  if (!items?.length) return null;
  let best = items[0];
  let bestVal = dropValue(best);
  for (let i = 1; i < items.length; i += 1) {
    const it = items[i];
    const v = dropValue(it);
    if (v > bestVal) {
      best = it;
      bestVal = v;
    }
  }
  return best;
}

export default function CasePage() {
  const { id } = useParams();
  const { user, loading, updateBalance } = useAuth();
  const [crate, setCrate] = useState(null);
  const [error, setError] = useState('');
  const [keyMessage, setKeyMessage] = useState('');
  const [openBonusMessage, setOpenBonusMessage] = useState('');
  const [animReduction, setAnimReduction] = useState(0);
  const [quantity, setQuantity] = useState(1);
  const [opening, setOpening] = useState(false);
  const [spins, setSpins] = useState([]);
  const [allDrops, setAllDrops] = useState([]);
  const [caseName, setCaseName] = useState('');
  /** True while paused on discarded winners before the respin reels restart. */
  const [respinRestarting, setRespinRestarting] = useState(false);
  const announcedRef = useRef(false);
  const preAnnouncedRef = useRef(false);
  const finishingRef = useRef(false);
  const pendingAnnounceRef = useRef({ ids: [], caseName: '', respin: false });
  const pendingAutoSellRef = useRef([]);
  /** Auto-sell ids from the first roll — applied when that reel finishes. */
  const pendingPreRespinAutoSellRef = useRef([]);
  /** Keepers from first roll (auto-sell on, value >= threshold) — merged into final loot UI. */
  const pendingKeptPreRespinRef = useRef([]);
  /** When set, first reel pass is the pre-respin roll; then we restart with these final items. */
  const pendingRespinFinalRef = useRef(null);
  /** Snapshot of first-roll drops for feed (best item announced when first reels finish). */
  const pendingPreRespinAnnounceRef = useRef(null);
  /**
   * Respin animation phase (kept out of setState updaters — must be Strict-Mode safe):
   * null | 'pre' | 'restarting' | 'final'
   */
  const respinPhaseRef = useRef(null);
  const quantityRef = useRef(1);
  const caseIdRef = useRef(null);
  const prefReadyRef = useRef(false);
  /** Tracks case maxOpens so we can bump qty when a key unlocks. */
  const prevMaxOpensRef = useRef(null);

  const keyProgress = crate?.keyProgress ?? null;
  const maxOpens = keyProgress?.maxOpens ?? DEFAULT_MAX_OPENS;

  quantityRef.current = quantity;

  const persistQty = (caseId, qty) => {
    if (user?.id && caseId != null) saveCaseOpenQty(user.id, caseId, qty);
  };

  useEffect(() => {
    return () => {
      if (user?.id && caseIdRef.current != null) {
        saveCaseOpenQty(user.id, caseIdRef.current, quantityRef.current);
      }
    };
  }, [user?.id]);

  const announcePendingDrop = async (useKeepalive = false) => {
    const ids = pendingAnnounceRef.current.ids.filter(Boolean);
    const announceCaseName = pendingAnnounceRef.current.caseName || caseName || crate?.name || '';
    const respin = Boolean(pendingAnnounceRef.current.respin);
    if (!ids.length || announcedRef.current) return;

    announcedRef.current = true;
    const body = JSON.stringify({
      inventoryItemIds: ids,
      caseName: announceCaseName,
      respin,
    });

    if (useKeepalive) {
      fetch('/api/feed/announce', {
        method: 'POST',
        credentials: 'include',
        keepalive: true,
        headers: { 'Content-Type': 'application/json' },
        body,
      }).catch(() => {
        announcedRef.current = false;
      });
      return;
    }

    try {
      await api.feedAnnounce(ids, announceCaseName, { respin });
    } catch {
      announcedRef.current = false;
    }
  };

  const announcePreRespinDrop = async () => {
    const pending = pendingPreRespinAnnounceRef.current;
    if (!pending?.items?.length || preAnnouncedRef.current) return;
    preAnnouncedRef.current = true;
    pendingPreRespinAnnounceRef.current = null;
    const best = pickBestDrop(pending.items);
    if (!best) {
      preAnnouncedRef.current = false;
      return;
    }
    const count = pending.items.filter((it) => it.id === best.id).length;
    try {
      await api.feedAnnounce([], pending.caseName || caseName || crate?.name || '', {
        snapshotBest: {
          id: best.id,
          name: best.name,
          imageUrl: best.imageUrl,
          rarity: best.rarity,
          valueCents: best.valueCents ?? best.marketValue,
          wear: best.wear,
          floatValue: best.floatValue,
          paintSeed: best.paintSeed,
        },
        openCount: pending.items.length,
        count,
        respin: false,
      });
    } catch {
      preAnnouncedRef.current = false;
    }
  };

  const applyPendingAutoSellKeepalive = () => {
    const ids = [
      ...pendingPreRespinAutoSellRef.current,
      ...pendingAutoSellRef.current,
    ].filter(Boolean);
    if (!ids.length) return;
    pendingPreRespinAutoSellRef.current = [];
    pendingAutoSellRef.current = [];
    fetch('/api/inventory/auto-sell/apply', {
      method: 'POST',
      credentials: 'include',
      keepalive: true,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ inventoryItemIds: ids }),
    }).catch(() => {});
  };

  const markSpinsAutoSold = (soldIds, soldDetails = []) => {
    const soldSet = new Set(soldIds);
    const payoutById = new Map(
      (soldDetails || []).map((s) => [s.id, s.payoutCents ?? s.valueCents])
    );
    setSpins((curr) =>
      curr.map((s) =>
        soldSet.has(s.item.inventoryId)
          ? {
              ...s,
              item: {
                ...s.item,
                autoSold: true,
                soldForCents:
                  payoutById.get(s.item.inventoryId) ?? s.item.valueCents,
              },
            }
          : s
      )
    );
    setAllDrops((curr) =>
      curr.map((item) =>
        soldSet.has(item.inventoryId)
          ? {
              ...item,
              autoSold: true,
              soldForCents: payoutById.get(item.inventoryId) ?? item.valueCents,
            }
          : item
      )
    );
  };

  /** Credit auto-sell for the discarded first roll once its reels finish. */
  const applyPreRespinAutoSell = async () => {
    const toSell = [...pendingPreRespinAutoSellRef.current];
    pendingPreRespinAutoSellRef.current = [];
    if (!toSell.length) return;
    try {
      const data = await api.applyPendingAutoSell(toSell);
      updateBalance(data.user.balance);
      notifyInventoryChanged();
      if (data.soldIds?.length) markSpinsAutoSold(data.soldIds, data.sold);
    } catch (err) {
      // Keep ids so unload keepalive can still flush if the request failed mid-respin.
      pendingPreRespinAutoSellRef.current = toSell;
      setError(err.message);
    }
  };

  const finalizeOpens = async (announceCaseName) => {
    if (finishingRef.current) return;
    finishingRef.current = true;

    // Keep full-batch ids already stored in pending refs (not only showcase reels)
    if (announceCaseName) {
      pendingAnnounceRef.current = {
        ...pendingAnnounceRef.current,
        caseName: announceCaseName,
      };
    }

    try {
      await announcePendingDrop(false);

      const toSell = [...pendingAutoSellRef.current];
      pendingAutoSellRef.current = [];

      if (toSell.length) {
        const data = await api.applyPendingAutoSell(toSell);
        updateBalance(data.user.balance);
        notifyInventoryChanged();
        if (data.soldIds?.length) markSpinsAutoSold(data.soldIds, data.sold);
      }
    } catch (err) {
      setError(err.message);
      announcedRef.current = false;
    } finally {
      finishingRef.current = false;
    }
  };

  useEffect(() => {
    let cancelled = false;
    prefReadyRef.current = false;
    prevMaxOpensRef.current = null;
    caseIdRef.current = null;
    setCrate(null);
    (async () => {
      try {
        const data = await api.case(id);
        if (!cancelled) {
          setCrate(data.case);
          caseIdRef.current = data.case.id;
        }
      } catch (err) {
        if (!cancelled) setError(err.message);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [id]);

  const maxAffordable = useMemo(() => {
    if (!crate || !user) return 1;
    try {
      const price = BigInt(String(crate.price ?? 0).split('.')[0] || '0');
      if (price <= 0n) return maxOpens;
      const bal = BigInt(String(user.balance ?? 0).split('.')[0] || '0');
      const affordable = bal / price;
      const n = affordable > BigInt(Number.MAX_SAFE_INTEGER) ? Number.MAX_SAFE_INTEGER : Number(affordable);
      return Math.min(maxOpens, Math.max(n, 0));
    } catch {
      return 1;
    }
  }, [crate, user, maxOpens]);

  const openOptions = useMemo(() => buildOpenQuantityOptions(maxOpens), [maxOpens]);

  useLayoutEffect(() => {
    if (!crate || !user?.id) return;

    const saved = loadCaseOpenQty(user.id, crate.id);
    const resolved = resolveOpenQty(saved, openOptions, maxAffordable);

    if (!prefReadyRef.current) {
      prefReadyRef.current = true;
      prevMaxOpensRef.current = maxOpens;
      setQuantity(resolved);
      return;
    }

    const prevMax = prevMaxOpensRef.current;
    prevMaxOpensRef.current = maxOpens;
    // New key unlocked → jump select to highest affordable tier (new max if credits allow).
    const unlockedHigher = prevMax != null && maxOpens > prevMax;

    setQuantity((q) => {
      const allowed = openOptions.filter((n) => n <= Math.max(1, maxAffordable));
      if (!allowed.length) return 1;
      // Jump to newly unlocked max only when the balance can cover it.
      if (unlockedHigher && maxOpens <= Math.max(1, maxAffordable)) {
        return maxOpens;
      }
      if (allowed.includes(q)) return q;
      const below = allowed.filter((n) => n <= q);
      return below.length ? below[below.length - 1] : allowed[0];
    });
  }, [crate, maxAffordable, openOptions, user?.id, maxOpens]);

  useEffect(() => {
    if (!prefReadyRef.current || !user?.id || crate?.id == null) return;
    persistQty(crate.id, quantity);
  }, [quantity, user?.id, crate?.id]);

  useEffect(() => {
    return () => {
      // Flush first-spin feed if page closes mid-respin
      const pre = pendingPreRespinAnnounceRef.current;
      if (pre?.items?.length && !preAnnouncedRef.current) {
        preAnnouncedRef.current = true;
        const best = pickBestDrop(pre.items);
        if (best) {
          const count = pre.items.filter((it) => it.id === best.id).length;
          fetch('/api/feed/announce', {
            method: 'POST',
            credentials: 'include',
            keepalive: true,
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              snapshotBest: {
                id: best.id,
                name: best.name,
                imageUrl: best.imageUrl,
                rarity: best.rarity,
                valueCents: best.valueCents ?? best.marketValue,
                wear: best.wear,
                floatValue: best.floatValue,
                paintSeed: best.paintSeed,
              },
              openCount: pre.items.length,
              count,
              caseName: pre.caseName || '',
              respin: false,
            }),
          }).catch(() => {});
        }
      }
      announcePendingDrop(true);
      applyPendingAutoSellKeepalive();
    };
  }, []);

  const possibleDrops = useMemo(() => {
    if (!crate?.items?.length) return [];
    return [...crate.items].sort((a, b) => {
      const ca = Number(a.chance ?? a.dropRate ?? 0);
      const cb = Number(b.chance ?? b.dropRate ?? 0);
      if (ca !== cb) return ca - cb;
      return compareCents(b.marketValue, a.marketValue);
    });
  }, [crate]);

  const progressPct = useMemo(() => {
    if (!keyProgress?.opensRequired) return 0;
    return Math.min(
      100,
      Math.round((keyProgress.opensSinceLastKey / keyProgress.opensRequired) * 100)
    );
  }, [keyProgress]);

  const totalCost = useMemo(() => {
    if (!crate) return '0';
    return (toCentsBigInt(crate.price) * BigInt(quantity)).toString();
  }, [crate, quantity]);

  const canOpen = useMemo(() => {
    if (!user || !crate || opening || !crate.items?.length) return false;
    if (quantity < 1 || quantity > maxOpens) return false;
    return toCentsBigInt(user.balance) >= toCentsBigInt(totalCost);
  }, [user, crate, opening, quantity, maxOpens, totalCost]);

  const allDone =
    spins.length > 0 &&
    spins.every((s) => s.done) &&
    !respinRestarting &&
    respinPhaseRef.current !== 'pre' &&
    respinPhaseRef.current !== 'restarting';

  const heroHidden = opening || (spins.length > 0 && !allDone);

  const beginReelSpins = (items, extraAllDrops = []) => {
    setAllDrops(extraAllDrops.length ? [...extraAllDrops, ...items] : items);
    const showcase = pickShowcaseDrops(items, MAX_VISIBLE_REELS);
    const next = showcase.map((item, idx) => ({
      key: `${item.inventoryId || item.id || 'x'}-${idx}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
      item,
      spinning: false,
      done: false,
    }));
    setSpins(next);
    requestAnimationFrame(() => {
      setSpins((prev) => prev.map((s) => ({ ...s, spinning: true })));
    });
  };

  const openCase = async () => {
    if (!canOpen || !crate) return;
    setError('');
    setKeyMessage('');
    setOpenBonusMessage('');
    setSpins([]);
    setAllDrops([]);
    setOpening(true);
    setRespinRestarting(false);
    announcedRef.current = false;
    preAnnouncedRef.current = false;
    finishingRef.current = false;
    pendingRespinFinalRef.current = null;
    pendingPreRespinAnnounceRef.current = null;
    pendingPreRespinAutoSellRef.current = [];
    pendingKeptPreRespinRef.current = [];
    respinPhaseRef.current = null;

    try {
      const result = await api.openCase(crate.id, quantity);
      updateBalance(result.user.balance);
      notifyInventoryChanged();
      persistQty(crate.id, quantity);
      if (result.animReduction != null) {
        setAnimReduction(result.animReduction);
      }
      if (result.refunded) {
        setOpenBonusMessage('Refund! Case cost returned');
      }
      if (result.keyProgress) {
        setCrate((prev) => (prev ? { ...prev, keyProgress: result.keyProgress } : prev));
      }
      if (result.keysGained > 0) {
        setKeyMessage(
          `+${result.keysGained} key${result.keysGained === 1 ? '' : 's'} unlocked · now x${result.keyProgress.maxOpens} max`
        );
      }
      if (result.catalog) {
        if (result.catalog.luckPercent != null) {
          setCrate((prev) =>
            prev ? { ...prev, catalogLuckPercent: result.catalog.luckPercent } : prev
          );
          if (result.catalog.chapterJustCompleted) {
            api.case(crate.id).then((data) => {
              setCrate((prev) =>
                prev
                  ? {
                      ...prev,
                      ...data.case,
                      keyProgress: result.keyProgress || data.case.keyProgress,
                    }
                  : data.case
              );
            }).catch(() => {});
          }
        }
      }
      const resolvedCaseName = result.caseName || crate.name;
      setCaseName(resolvedCaseName);
      const items = result.items?.length ? result.items : result.item ? [result.item] : [];
      const inventoryIds = items.map((item) => item?.inventoryId).filter(Boolean);
      const pre = result.respin && result.preRespinItems?.length ? result.preRespinItems : null;

      pendingAnnounceRef.current = {
        ids: inventoryIds,
        caseName: resolvedCaseName || '',
        respin: Boolean(pre),
      };
      pendingAutoSellRef.current = items
        .filter((item) => item?.willAutoSell)
        .map((item) => item.inventoryId)
        .filter(Boolean);

      if (pre) {
        pendingPreRespinAutoSellRef.current = pre
          .filter((item) => item?.willAutoSell && item?.inventoryId)
          .map((item) => item.inventoryId);
        pendingKeptPreRespinRef.current =
          result.keptPreRespinItems?.length
            ? result.keptPreRespinItems
            : pre.filter((item) => item?.kept || (!item?.willAutoSell && item?.inventoryId));
        pendingPreRespinAnnounceRef.current = {
          items: pre,
          caseName: resolvedCaseName || '',
        };
        pendingRespinFinalRef.current = items;
        respinPhaseRef.current = 'pre';
        beginReelSpins(pre);
      } else {
        pendingPreRespinAutoSellRef.current = [];
        pendingKeptPreRespinRef.current = [];
        respinPhaseRef.current = null;
        beginReelSpins(items);
      }
    } catch (err) {
      setError(err.message);
      setOpening(false);
    }
  };

  const onReelDone = (key) => {
    setSpins((prev) => {
      const alreadyDone = prev.find((s) => s.key === key)?.done;
      if (alreadyDone) return prev;
      return prev.map((s) =>
        s.key === key ? { ...s, spinning: false, done: true } : s
      );
    });
  };

  // 1) When first spin reels finish → feed line #1, start respin pause
  // 2) When final reels finish → feed line #2 (respin) via finalizeOpens
  useEffect(() => {
    if (!spins.length || spins.some((s) => !s.done)) return;

    const phase = respinPhaseRef.current;

    if (phase === 'pre') {
      if (!pendingRespinFinalRef.current) return;
      // Block re-entry while we credit first-roll auto-sell, then pause before restart.
      respinPhaseRef.current = 'restarting';
      void (async () => {
        await announcePreRespinDrop();
        await applyPreRespinAutoSell();
        setRespinRestarting(true);
      })();
      return;
    }

    if (phase === 'restarting') return;

    if ((phase === 'final' || phase === null) && opening && !finishingRef.current) {
      respinPhaseRef.current = null;
      setOpening(false);
      void finalizeOpens(caseName || crate?.name || '');
    }
  }, [spins, opening, caseName, crate?.name]);

  // After pause, restart reels with real loot (respin feed waits until those finish)
  useEffect(() => {
    if (!respinRestarting) return;
    const finalItems = pendingRespinFinalRef.current;
    if (!finalItems) return;

    const t = setTimeout(() => {
      pendingRespinFinalRef.current = null;
      const kept = pendingKeptPreRespinRef.current || [];
      pendingKeptPreRespinRef.current = [];
      respinPhaseRef.current = 'final';
      const keptNote =
        kept.length > 0
          ? ` · Kept ${kept.length} item${kept.length === 1 ? '' : 's'} from first spin`
          : '';
      setOpenBonusMessage((msg) =>
        msg?.includes('Respin')
          ? msg
          : msg
            ? `${msg} · Respin! Re-rolling…${keptNote}`
            : `Respin! Re-rolling all drops…${keptNote}`
      );
      beginReelSpins(finalItems, kept);
      setRespinRestarting(false);
    }, 550);

    return () => clearTimeout(t);
  }, [respinRestarting]);

  if (loading) return <p className="muted">Loading…</p>;
  if (!user) return <Navigate to="/login" replace />;
  if (user.role === 'admin') return <Navigate to="/admin" replace />;

  return (
    <div className="case-page">
      {!crate && !error && <p className="muted">Loading case…</p>}
      {error && <div className="error">{error}</div>}
      {keyMessage && <div className="ok-msg">{keyMessage}</div>}
      {openBonusMessage && <div className="ok-msg">{openBonusMessage}</div>}

      {crate && (
        <>
          <div
            className={`case-hero-wrap${heroHidden ? ' case-hero-wrap--hidden' : ''}`}
            aria-hidden={heroHidden}
          >
            <div className="case-hero">
              <h1>{crate.name}</h1>

              {keyProgress && (
                <div className="case-key-progress">
                  <div className="case-key-head">
                    <span>
                      Keys <strong>{keyProgress.keys}</strong> · Max{' '}
                      <strong>x{keyProgress.maxOpens}</strong>
                    </span>
                    <span className="muted">
                      Next key: {keyProgress.opensSinceLastKey} / {keyProgress.opensRequired} opens
                    </span>
                  </div>
                  <div className="case-key-bar" role="progressbar" aria-valuenow={progressPct}>
                    <div className="case-key-bar-fill" style={{ width: `${progressPct}%` }} />
                  </div>
                </div>
              )}

              <div className="open-qty">
                <label className="muted">
                  Open
                  <select
                    value={quantity}
                    disabled={opening}
                    onChange={(e) => {
                      const next = Number(e.target.value);
                      setQuantity(next);
                      persistQty(crate.id, next);
                    }}
                  >
                    {openOptions.map((n) => (
                      <option key={n} value={n} disabled={n > maxAffordable && maxAffordable > 0}>
                        {n}×
                      </option>
                    ))}
                  </select>
                </label>
                <span className="muted">
                  Total {formatCredits(totalCost)} cr
                  {maxAffordable < quantity ? ' · insufficient balance' : ''}
                </span>
              </div>

              <button
                type="button"
                className={`case-icon ${canOpen ? '' : 'disabled'}${crate.imageUrl ? ' has-img' : ''}`}
                onClick={openCase}
                disabled={!canOpen}
                title="Open case"
              >
                {crate.imageUrl ? <img src={crate.imageUrl} alt="" /> : '▣'}
              </button>
              <button className="btn" type="button" onClick={openCase} disabled={!canOpen}>
                {opening
                  ? `Opening ${quantity}×…`
                  : `Open ${quantity}× for ${formatCredits(totalCost)} cr`}
              </button>
            </div>
          </div>

          {spins.length > 0 && (
            <div className={`multi-reels${spins.length > 1 ? ' many' : ''}`}>
              {allDrops.length > spins.length && (
                <p className="muted multi-reel-hint">
                  Showing top {spins.length} of {allDrops.length} drops
                </p>
              )}
              {spins.map((spin, idx) => (
                <div key={spin.key} className="multi-reel-slot">
                  {spins.length > 1 && (
                    <div className="muted multi-reel-label">#{idx + 1}</div>
                  )}
                  <CaseReel
                    items={crate.items}
                    winnerId={spin.item.id}
                    spinning={spin.spinning}
                    onDone={() => onReelDone(spin.key)}
                    animReduction={animReduction}
                  />
                  {spin.done && (
                    <div
                      className="won-banner compact"
                      style={{ '--rarity': rarityColor(spin.item.rarity) }}
                    >
                      {spin.item.imageUrl ? (
                        <img src={spin.item.imageUrl} alt="" className="won-img" />
                      ) : null}
                      <strong>{spin.item.name}</strong>
                      <div style={{ color: rarityColor(spin.item.rarity) }}>
                        {spin.item.rarity}
                      </div>
                      <div className="muted">
                        {[
                          spin.item.wear || null,
                          `${formatCredits(spin.item.valueCents ?? spin.item.marketValue)} cr`,
                        ]
                          .filter(Boolean)
                          .join(' · ')}
                        {spin.item.autoSold ? (
                          <>
                            {' '}
                            · Auto-sold for {formatCredits(spin.item.soldForCents)} cr
                          </>
                        ) : null}
                      </div>
                    </div>
                  )}
                </div>
              ))}
            </div>
          )}

          {allDone && allDrops.length > 1 && (
            <div className="won-banner section">
              <div className="muted">
                You unboxed {allDrops.length} items
                {allDrops.length > spins.length
                  ? ` · reels showed the top ${spins.length} by value`
                  : ''}
              </div>
              <div className="grid" style={{ marginTop: '0.75rem' }}>
                {[...allDrops]
                  .sort((a, b) => compareCents(dropValue(b), dropValue(a)))
                  .map((item) => (
                    <ItemTile
                      key={item.inventoryId || `${item.id}-${item.floatValue}`}
                      item={item}
                    />
                  ))}
              </div>
            </div>
          )}

          <section className="section">
            <div className="section-head">
              <div>
                <h2>Possible drops</h2>
                <p className="muted">
                  Relative drop odds
                  {crate.catalogLuckPercent > 0
                    ? ` (with +${crate.catalogLuckPercent}% catalog luck)`
                    : ''}{' '}
                  · prices shown at Field-Tested reference.
                </p>
              </div>
            </div>
            <div className="grid">
              {possibleDrops.map((item) => (
                <div key={item.id}>
                  <ItemTile item={item} />
                  <div className="item-meta" style={{ marginTop: '0.35rem' }}>
                    {item.chance}%
                  </div>
                </div>
              ))}
            </div>
          </section>
        </>
      )}
    </div>
  );
}
