/* P.E.S.T Web App — token prices: real prices via nc-proxy.php?action=getPrices
   (server-cached 5min from CoinGecko). Watchlist, picker, guest strip. */

/* ---------- live price engine: pulls from server every 5 min ---------- */
const PriceEngine = (function () {
  // Map: token.id → { price, change }
  let state = {};
  let tokensRef = [];
  const subs = new Set();

  function publish() { subs.forEach((fn) => fn()); }

  function applyResponse(resp) {
    // resp.prices is keyed by CoinGecko id ("solana", "bitcoin", …)
    const cg = (resp && resp.prices) || {};
    const ns = {};
    tokensRef.forEach((t) => {
      const live = t.gecko && cg[t.gecko];
      if (live) {
        ns[t.id] = {
          price:  typeof live.usd === "number" ? live.usd : t.price,
          change: typeof live.usd_24h_change === "number" ? live.usd_24h_change : t.change24h,
        };
      } else {
        // Fallback: use seed values until next pull (or token has no gecko id)
        const prev = state[t.id];
        ns[t.id] = prev || { price: t.price, change: t.change24h };
      }
    });
    state = ns;
    publish();
  }

  async function pull() {
    try {
      const resp = await window.PEST_APP.api.getPrices();
      applyResponse(resp);
    } catch (e) { /* keep current state on failure */ }
  }

  function ensure(tokens) {
    tokensRef = tokens || [];
    // Seed any missing entries so the UI has something to render before the first pull lands
    tokensRef.forEach((t) => { if (!state[t.id]) state[t.id] = { price: t.price, change: t.change24h }; });
  }

  // Refresh every 5 min; first call happens on initial ensure() via useLivePrices.
  let timer = null, started = false;
  function start() {
    if (started) return;
    started = true;
    pull();
    timer = setInterval(pull, 5 * 60 * 1000);
  }

  return {
    get: () => state,
    ensure: (tokens) => { ensure(tokens); start(); },
    subscribe: (fn) => { subs.add(fn); return () => subs.delete(fn); },
    refresh: pull,
  };
})();

function useLivePrices(tokens) {
  const [, force] = useState(0);
  if (tokens) PriceEngine.ensure(tokens);
  useEffect(() => PriceEngine.subscribe(() => force((x) => x + 1)), []);
  return PriceEngine.get();
}

/* ---------- formatting ---------- */
function fmtPrice(p) {
  if (p >= 1000) return "$" + p.toLocaleString("en-US", { maximumFractionDigits: 0 });
  if (p >= 1) return "$" + p.toFixed(2);
  if (p >= 0.01) return "$" + p.toFixed(4);
  if (p >= 0.0001) return "$" + p.toFixed(6);
  return "$" + p.toFixed(8);
}
function fmtChange(c) { return (c >= 0 ? "+" : "") + c.toFixed(2) + "%"; }

/* ---------- token tile: image if logo present, else deterministic-hue symbol tile ---------- */
function TokenTile({ token, className }) {
  const src = window.resolveLogo(token.logo);
  if (src) {
    return (
      <div className={"tok-tile " + (className || "")}>
        <img src={src} alt={token.symbol} style={{ width: "100%", height: "100%", objectFit: "cover", borderRadius: "inherit" }} />
      </div>
    );
  }
  const h = hueFromString(token.symbol + token.name);
  const bg = `linear-gradient(150deg, hsl(${h} 68% 28%), hsl(${(h + 42) % 360} 62% 16%))`;
  return (
    <div className={"tok-tile " + (className || "")} style={{ background: bg }}>
      <span>{token.symbol.length > 4 ? token.symbol.slice(0, 4) : token.symbol}</span>
    </div>
  );
}

