/* global React, Jersey, KitBadge, useT, useLang, PlayerName, CountryName, Ball, Bnb, Logo, PlayerToon */
const { useState: usePF, useEffect: useFXPF, useMemo: useMemoPF } = React;

// Portfolio / Staking view — sole place to manage GOLDBALL stake + silver/gold balances.
// Mirrors the Vault.sol surface:
//   stake(amount) / unstake(amount) → GOLDBALL principal
//   harvest() → accrued silver into silverBalls
//   silverBalls used on PlayerCurves
//   profitable curve sells crystallize into goldBalls
//   withdrawGold() → BNB 1:1 from goldBallReserveBNB

function FlowDiagram() {
  const t = useT();
  return (
    <div className="pf-flow">
      <div className="pf-flow-row">
        <div className="pf-flow-node yellow">
          <div className="pf-flow-icon"><Logo size={28} /></div>
          <div className="pf-flow-name">{t("pf.flow.step1")}</div>
        </div>
        <div className="pf-flow-arrow">
          <span>{t("pf.flow.arrow1")}</span>
          <svg width="36" height="14" viewBox="0 0 36 14"><path d="M0 7 L30 7 M24 2 L30 7 L24 12" stroke="currentColor" strokeWidth="1.5" fill="none"/></svg>
        </div>
        <div className="pf-flow-node silver">
          <div className="pf-flow-icon"><Ball variant="silver" size={26} /></div>
          <div className="pf-flow-name">{t("pf.flow.step2")}</div>
        </div>
        <div className="pf-flow-arrow">
          <span>{t("pf.flow.arrow2")}</span>
          <svg width="36" height="14" viewBox="0 0 36 14"><path d="M0 7 L30 7 M24 2 L30 7 L24 12" stroke="currentColor" strokeWidth="1.5" fill="none"/></svg>
        </div>
        <div className="pf-flow-node player">
          <div className="pf-flow-icon"><PlayerToon size={28} /></div>
          <div className="pf-flow-name">{t("pf.flow.step3")}</div>
        </div>
        <div className="pf-flow-arrow">
          <span>{t("pf.flow.arrow3")}</span>
          <svg width="36" height="14" viewBox="0 0 36 14"><path d="M0 7 L30 7 M24 2 L30 7 L24 12" stroke="currentColor" strokeWidth="1.5" fill="none"/></svg>
        </div>
        <div className="pf-flow-node gold">
          <div className="pf-flow-icon"><Ball variant="gold" size={26} /></div>
          <div className="pf-flow-name">{t("pf.flow.step4")}</div>
        </div>
        <div className="pf-flow-arrow">
          <span>{t("pf.flow.arrow4")}</span>
          <svg width="36" height="14" viewBox="0 0 36 14"><path d="M0 7 L30 7 M24 2 L30 7 L24 12" stroke="currentColor" strokeWidth="1.5" fill="none"/></svg>
        </div>
        <div className="pf-flow-node bnb">
          <div className="pf-flow-icon"><Bnb size={26} /></div>
          <div className="pf-flow-name">{t("pf.flow.step5")}</div>
        </div>
      </div>
    </div>
  );
}

