// pedby.com — app shell: routing, wallet state, trade state.
// Runs in LIVE mode against PedbyLaunchpad on Base when PEDBY_CONFIG.LAUNCHPAD is set,
// otherwise in DEMO mode with simulated tokens (no real funds).

const PEDBY_LIVE = window.PEDBY_CHAIN && PEDBY_CHAIN.isLive();
window.PEDBY_LIVE = PEDBY_LIVE;

// ── Toasts ─────────────────────────────────────────────────────────────────
function PedbyToasts({ toasts }) {
  if (!toasts.length) return null;
  const tone = { pending: ['#E1EEF8', '#0F3F66'], ok: ['#E7F4EC', '#0F4E27'], error: ['#FBE3E0', '#7A1E15'] };
  return (
    <div style={{ position: 'fixed', bottom: 20, right: 20, zIndex: 100, display: 'flex', flexDirection: 'column', gap: 8, maxWidth: 340 }}>
      {toasts.map(t => (
        <div key={t.id} style={{
          background: tone[t.kind][0], color: tone[t.kind][1], border: '1px solid rgba(0,0,0,.06)',
          borderRadius: 12, padding: '11px 14px', fontSize: 12.5, fontWeight: 600, lineHeight: 1.45,
          boxShadow: '0 10px 30px -12px rgba(22,32,26,.3)', display: 'flex', gap: 9, alignItems: 'flex-start',
        }}>
          <span style={{ marginTop: 1 }}>{t.kind === 'pending' ? '⏳' : t.kind === 'ok' ? '✓' : '✕'}</span>
          <span style={{ wordBreak: 'break-word' }}>
            {t.msg}
            {t.tx && <span> · <a href={PEDBY_CHAIN.explorerTx(t.tx)} target="_blank" rel="noreferrer" style={{ color: 'inherit' }}>view tx ↗</a></span>}
          </span>
        </div>
      ))}
    </div>
  );
}

// Shown when "Connect wallet" is tapped and no provider is injected —
// the normal case in a mobile browser outside a wallet app.
function PedbyWalletModal({ onClose, onWalletConnect }) {
  const mobile = PEDBY_CHAIN.isMobile();
  const hasWC = PEDBY_CHAIN.hasWalletConnect();
  const rowStyle = {
    display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%',
    padding: '14px 16px', borderRadius: 14, border: '1px solid #E6EAE6', background: '#fff',
    fontFamily: 'inherit', fontSize: 14.5, fontWeight: 700, color: '#16201A', cursor: 'pointer',
    textDecoration: 'none',
  };
  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, zIndex: 200, background: 'rgba(22,32,26,.55)', backdropFilter: 'blur(4px)',
      display: 'flex', alignItems: 'flex-end', justifyContent: 'center', padding: 16,
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        background: '#F6F8F4', borderRadius: 22, padding: 22, width: '100%', maxWidth: 420,
        marginBottom: 'env(safe-area-inset-bottom, 0px)', boxShadow: '0 -20px 60px rgba(22,32,26,.35)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}>
          <div style={{ fontSize: 17, fontWeight: 800, letterSpacing: -0.3 }}>Connect a wallet</div>
          <button onClick={onClose} style={{ background: 'none', border: 'none', fontSize: 18, cursor: 'pointer', color: '#6B7570', padding: 4 }}>✕</button>
        </div>
        <div style={{ fontSize: 12.5, color: '#6B7570', lineHeight: 1.5, marginBottom: 16 }}>
          {mobile ? 'Open pedby inside your wallet’s browser — you’ll land right back here, connected.' : 'No wallet extension detected in this browser.'}
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {hasWC && (
            <button onClick={onWalletConnect} style={{ ...rowStyle, background: '#16201A', color: '#F6F8F4', border: 'none' }}>
              WalletConnect <span style={{ fontFamily: 'JetBrains Mono', fontSize: 10.5, fontWeight: 500, color: 'rgba(246,248,244,.6)' }}>ZERION · RAINBOW · 400+</span>
            </button>
          )}
          {mobile ? (
            PEDBY_CHAIN.walletDeepLinks().map(w => (
              <a key={w.name} href={w.href} style={rowStyle}>
                {w.name} <span style={{ color: '#9AA39E' }}>↗</span>
              </a>
            ))
          ) : (
            <React.Fragment>
              <a href="https://metamask.io/download/" target="_blank" rel="noreferrer" style={rowStyle}>Install MetaMask <span style={{ color: '#9AA39E' }}>↗</span></a>
              <a href="https://www.coinbase.com/wallet/downloads" target="_blank" rel="noreferrer" style={rowStyle}>Install Coinbase Wallet <span style={{ color: '#9AA39E' }}>↗</span></a>
            </React.Fragment>
          )}
        </div>
        {!hasWC && mobile && (
          <div style={{ fontSize: 11, color: '#9AA39E', lineHeight: 1.5, marginTop: 14, fontFamily: 'JetBrains Mono' }}>
            Using Zerion or another wallet? Open its in-app browser and visit {window.location.host}.
          </div>
        )}
      </div>
    </div>
  );
}