/* ---------- dashboard watchlist card ---------- */
function TokenWatchlist({ onManage }) {
  const store = useStore();
  const prices = useLivePrices(store.tokens);
  const byId = (id) => store.tokens.find((t) => t.id === id);
  const watched = (store.user.watchTokens || []).map(byId).filter(Boolean);

  return (
    <div className="card cut tok-card">
      <div className="tok-head">
        <div className="tok-head-l">
          <span className="eyebrow">Markets</span>
          <h2>Your <span className="grad-text">watchlist</span></h2>
        </div>
        <button className="btn btn-sm" onClick={onManage}><window.AIcon.edit /> Manage</button>
      </div>

      {watched.length === 0 ? (
        <div className="tok-empty">
          <p>No tokens yet. Pick the coins you track and watch them live here.</p>
          <button className="btn btn-primary btn-sm" onClick={onManage}><window.AIcon.plus /> Add tokens</button>
        </div>
      ) : (
        <div className="tok-list">
          {watched.map((t) => {
            const live = prices[t.id] || { price: t.price, change: t.change24h };
            const up = live.change >= 0;
            return (
              <div className="tok-row" key={t.id}>
                <TokenTile token={t} />
                <div className="tok-id">
                  <b>{t.symbol}</b>
                  <span>{t.name}</span>
                </div>
                <div className="tok-price">
                  <b>{fmtPrice(live.price)}</b>
                  <span className={"tok-chg " + (up ? "up" : "down")}>
                    <i className={"tok-arrow " + (up ? "up" : "down")}></i>{fmtChange(live.change)}
                  </span>
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

/* ---------- token picker modal ---------- */
function TokenPicker({ onClose }) {
  const store = useStore();
  const [q, setQ] = useState("");
  useEffect(() => {
    const k = (e) => e.key === "Escape" && onClose();
    window.addEventListener("keydown", k);
    return () => window.removeEventListener("keydown", k);
  }, []);
  const watched = store.user.watchTokens || [];
  const MAX_WATCH = window.PEST_APP.MAX_WATCH;
  const full = watched.length >= MAX_WATCH;
  const ql = q.trim().toLowerCase();
  const list = store.tokens.filter(
    (t) => !ql || t.symbol.toLowerCase().includes(ql) || t.name.toLowerCase().includes(ql)
  );
  const toggle = (t) => {
    if (!watched.includes(t.id) && full) { toast("Watchlist is full (" + MAX_WATCH + " max)", "err"); return; }
    store.toggleToken(t.id);
  };

  return ReactDOM.createPortal(
    <div className="modal-back" onClick={onClose}>
      <div className="modal cut" style={{ width: "min(460px,100%)" }} onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <div className="eyebrow">Markets</div>
            <h3 style={{ margin: "4px 0 0" }}>Manage watchlist</h3>
          </div>
          <button className="icon-x" onClick={onClose} aria-label="Close"><window.AIcon.close /></button>
        </div>
        <div className="modal-body">
          <p className="tok-note">Pick the tokens you want on your dashboard. <b style={{ color: full ? "var(--danger)" : "var(--tx)" }}>{watched.length}/{MAX_WATCH}</b> selected · the community's top 6 show on the public board.</p>
          <div className="search-wrap" style={{ marginBottom: 12 }}>
            <window.AIcon.search />
            <input className="input" placeholder="Search tokens…" value={q} onChange={(e) => setQ(e.target.value)} />
          </div>
          <div className="tok-pick-list">
            {list.map((t) => {
              const on = watched.includes(t.id);
              const blocked = !on && full;
              return (
                <button className={"tok-pick" + (on ? " on" : "") + (blocked ? " is-disabled" : "")} key={t.id} onClick={() => toggle(t)} title={blocked ? "Watchlist is full (" + MAX_WATCH + " max)" : ""}>
                  <TokenTile token={t} />
                  <div className="tok-id"><b>{t.symbol}</b><span>{t.name}</span></div>
                  <span className="tok-pick-state">{on ? <window.AIcon.check /> : <window.AIcon.plus />}</span>
                </button>
              );
            })}
            {list.length === 0 && <p className="tok-note" style={{ textAlign: "center" }}>No tokens match “{q}”.</p>}
          </div>
        </div>
      </div>
    </div>,
    document.body
  );
}

/* ---------- public board: top 6 most-watched tokens ---------- */
function topCommunityTokens(store, n) {
  const mine = new Set(store.user.watchTokens || []);
  return [...store.tokens]
    .map((t) => ({ t, score: t.watchers + (mine.has(t.id) ? 1 : 0) }))
    .sort((a, b) => b.score - a.score)
    .slice(0, n)
    .map((x) => x.t);
}

function GuestTokenStrip({ tokens }) {
  const store = useStore();
  // Guests get the pre-ranked public-board tokens via prop; fall back to the store otherwise.
  const top = ((tokens && tokens.length) ? tokens : topCommunityTokens(store, 8)).slice(0, 8);
  const prices = useLivePrices(top);
  if (!top.length) return null;
  return (
    <div className="tok-strip">
      <div className="tok-strip-label"><span className="eyebrow">Most-watched by the Core</span></div>
      <div className="tok-strip-row">
        {top.map((t) => {
          const live = prices[t.id] || { price: t.price, change: t.change24h };
          const up = live.change >= 0;
          return (
            <div className="tok-tick" key={t.id}>
              <TokenTile token={t} className="sm" />
              <div className="tok-tick-id">
                <div className="tok-tick-top">
                  <b>{t.symbol}</b>
                  <span className={"tok-chg " + (up ? "up" : "down")}><i className={"tok-arrow " + (up ? "up" : "down")}></i>{fmtChange(live.change)}</span>
                </div>
                <span className="tok-tick-price">{fmtPrice(live.price)}</span>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

Object.assign(window, {
  PriceEngine, useLivePrices, fmtPrice, fmtChange, TokenTile,
  TokenWatchlist, TokenPicker, GuestTokenStrip, topCommunityTokens,
});