function StakeCard() {
  const t = useT();
  const lang = useLang();
  const [mode, setMode] = usePF("stake");
  const [amount, setAmount] = usePF("");
  const [account, setAccount] = usePF(null);
  const [staked, setStaked] = usePF(0);
  const [walletBal, setWalletBal] = usePF(0);
  const [unlockSec, setUnlockSec] = usePF(0);
  const [busy, setBusy] = usePF(false);
  const [status, setStatus] = usePF("");

  // Subscribe to wallet account
  useFXPF(() => {
    if (!window.GB) return;
    setAccount(window.GB.account());
    return window.GB.onAccountChange(setAccount);
  }, []);

  // Poll on-chain staked + GOLDBALL wallet balance
  useFXPF(() => {
    if (!account || !window.GB) {
      setStaked(0); setWalletBal(0); setUnlockSec(0);
      return;
    }
    let cancelled = false;
    const load = async () => {
      try {
        const v = window.GB.vault(false);
        const gb = window.GB.goldballToken(false);
        const [stk, bal, unlock] = await Promise.all([
          v.stakedAmount(account),
          gb.balanceOf(account),
          v.stakeUnlockTime(account),
        ]);
        if (cancelled) return;
        setStaked(Number(window.GB.formatEther(stk)));
        setWalletBal(Number(window.GB.formatEther(bal)));
        const now = Math.floor(Date.now() / 1000);
        setUnlockSec(Math.max(0, Number(unlock) - now));
      } catch (e) { /* noop */ }
    };
    load();
    const id = setInterval(load, 6000);
    return () => { cancelled = true; clearInterval(id); };
  }, [account]);

  // Live tick down the unlock countdown locally
  useFXPF(() => {
    if (unlockSec <= 0) return;
    const id = setInterval(() => setUnlockSec(s => Math.max(0, s - 1)), 1000);
    return () => clearInterval(id);
  }, [unlockSec > 0]);

  const max = mode === "stake" ? walletBal : staked;
  const v = parseFloat(amount) || 0;
  const hasAccount = !!account;

  async function pickWallet(picker) {
    // Default to injected if available, else WalletConnect
    const mode = (typeof window.ethereum !== "undefined") ? "injected" : "walletconnect";
    setBusy(true);
    setStatus(lang === "zh" ? "连接中..." : "Connecting...");
    try { await window.GB.connectWallet(mode); }
    catch (e) {}
    setBusy(false); setStatus("");
  }

  async function submit() {
    if (!hasAccount) { return pickWallet(); }
    if (!v || v > max) return;
    if (mode === "unstake" && unlockSec > 0) return;
    setBusy(true);
    try {
      const vault = window.GB.vault(true);
      const amt = window.GB.parseEther(String(v));
      if (mode === "stake") {
        // Approve GOLDBALL → vault if allowance insufficient
        const gb = window.GB.goldballToken(true);
        const vaultAddr = window.GB_CONTRACTS.vault;
        const allow = await gb.allowance(account, vaultAddr);
        if (allow < amt) {
          setStatus(lang === "zh" ? "授权中..." : "Approving...");
          const ap = await gb.approve(vaultAddr, (1n << 256n) - 1n);
          await ap.wait();
        }
        setStatus(lang === "zh" ? "确认中..." : "Confirm in wallet...");
        const tx = await vault.stake(amt);
        setStatus(lang === "zh" ? "上链中..." : "Pending...");
        await tx.wait();
      } else {
        setStatus(lang === "zh" ? "确认中..." : "Confirm in wallet...");
        const tx = await vault.unstake(amt);
        setStatus(lang === "zh" ? "上链中..." : "Pending...");
        await tx.wait();
      }
      setStatus(lang === "zh" ? "完成!" : "Done!");
      setAmount("");
      setTimeout(() => setStatus(""), 2500);
    } catch (e) {
      const msg = e?.shortMessage || e?.reason || e?.message || "Tx failed";
      setStatus("Error: " + String(msg).slice(0, 80));
      setTimeout(() => setStatus(""), 5000);
    } finally {
      setBusy(false);
    }
  }

  const fmt = (n) => Number(n).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});

  return (
    <div className="pf-card">
      <div className="pf-card-head">{t("pf.stake.title")}</div>

      <div className="pf-stat-row">
        <div className="pf-stat">
          <div className="lab">{t("pf.stake.staked")}</div>
          <div className="val mono">{fmt(staked)} <span className="unit">GOLDBALL</span></div>
        </div>
        <div className="pf-stat">
          <div className="lab">{t("pf.stake.bal")}</div>
          <div className="val mono">{fmt(walletBal)} <span className="unit">GOLDBALL</span></div>
        </div>
      </div>

      <div className="pf-tabs">
        <button className={mode === "stake" ? "active" : ""} onClick={() => setMode("stake")}>{t("pf.stake.btn")}</button>
        <button className={mode === "unstake" ? "active" : ""} onClick={() => setMode("unstake")}>{t("pf.unstake.btn")}</button>
      </div>

      <div className="sp-field">
        <div className="sp-field-top">
          <span className="dim">{mode === "stake" ? t("pf.stake.btn") : t("pf.unstake.btn")}</span>
          <span className="dim mono" style={{cursor: 'pointer'}} onClick={() => setAmount(max.toString())}>MAX {max.toFixed(2)}</span>
        </div>
        <div className="sp-field-mid">
          <input value={amount} onChange={e => setAmount(e.target.value)} placeholder="0.0" className="mono" />
          <div className="sp-asset"><span style={{display:'inline-flex',alignItems:'center',gap:6}}><Logo size={20} /> GOLDBALL</span></div>
        </div>
        <div className="sp-pcts">
          {[25, 50, 75, 100].map(p => (
            <button key={p} onClick={() => setAmount((max * p / 100).toFixed(2))}>{p === 100 ? "MAX" : p + "%"}</button>
          ))}
        </div>
      </div>

      {hasAccount && mode === "unstake" && unlockSec > 0 && (
        <div className="pf-warn">⏳ {t("pf.stake.unlock")}: <span className="mono">{Math.floor(unlockSec/60)}m {unlockSec % 60}s</span> · <span className="dim">{t("pf.stake.locked")}</span></div>
      )}

      <button
        className={`sp-submit ${mode === "stake" ? "buy" : "sell"}`}
        onClick={submit}
        disabled={busy || (hasAccount && (!v || v > max || (mode === "unstake" && unlockSec > 0)))}
      >
        {busy && status ? status :
         !hasAccount ? (lang === "zh" ? "🔗 连接钱包" : "🔗 Connect Wallet") :
         !v ? t("sp.enterAmount") :
         v > max ? t("sp.insufficient") :
         (mode === "stake" ? t("pf.stake.btn") + " GOLDBALL" : t("pf.unstake.btn") + " GOLDBALL")}
      </button>
      {status && !busy && <div className="sp-status mono">{status}</div>}
    </div>
  );
}