// Hash routing (#/marketplace, #/token/0x…, #/create, #/profile) — pages survive
// reloads and are linkable, no server-side routes needed on a static host.
const PEDBY_PAGES = ['landing', 'marketplace', 'create', 'profile', 'token'];
function pedbyParseHash() {
  const parts = window.location.hash.replace(/^#\/?/, '').split('/');
  const page = PEDBY_PAGES.includes(parts[0]) ? parts[0] : 'landing';
  return { page, id: page === 'token' && parts[1] ? decodeURIComponent(parts[1]).toLowerCase() : null };
}

function PedbyApp() {
  const initialRoute = React.useMemo(pedbyParseHash, []);
  const [page, setPage] = React.useState(initialRoute.page);
  const [selectedId, setSelectedId] = React.useState(initialRoute.id);
  const [tokens, setTokens] = React.useState(PEDBY_LIVE ? [] : PEDBY_TOKENS);
  const [wallet, setWallet] = React.useState({ connected: false, address: '', addressFull: '', eth: 0, holdings: {} });
  const [trades, setTrades] = React.useState([]);
  const [loading, setLoading] = React.useState(PEDBY_LIVE);
  const [toasts, setToasts] = React.useState([]);
  const [creationFee, setCreationFee] = React.useState(null); // ETH, from the contract in live mode
  const [showWallets, setShowWallets] = React.useState(false);
  const [activityReady, setActivityReady] = React.useState(!PEDBY_LIVE); // trade history synced?
  const inFlightRef = React.useRef(false);
  const tradeBusyRef = React.useRef(false);
  const walletRef = React.useRef(wallet);
  walletRef.current = wallet;
  const tokensRef = React.useRef(tokens);
  tokensRef.current = tokens;

  const toast = (kind, msg, tx) => {
    const id = Date.now() + Math.random();
    setToasts(ts => [...ts, { id, kind, msg, tx }]);
    if (kind !== 'pending') setTimeout(() => setToasts(ts => ts.filter(t => t.id !== id)), 6000);
    return () => setToasts(ts => ts.filter(t => t.id !== id));
  };

  const onNav = (p, id) => {
    if (id) setSelectedId(id);
    setPage(p);
    const hash = p === 'landing' ? '#/' : '#/' + p + (p === 'token' && id ? '/' + id : '');
    if (window.location.hash !== hash) window.history.pushState(null, '', hash);
    window.scrollTo(0, 0);
  };

  // back/forward buttons + hand-typed hashes
  React.useEffect(() => {
    const onHash = () => {
      const r = pedbyParseHash();
      setPage(r.page);
      if (r.id) setSelectedId(r.id);
    };
    window.addEventListener('hashchange', onHash);
    window.addEventListener('popstate', onHash);
    return () => { window.removeEventListener('hashchange', onHash); window.removeEventListener('popstate', onHash); };
  }, []);

  // ── Live-mode data loading ───────────────────────────────────────────────
  // Two-phase: tokens paint immediately (fast — 2 RPC calls), then the trade
  // history streams in and upgrades charts/feeds. Overlapping polls are skipped.
  const refresh = React.useCallback(async () => {
    if (!PEDBY_LIVE || inFlightRef.current) return;
    inFlightRef.current = true;
    try {
      const list = await PEDBY_CHAIN.loadTokens();
      setTokens(list.map(t => ({ ...t })));
      setLoading(false); // page is usable now — history keeps loading behind shimmers

      const feed = await PEDBY_CHAIN.loadActivity(list);
      setTokens(list.map(t => ({ ...t })));
      setTrades(feed);
      setActivityReady(true);

      const w = walletRef.current;
      if (w.connected && w.addressFull) {
        const [eth, holdings] = await Promise.all([
          PEDBY_CHAIN.ethBalance(w.addressFull),
          PEDBY_CHAIN.loadHoldings(w.addressFull, list),
        ]);
        setWallet(prev => ({ ...prev, eth, holdings }));
      }
    } catch (e) {
      console.warn('pedby: refresh failed', e);
      setLoading(false);
    } finally {
      inFlightRef.current = false;
    }
  }, []);

  React.useEffect(() => {
    if (!PEDBY_LIVE) return;
    const syncUsd = () => PEDBY_CHAIN.fetchEthUsd().then(p => { PEDBY_CURVE.ETH_USD = p; });
    syncUsd();
    const usdId = setInterval(syncUsd, 300000); // keep USD figures honest
    PEDBY_CHAIN.creationFeeEth().then(setCreationFee).catch(() => {});
    refresh();
    // don't burn RPC quota while the tab is in the background
    const id = setInterval(() => { if (!document.hidden) refresh(); }, PEDBY_CONFIG.POLL_MS);
    const onVisible = () => { if (!document.hidden) refresh(); };
    document.addEventListener('visibilitychange', onVisible);
    // react to wallet account/network switches WITHOUT reloading (a reload would
    // dump the user back on the landing page mid-flow)
    if (window.ethereum && window.ethereum.on) {
      window.ethereum.on('accountsChanged', async (accs) => {
        if (!accs || !accs.length) {
          setWallet({ connected: false, address: '', addressFull: '', eth: 0, holdings: {} });
          return;
        }
        try {
          const address = ethers.getAddress(accs[0]);
          const [eth, holdings] = await Promise.all([
            PEDBY_CHAIN.ethBalance(address),
            PEDBY_CHAIN.loadHoldings(address, tokensRef.current),
          ]);
          setWallet({ connected: true, address: address.slice(0, 5) + '…' + address.slice(-3), addressFull: address, eth, holdings });
        } catch (e) { console.warn('pedby: account switch', e); }
      });
      window.ethereum.on('chainChanged', (cid) => {
        if (parseInt(cid, 16) !== PEDBY_CONFIG.CHAIN.id) {
          toast('error', 'Your wallet left ' + PEDBY_CONFIG.CHAIN.name + ' — switch back to keep trading.');
        } else {
          refresh();
        }
      });
    }
    return () => { clearInterval(id); clearInterval(usdId); document.removeEventListener('visibilitychange', onVisible); };
  }, [refresh]);

  // ── Demo-mode simulated market activity ─────────────────────────────────
  React.useEffect(() => {
    if (PEDBY_LIVE) return;
    const id = setInterval(() => {
      setTokens(ts => {
        const live = ts.filter(t => !t.graduated);
        if (!live.length) return ts;
        const pick = live[Math.floor(Math.random() * live.length)];
        const isBuy = Math.random() < 0.75;
        const amt = +(0.01 + Math.random() * 0.12).toFixed(3);
        let updated;
        const bump = (spark, isB) => [...spark.slice(1), Math.max(0.5, spark[spark.length - 1] * (isB ? 1.045 : 0.955))];
        if (isBuy) {
          const { curve } = PEDBY_CURVE.buy(pick.curve, amt);
          updated = { ...pick, curve, graduated: PEDBY_CURVE.graduated(curve), spark: bump(pick.spark, true) };
        } else {
          const sellAmt = Math.min(pick.curve.virtualTokens * 0.002, 400000);
          const { curve } = PEDBY_CURVE.sell(pick.curve, sellAmt);
          updated = { ...pick, curve, graduated: PEDBY_CURVE.graduated(curve), spark: bump(pick.spark, false) };
        }
        setTrades(tr => [{ key: Date.now() + Math.random(), tokenId: pick.id, side: isBuy ? 'buy' : 'sell', ethAmt: amt }, ...tr].slice(0, 10));
        return ts.map(t => t.id === pick.id ? updated : t);
      });
    }, 3200);
    return () => clearInterval(id);
  }, []);

  // ── Wallet ───────────────────────────────────────────────────────────────
  const finishConnect = async ({ address, eth }) => {
    const holdings = await PEDBY_CHAIN.loadHoldings(address, tokensRef.current);
    setWallet({ connected: true, address: address.slice(0, 5) + '…' + address.slice(-3), addressFull: address, eth, holdings });
    setShowWallets(false);
    toast('ok', 'Wallet connected on ' + PEDBY_CONFIG.CHAIN.name + '.');
  };

  const connect = async () => {
    if (!PEDBY_LIVE) {
      setWallet({ connected: true, address: '0x7a3…f21', addressFull: '', eth: 2.4180, holdings: { goblin: 184200, meow: 92000 } });
      return;
    }
    if (!PEDBY_CHAIN.hasWallet()) {
      // no injected provider (typical on mobile browsers) — offer wallet options
      setShowWallets(true);
      throw Object.assign(new Error('Pick a wallet to continue.'), { quiet: true });
    }
    setWallet(w => ({ ...w, connecting: true }));
    try {
      await finishConnect(await PEDBY_CHAIN.connect());
    } catch (e) {
      toast('error', pedbyErrMsg(e, 'Wallet connection failed.'));
      throw Object.assign(e || new Error('connect failed'), { quiet: true });
    } finally {
      setWallet(w => ({ ...w, connecting: false }));
    }
  };

  const connectViaWalletConnect = async () => {
    setWallet(w => ({ ...w, connecting: true }));
    try {
      await finishConnect(await PEDBY_CHAIN.connectWalletConnect());
    } catch (e) {
      if (!/reset|Modal closed|Proposal expired/i.test((e && e.message) || '')) {
        toast('error', pedbyErrMsg(e, 'WalletConnect failed.'));
      }
    } finally {
      setWallet(w => ({ ...w, connecting: false }));
    }
  };

  // ── Launch ───────────────────────────────────────────────────────────────
  // In live mode this sends the createToken transaction and resolves with the
  // freshly indexed token; Create.jsx awaits it and shows the success screen.
  const onLaunch = async (formOrToken, devBuyEth = 0) => {
    if (!PEDBY_LIVE) {
      setTokens(ts => [formOrToken, ...ts]);
      return formOrToken;
    }
    if (!walletRef.current.connected) await connect();
    // pre-flight: can this wallet actually afford the launch?
    const needed = (creationFee || 0) + (devBuyEth || 0) + 0.0003;
    if (walletRef.current.eth < needed) {
      throw new Error(
        `Not enough ETH to launch: this needs ~${needed.toFixed(4)} ETH` +
        ` (${creationFee || 0} launch fee${devBuyEth ? ` + ${devBuyEth} first buy` : ''} + gas)` +
        ` but your wallet holds ${walletRef.current.eth.toFixed(4)} ETH.`
      );
    }
    const dismiss = toast('pending', 'Deploying $' + formOrToken.ticker.toUpperCase() + ' as a B20 token on ' + PEDBY_CONFIG.CHAIN.name + '…');
    try {
      const { tokenAddress, txHash } = await PEDBY_CHAIN.createToken(formOrToken, devBuyEth);
      dismiss();
      toast('ok', '$' + formOrToken.ticker.toUpperCase() + ' is live.', txHash);
      await refresh();
      const list = await PEDBY_CHAIN.loadTokens();
      setTokens(list);
      const minted = list.find(t => tokenAddress && t.id === tokenAddress.toLowerCase()) || list[0];
      return minted;
    } catch (e) {
      dismiss();
      toast('error', pedbyErrMsg(e, 'Deploy failed.'));
      throw e;
    }
  };

  // ── Trade ────────────────────────────────────────────────────────────────
  const onTrade = async (id, side, amount) => {
    if (amount <= 0) return;
    if (!PEDBY_LIVE) {
      if (!walletRef.current.connected) connect();
      setTokens(ts => ts.map(t => {
        if (t.id !== id) return t;
        if (side === 'buy') {
          const { curve, tokensOut } = PEDBY_CURVE.buy(t.curve, amount);
          setWallet(w => ({ ...w, eth: Math.max(0, w.eth - amount), holdings: { ...w.holdings, [id]: (w.holdings[id] || 0) + tokensOut } }));
          return { ...t, curve, graduated: PEDBY_CURVE.graduated(curve) };
        }
        const { curve, ethOut } = PEDBY_CURVE.sell(t.curve, amount);
        setWallet(w => ({ ...w, eth: w.eth + ethOut, holdings: { ...w.holdings, [id]: Math.max(0, (w.holdings[id] || 0) - amount) } }));
        return { ...t, curve, graduated: PEDBY_CURVE.graduated(curve) };
      }));
      return;
    }

    const t = tokens.find(tt => tt.id === id);
    if (!t) return;
    if (tradeBusyRef.current) {
      toast('error', 'Hold on — your previous trade is still confirming.');
      return;
    }
    try { if (!walletRef.current.connected) await connect(); } catch (e) { return; }

    // Pre-flight checks — catch the obvious failures BEFORE a wallet popup,
    // with messages that say exactly what's wrong.
    const w = walletRef.current;
    if (side === 'buy') {
      const gasHeadroom = 0.0002; // keep a little ETH for gas on Base
      if (amount > w.eth) {
        toast('error', `Not enough ETH: you tried to buy with ${amount} ETH but your wallet holds ${w.eth.toFixed(4)} ETH.`);
        return;
      }
      if (amount > w.eth - gasHeadroom) {
        toast('error', `That's your entire balance (${w.eth.toFixed(4)} ETH) — leave at least ~${gasHeadroom} ETH for gas and try a slightly smaller amount.`);
        return;
      }
    } else {
      const held = w.holdings[id] || 0;
      if (held <= 0) {
        toast('error', `You don't hold any $${t.ticker} in this wallet.`);
        return;
      }
      if (amount > held * 1.0001) {
        toast('error', `You only hold ${Math.floor(held).toLocaleString()} $${t.ticker} — you tried to sell ${Math.floor(amount).toLocaleString()}.`);
        return;
      }
      if (w.eth < 0.00003) {
        toast('error', `You need a little ETH for gas to sell (wallet holds ${w.eth.toFixed(5)} ETH).`);
        return;
      }
    }

    const label = side === 'buy'
      ? 'Buying $' + t.ticker + ' with ' + amount + ' ETH…'
      : 'Selling ' + Math.round(amount).toLocaleString() + ' $' + t.ticker + '…';
    const dismiss = toast('pending', label + ' Confirm in your wallet.');
    tradeBusyRef.current = true;
    try {
      const hash = side === 'buy'
        ? await PEDBY_CHAIN.buy(t.address, amount)
        : await PEDBY_CHAIN.sell(t.address, amount);
      dismiss();
      toast('ok', (side === 'buy' ? 'Bought $' : 'Sold $') + t.ticker + '.', hash);
      tradeBusyRef.current = false;
      await refresh();
    } catch (e) {
      tradeBusyRef.current = false;
      dismiss();
      toast('error', pedbyErrMsg(e, 'Trade failed.'));
    }
  };

  const selected = tokens.find(t => t.id === selectedId);

  const onQuickTrade = (id, side) => {
    if (side === 'buy') {
      onTrade(id, 'buy', 0.05);
    } else {
      const held = wallet.holdings[id] || 0;
      if (held > 0) onTrade(id, 'sell', held);
    }
  };

  const tokensWithHeld = tokens.map(t => ({ ...t, held: wallet.holdings[t.id] || 0 }));
  // UI buttons get a non-throwing connect; internal flows use connect() and handle aborts
  const safeConnect = () => connect().catch(e => { if (!e || !e.quiet) console.warn('pedby connect:', e); });
  const walletApi = { ...wallet, connect: safeConnect };

  return (
    <div style={{ minHeight: '100vh' }}>
      <PedbyNav page={page} onNav={onNav} wallet={walletApi} />
      {!PEDBY_LIVE && (
        <div style={{ background: '#FCEBD0', color: '#7A4A0B', textAlign: 'center', fontFamily: 'JetBrains Mono', fontSize: 11, letterSpacing: 0.6, padding: '7px 12px' }}>
          DEMO MODE — simulated market, no real funds. Deploy the launchpad contract and set its address in src/pedby/config.js to go live.
        </div>
      )}
      {loading ? (
        <div style={{ textAlign: 'center', padding: '130px 24px 160px' }}>
          <img src="pedby-icon.png" alt="" style={{ width: 52, height: 52, borderRadius: '32%', animation: 'pedby-pulse-dot 1.4s ease-in-out infinite' }} />
          <div style={{ marginTop: 18, color: '#6B7570', fontFamily: 'JetBrains Mono', fontSize: 11.5, letterSpacing: 1.5 }}>
            READING THE CURVE ON {PEDBY_CONFIG.CHAIN.name.toUpperCase()}…
          </div>
          <div style={{ margin: '16px auto 0', width: 180, height: 3, borderRadius: 999, background: '#EFF2EE', overflow: 'hidden' }}>
            <div style={{ width: '40%', height: '100%', borderRadius: 999, background: '#1E8C45', animation: 'pedby-loading-slide 1.2s ease-in-out infinite' }} />
          </div>
        </div>
      ) : (
        <React.Fragment>
          {page === 'landing' && <PedbyLanding onNav={onNav} tokens={tokensWithHeld} creationFee={creationFee} onTrade={onTrade} />}
          {page === 'marketplace' && <PedbyMarketplace tokens={tokensWithHeld} onNav={onNav} trades={trades} onQuickTrade={onQuickTrade} onTrade={onTrade} wallet={walletApi} activityReady={activityReady} />}
          {page === 'create' && <PedbyCreate onNav={onNav} onLaunch={onLaunch} wallet={walletApi} creationFee={creationFee} />}
          {page === 'token' && <PedbyTokenDetail token={selected} onNav={onNav} wallet={walletApi} onTrade={onTrade} trades={trades} activityReady={activityReady} />}
          {page === 'profile' && <PedbyProfile wallet={walletApi} tokens={tokens} onNav={onNav} />}
        </React.Fragment>
      )}

      <PedbyToasts toasts={toasts} />
      {showWallets && <PedbyWalletModal onClose={() => setShowWallets(false)} onWalletConnect={connectViaWalletConnect} />}

      <div style={{ background: '#16201A', marginTop: 40 }}>
        <div style={{ maxWidth: 1080, margin: '0 auto', padding: '48px 32px 40px' }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 20 }}>
            <div style={{ color: '#F6F8F4' }}><PedbyLogo size={19} mono /></div>
            <div style={{ display: 'flex', gap: 26 }}>
              {[['marketplace', 'Marketplace'], ['create', 'Create'], ['profile', 'Wallet']].map(([id, label]) => (
                <button key={id} onClick={() => onNav(id)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 600, color: 'rgba(246,248,244,.65)', padding: 0 }}>{label}</button>
              ))}
            </div>
            <BaseBadge dark />
          </div>
          <div className="pedby-footer-row" style={{ borderTop: '1px solid rgba(255,255,255,.09)', marginTop: 32, paddingTop: 22, display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', gap: 12 }}>
            <span style={{ fontFamily: 'JetBrains Mono', fontSize: 10.5, color: 'rgba(246,248,244,.4)', letterSpacing: 0.5 }}>pedby.com — fair-launch B20 tokens on Base</span>
            <span style={{ fontFamily: 'JetBrains Mono', fontSize: 10.5, color: 'rgba(246,248,244,.4)', letterSpacing: 0.5 }}>
              {PEDBY_LIVE ? 'every token starts as random entropy' : 'bonding-curve mechanics shown are illustrative'}
            </span>
          </div>
        </div>
      </div>
    </div>
  );
}

// Wallet rejections and revert reasons, made human.
function pedbyErrMsg(e, fallback) {
  if (!e) return fallback;
  if (e.code === 'ACTION_REJECTED' || e.code === 4001) return 'You rejected the request in your wallet — nothing was sent.';
  if (e.code === -32002 || /already processing|request already pending/i.test(e.message || ''))
    return 'Your wallet already has a pending request — open the wallet popup and finish it there.';
  const m = (e.shortMessage || e.reason || (e.info && e.info.error && e.info.error.message) || e.message || '');
  if (e.code === 'INSUFFICIENT_FUNDS' || /insufficient funds|exceeds.*balance/i.test(m))
    return 'Not enough ETH in your wallet to cover this amount plus gas — lower the amount or top up.';
  if (/Slippage/i.test(m)) return 'Price moved more than 2% while you were confirming — nothing was traded, just try again.';
  if (/allowance|approve/i.test(m)) return 'Token approval failed — approve the launchpad in your wallet, then retry the sell.';
  if (/chain|network/i.test(m) && /switch|mismatch|unsupported|wrong/i.test(m))
    return 'Your wallet is on the wrong network — switch to ' + PEDBY_CONFIG.CHAIN.name + ' and retry.';
  if (/nonce/i.test(m)) return 'Your wallet has a stuck pending transaction — wait for it or reset activity in wallet settings.';
  if (/timeout|failed to fetch|network error|could not coalesce|missing response/i.test(m))
    return 'The Base RPC hiccuped — your funds are untouched. Wait a few seconds and try again.';
  if (/UnknownToken/i.test(m)) return 'That token is not registered on the pedby launchpad.';
  const clean = m.slice(0, 140);
  return clean || fallback;
}

ReactDOM.createRoot(document.getElementById('root')).render(<PedbyApp />);