function SilverCard({ silver, pending, onGoTrade }) {
  const t = useT();
  const lang = useLang();
  const [vaultState, setVaultState] = usePF(null);
  const [nextDispatchIn, setNextDispatchIn] = usePF(0);
  const [stakeUnlockIn, setStakeUnlockIn] = usePF(0);
  const [busy, setBusy] = usePF(false);
  const [status, setStatus] = usePF("");
  const [account, setAccount] = usePF(null);
  useFXPF(() => {
    if (!window.GB) return;
    setAccount(window.GB.account());
    return window.GB.onAccountChange(setAccount);
  }, []);

  // Poll user's stakeUnlockTime — harvest reverts with StakeLocked() if you
  // try within 15 min of your latest stake (anti-JIT protection).
  useFXPF(() => {
    if (!account || !window.GB) { setStakeUnlockIn(0); return; }
    let cancelled = false;
    const load = async () => {
      try {
        const v = window.GB.vault(false);
        const unlockAt = Number(await v.stakeUnlockTime(account));
        if (!cancelled) setStakeUnlockIn(Math.max(0, unlockAt - Math.floor(Date.now() / 1000)));
      } catch (e) {}
    };
    load();
    const id = setInterval(load, 8000);
    return () => { cancelled = true; clearInterval(id); };
  }, [account]);
  useFXPF(() => {
    if (stakeUnlockIn <= 0) return;
    const id = setInterval(() => setStakeUnlockIn(s => Math.max(0, s - 1)), 1000);
    return () => clearInterval(id);
  }, [stakeUnlockIn > 0]);

  // Harvest runs INSIDE this card so its busy/status drives the same button.
  // Previously it lived in PortfolioView, so any error / pending state was
  // invisible to the user — clicking the button looked like it did nothing.
  async function onHarvest() {
    if (!window.GB) return;
    setBusy(true);
    try {
      // Force a fresh wallet connection if signer is missing — Reown AppKit
      // sometimes loses the signer reference on mobile after app-switching.
      let signer = window.GB.signer();
      if (!signer) {
        setStatus(lang === "zh" ? "连接钱包..." : "Connecting wallet...");
        await window.GB.connectWallet();
        signer = window.GB.signer();
        if (!signer) throw new Error(lang === "zh" ? "未连接钱包" : "Wallet not connected");
      }
      setStatus(lang === "zh" ? "请在钱包确认..." : "Confirm in wallet...");
      const v = window.GB.vault(true);
      const tx = await v.harvest();
      setStatus(lang === "zh" ? "广播中..." : "Pending...");
      await tx.wait();
      setStatus(lang === "zh" ? "✓ 收获完成!" : "✓ Harvested!");
      setTimeout(() => setStatus(""), 3000);
    } catch (e) {
      console.error("[harvest] failed:", e);
      // Map known custom errors to friendly Chinese / English messages.
      const errName = e?.errorName || e?.revert?.name;
      const rawMsg = e?.shortMessage || e?.reason || e?.message || "Tx failed";
      let friendly;
      if (errName === "StakeLocked" || /StakeLocked/i.test(rawMsg)) {
        friendly = lang === "zh" ? "质押后 15 分钟内不能收获 · 反 JIT 锁定" : "Locked · 15 min after stake (anti-JIT)";
      } else if (/user rejected|user denied/i.test(rawMsg)) {
        friendly = lang === "zh" ? "你在钱包里取消了交易" : "Rejected in wallet";
      } else if (/insufficient funds/i.test(rawMsg)) {
        friendly = lang === "zh" ? "BNB 不足支付 gas" : "Not enough BNB for gas";
      } else {
        friendly = String(rawMsg).slice(0, 80);
      }
      setStatus("✗ " + friendly);
      setTimeout(() => setStatus(""), 7000);
    } finally { setBusy(false); }
  }

  // Poll global vault state (nextDispatchAt + pendingBNB)
  useFXPF(() => {
    if (!window.GB) return;
    let cancelled = false;
    const load = async () => {
      try {
        const v = window.GB.vault(false);
        const s = await v.getVaultState();
        if (cancelled) return;
        const nextAt = Number(s.nextDispatchAt);
        const pendingBNBNum = Number(window.GB.formatEther(s.pendingBNB));
        setVaultState({ nextAt, pendingBNB: pendingBNBNum });
        setNextDispatchIn(Math.max(0, nextAt - Math.floor(Date.now() / 1000)));
      } catch (e) {}
    };
    load();
    const id = setInterval(load, 8000);
    return () => { cancelled = true; clearInterval(id); };
  }, []);

  // Local tick-down so the countdown doesn't wait 8s for each update
  useFXPF(() => {
    if (nextDispatchIn <= 0) return;
    const id = setInterval(() => setNextDispatchIn(s => Math.max(0, s - 1)), 1000);
    return () => clearInterval(id);
  }, [nextDispatchIn > 0]);

  const canDistribute = nextDispatchIn === 0 && vaultState && vaultState.pendingBNB > 0;

  async function distribute() {
    if (!window.GB || !account) return;
    setBusy(true);
    try {
      setStatus(lang === "zh" ? "确认中..." : "Confirm in wallet...");
      const v = window.GB.vault(true);
      const tx = await v.distribute();
      setStatus(lang === "zh" ? "上链中..." : "Pending...");
      await tx.wait();
      setStatus(lang === "zh" ? "分发完成!" : "Distributed!");
      setTimeout(() => setStatus(""), 2500);
    } catch (e) {
      const msg = e?.shortMessage || e?.reason || e?.message || "Tx failed";
      setStatus("Error: " + String(msg).slice(0, 80));
      setTimeout(() => setStatus(""), 5000);
    } finally { setBusy(false); }
  }

  const fmtTime = (sec) => {
    if (sec <= 0) return lang === "zh" ? "可触发" : "Ready";
    const m = Math.floor(sec / 60), s = sec % 60;
    return `${m}m ${String(s).padStart(2,'0')}s`;
  };

  return (
    <div className="pf-card pf-silver-card">
      <div className="pf-card-head">{t("pf.silver.title")}</div>

      <div className="pf-balance">
        <div className="pf-balance-coin-img"><Ball variant="silver" size={56} /></div>
        <div>
          <div className="pf-balance-label">{t("pf.silver.avail")}</div>
          <div className="pf-balance-value mono">{silver.toFixed(2)}</div>
        </div>
      </div>

      <div className="pf-pending">
        <div>
          <div className="pf-pending-label">{t("pf.silver.pending")}</div>
          <div className="pf-pending-value mono up">+{pending.toFixed(4)}</div>
        </div>
        <button
          className="pf-btn-secondary"
          onClick={onHarvest}
          disabled={pending < 0.0001 || busy || stakeUnlockIn > 0}
          title={
            !account ? (lang === "zh" ? "点击连接钱包后收获" : "Click to connect wallet and harvest") :
            stakeUnlockIn > 0 ? (lang === "zh" ? `质押锁定中,${fmtTime(stakeUnlockIn)} 后可收获(反 JIT)` : `Locked · ${fmtTime(stakeUnlockIn)} until harvest (anti-JIT)`) :
            pending < 0.0001 ? (lang === "zh" ? "暂无可收获 SILVER · 等待下一次分发" : "No SILVER to harvest · wait for next dispatch") :
            ""
          }
        >
          {busy && status ? status :
           !account ? (lang === "zh" ? "连接 & 收获" : "Connect") :
           stakeUnlockIn > 0 ? (lang === "zh" ? `锁定 ${fmtTime(stakeUnlockIn)}` : `Locked ${fmtTime(stakeUnlockIn)}`) :
           pending < 0.0001 ? (lang === "zh" ? "暂无可收" : "Nothing yet") :
           t("pf.silver.harvest")}
        </button>
      </div>
      {status && !busy && (
        <div className="sp-status mono" style={{marginTop:8, fontSize:12, color: status.startsWith('✗') ? 'var(--red)' : 'var(--green)'}}>
          {status}
        </div>
      )}

      {/* Distribution status — explains where silver actually comes from */}
      {vaultState && (
        <div className="pf-dispatch">
          <div className="pf-dispatch-row">
            <span className="dim">{lang === "zh" ? "下次分发" : "Next dispatch"}</span>
            <span className="mono" style={{color: nextDispatchIn === 0 ? "var(--green)" : "var(--text-2)"}}>
              {fmtTime(nextDispatchIn)}
            </span>
          </div>
          <div className="pf-dispatch-row">
            <span className="dim">{lang === "zh" ? "待分发税收" : "Pending tax (BNB)"}</span>
            <span className="mono">{vaultState.pendingBNB.toFixed(4)} BNB</span>
          </div>
          <button
            className="pf-btn-secondary"
            style={{width:"100%", marginTop:8}}
            onClick={distribute}
            disabled={!canDistribute || !account || busy}
            title={lang === "zh" ? "触发分发:把累积的税收转成 SILVER 给所有 staker" : "Trigger dispatch: convert accrued tax into SILVER for all stakers"}
          >
            {busy && status ? status :
             !account ? (lang === "zh" ? "🔗 连接钱包" : "🔗 Connect Wallet") :
             nextDispatchIn > 0 ? (lang === "zh" ? `等待 ${fmtTime(nextDispatchIn)}` : `Wait ${fmtTime(nextDispatchIn)}`) :
             !vaultState.pendingBNB ? (lang === "zh" ? "暂无待分发税收" : "No tax to dispatch") :
             (lang === "zh" ? "▶ 触发分发" : "▶ Trigger Dispatch")}
          </button>
          {status && !busy && <div className="sp-status mono" style={{marginTop:6}}>{status}</div>}
        </div>
      )}

      <button className="pf-btn-link" onClick={onGoTrade}>{t("pf.silver.spend")}</button>
    </div>
  );
}

function GoldCard({ gold, onWithdraw }) {
  const t = useT();
  return (
    <div className="pf-card pf-gold-card">
      <div className="pf-card-head">{t("pf.gold.title")}</div>

      <div className="pf-balance">
        <div className="pf-balance-coin-img"><Ball variant="gold" size={56} /></div>
        <div>
          <div className="pf-balance-label">{t("pf.gold.bal")}</div>
          <div className="pf-balance-value mono" style={{display:'inline-flex',alignItems:'center',gap:6}}>{gold.toFixed(4)} <Bnb size={16} /> <span style={{fontSize:13, color:'var(--text-3)'}}>BNB</span></div>
        </div>
      </div>

      <div className="pf-note">{t("pf.gold.note")}</div>

      <button className="sp-submit buy" onClick={onWithdraw} disabled={gold <= 0}>
        {t("pf.gold.withdraw")}
      </button>
    </div>
  );
}

function PositionsTable({ positions, players, onOpen }) {
  const t = useT();
  return (
    <div className="pf-card pf-table-card">
      <div className="pf-card-head">{t("pf.pos.title")}</div>
      <table className="pf-table">
        <thead>
          <tr>
            <th>{t("pf.pos.col.player")}</th>
            <th>{t("pf.pos.col.qty")}</th>
            <th>{t("pf.pos.col.avg")}</th>
            <th>{t("pf.pos.col.price")}</th>
            <th>{t("pf.pos.col.value")}</th>
            <th>{t("pf.pos.col.pnl")}</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          {positions.length === 0 && (
            <tr><td colSpan="7" className="b-empty">{t("pf.pos.empty")}</td></tr>
          )}
          {positions.map((pos, i) => {
            const p = players.find(pp => pp.id === pos.playerId);
            if (!p) return null;
            const value = pos.qty * p.priceBNB; // value in silver
            const cost = pos.qty * pos.avg;
            const pnl = value - cost;
            const pnlPct = cost > 0 ? (pnl / cost) * 100 : 0;
            return (
              <tr key={i} onClick={() => onOpen(p)} style={{cursor: 'pointer'}}>
                <td>
                  <div className="b-mt-pair">
                    <Jersey kit={p.kit} number={p.number} size={28} />
                    <div className="b-mt-pair-meta">
                      <div className="b-mt-name"><span className="b-mt-sym">${p.symbol}</span></div>
                      <div className="b-mt-sub"><PlayerName p={p} both={false} /></div>
                    </div>
                  </div>
                </td>
                <td className="mono">{pos.qty.toLocaleString(undefined, {maximumFractionDigits: 0})}</td>
                <td className="mono dim">{window.fmtUsdPrice ? window.fmtUsdPrice(pos.avg) : "$" + (pos.avg * 612).toFixed(4)}</td>
                <td className="mono">{window.fmtUsdPrice ? window.fmtUsdPrice(p.priceBNB) : "$" + (p.priceBNB * 612).toFixed(4)}</td>
                <td className="mono" style={{display:'inline-flex',alignItems:'center',gap:6}}>{value.toFixed(2)} <Ball variant="silver" size={14} /></td>
                <td className={`mono ${pnl >= 0 ? 'up' : 'down'}`}>
                  {pnl >= 0 ? '+' : ''}{pnl.toFixed(2)} <span style={{fontSize:11}}>({pnlPct >= 0 ? '+' : ''}{pnlPct.toFixed(1)}%)</span>
                </td>
                <td>
                  <button className="b-mt-btn" onClick={(e) => { e.stopPropagation(); onOpen(p); }}>{t("mt.action")}</button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

function PortfolioView({ players, onOpen, lang, setLang, onNavHome }) {
  const t = useT();

  // All values come from chain. No more hardcoded demo numbers.
  const [account, setAccount] = usePF(null);
  const [silver, setSilver] = usePF(0);
  const [pending, setPending] = usePF(0);
  const [gold, setGold] = usePF(0);
  const [busy, setBusy] = usePF(false);
  const [status, setStatus] = usePF("");

  // Subscribe to wallet account
  useFXPF(() => {
    if (!window.GB) return;
    setAccount(window.GB.account());
    return window.GB.onAccountChange(setAccount);
  }, []);

  // Poll real silver/pending/gold balances from Vault
  useFXPF(() => {
    if (!account || !window.GB) {
      setSilver(0); setPending(0); setGold(0);
      return;
    }
    let cancelled = false;
    const load = async () => {
      try {
        const v = window.GB.vault(false);
        const [sb, gb, ps] = await Promise.all([
          v.silverBalls(account),
          v.goldBalls(account),
          v.pendingSilver(account),
        ]);
        if (cancelled) return;
        setSilver(Number(window.GB.formatEther(sb)));
        setGold(Number(window.GB.formatEther(gb)));
        setPending(Number(window.GB.formatEther(ps)));
      } catch (e) { /* noop */ }
    };
    load();
    const id = setInterval(load, 6000);
    return () => { cancelled = true; clearInterval(id); };
  }, [account]);

  async function onWithdraw() {
    if (!account || !window.GB || gold <= 0) return;
    setBusy(true);
    try {
      setStatus(lang === "zh" ? "确认中..." : "Confirm in wallet...");
      const v = window.GB.vault(true);
      const amt = window.GB.parseEther(String(gold));
      const tx = await v.withdrawGold(amt);
      setStatus(lang === "zh" ? "上链中..." : "Pending...");
      await tx.wait();
      setStatus(lang === "zh" ? "完成!" : "Done!");
      setTimeout(() => setStatus(""), 2500);
    } catch (e) {
      const msg = e?.shortMessage || e?.reason || e?.message || "Tx failed";
      setStatus("Error: " + String(msg).slice(0, 80));
      setTimeout(() => setStatus(""), 5000);
    } finally { setBusy(false); }
  }

  // Real positions: read player token balances for each on-chain player.
  // Build on demand once players are loaded.
  const [positions, setPositions] = usePF([]);
  useFXPF(() => {
    if (!account || !window.GB || !players || players.length === 0) {
      setPositions([]);
      return;
    }
    let cancelled = false;
    const load = async () => {
      try {
        const live = players.filter(p => p.token && !p.pending);
        const balances = await Promise.all(live.map(async (p) => {
          try {
            const tok = window.GB.playerToken(p.token, false);
            const bal = await tok.balanceOf(account);
            const qty = Number(window.GB.formatEther(bal));
            return qty > 0 ? { playerId: p.id, symbol: p.symbol, qty, avg: p.priceBNB || 0 } : null;
          } catch (e) { return null; }
        }));
        if (cancelled) return;
        setPositions(balances.filter(Boolean));
      } catch (e) { /* noop */ }
    };
    load();
    const id = setInterval(load, 10000);
    return () => { cancelled = true; clearInterval(id); };
  }, [account, players]);

  return (
    <div className="b-container">
      <div className="pf-header">
        <div>
          <h2 className="pf-title">{t("pf.title")}</h2>
          <p className="pf-sub">{t("pf.sub")}</p>
        </div>
      </div>

      <FlowDiagram />

      <div className="pf-grid">
        <StakeCard />
        <SilverCard silver={silver} pending={pending} onGoTrade={onNavHome} />
        <GoldCard gold={gold} onWithdraw={onWithdraw} />
      </div>

      <PositionsTable positions={positions} players={players} onOpen={onOpen} />
    </div>
  );
}

window.PortfolioView = PortfolioView;
