/* ===============================================================
   AGENTICS.CREDIT — CAMPAIGN · SHARED MODALS MODULE
   Split out of campaign.jsx on 2026-07-03. Contains modals + banners
   used by BOTH the dashboard and the MyAgent page:

     - ReallocateCapitalModal + FundingPicker — move-capital flow
     - AddAgentModal + PineScriptImporter — new-agent creation
     - StrategyCoachModal — coach chat modal
     - KyaBadge — the Know-Your-Agent identity chip
     - RiskBanner — soft-cut / hard-cut / no-credit banner

   Load AFTER campaign.jsx (which owns apiPost / acGet / acSend / Card
   / TurnstileWidget) and BEFORE campaign-myagent.jsx (which mounts
   the coach + add-agent modals).
   =============================================================== */

function ReallocateCapitalModal({ source, agents, onClose, onTransferred, onCreateNew }) {
  const available = Number(source.paperBalance || 0);
  const eligibleTargets = (agents || []).filter(a =>
    a.id !== source.id &&
    a.status === "active" &&
    a.risk_state !== "halted"
  );

  const [targetId, setTargetId]   = useState(eligibleTargets[0] ? eligibleTargets[0].id : "");
  const [amount, setAmount]       = useState(available);
  const [submitting, setSubmitting] = useState(false);
  const [error, setError]         = useState("");

  function setMax()  { setAmount(available); }
  function setHalf() { setAmount(Math.round((available / 2) * 100) / 100); }

  async function submit() {
    setError("");
    const amt = Number(amount);
    if (!Number.isFinite(amt) || amt <= 0) {
      setError("Enter an amount above zero.");
      return;
    }
    if (amt > available + 1e-6) {
      setError("Amount exceeds available balance.");
      return;
    }
    if (!targetId) {
      setError("Pick a destination agent first.");
      return;
    }
    setSubmitting(true);
    try {
      await acSend(`/agents/${source.id}/transfer`, {
        target_agent_id: targetId,
        amount:          amt,
      }, "POST");
      onTransferred && onTransferred();
    } catch (e) {
      const msg = (e && (e.detail || e.message)) || "Try again";
      setError(msg.replace(/^\d+:\s*/, ""));
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <div className="cg-modal-backdrop" onClick={onClose}>
      <div className="cg-modal" style={{ width: 520, maxWidth: "94vw" }} onClick={(e) => e.stopPropagation()}>
        <div className="cg-modal-head">
          <div>
            <div className="cg-modal-eyebrow">// MOVE_CAPITAL — {source.name}</div>
            <div className="cg-modal-title">Reallocate ${available.toFixed(2)}</div>
          </div>
          <button className="cg-modal-x" onClick={onClose}>×</button>
        </div>

        <div style={{ padding: "16px 18px", borderBottom: "1px solid var(--border)" }}>
          <div style={{ fontSize: 12, color: "var(--cg-muted)", lineHeight: 1.6, marginBottom: 12 }}>
            <strong style={{ color: "var(--red, #b13b3b)" }}>{source.name}</strong> is finished for the campaign — its credit history is locked.
            But the remaining ${available.toFixed(2)} can be moved into an existing active agent or used to fund a new one.
          </div>
        </div>

        <div style={{ padding: 18 }}>
          {/* OPTION A — fund existing */}
          <div style={{ marginBottom: 18 }}>
            <div className="cg-field-label" style={{ fontSize: 11, letterSpacing: 1.5, color: "var(--cg-muted)", marginBottom: 8 }}>
              MOVE TO EXISTING AGENT
            </div>
            {eligibleTargets.length === 0 ? (
              <div style={{ fontSize: 12, color: "var(--cg-muted)", padding: "10px 12px", border: "1px dashed var(--border)" }}>
                No eligible active agents to receive — create a new one below.
              </div>
            ) : (
              <>
                <select
                  value={targetId}
                  onChange={e => setTargetId(e.target.value)}
                  style={{ width: "100%", padding: 10, background: "var(--bg)", border: "1px solid var(--border)", color: "var(--fg)", fontFamily: "JetBrains Mono, monospace", fontSize: 13 }}
                >
                  {eligibleTargets.map(a => (
                    <option key={a.id} value={a.id}>
                      {a.name} — current balance ${Number(a.paperBalance || 0).toFixed(2)}
                    </option>
                  ))}
                </select>

                <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 12 }}>
                  <input
                    type="number"
                    min={0.01}
                    max={available}
                    step={0.01}
                    value={amount}
                    onChange={e => setAmount(Number(e.target.value))}
                    style={{ flex: 1, padding: 10, background: "var(--bg)", border: "1px solid var(--border)", color: "var(--fg)", fontFamily: "JetBrains Mono, monospace", fontSize: 13 }}
                  />
                  <button className="cg-cta-ghost" onClick={setHalf} style={{ padding: "8px 12px", fontSize: 11 }}>50%</button>
                  <button className="cg-cta-ghost" onClick={setMax}  style={{ padding: "8px 12px", fontSize: 11 }}>MAX</button>
                </div>

                <button
                  className="cg-cta-primary"
                  disabled={submitting || !targetId || amount <= 0 || amount > available}
                  onClick={submit}
                  style={{ width: "100%", marginTop: 14 }}
                >
                  {submitting ? "MOVING…" : `MOVE $${Number(amount || 0).toFixed(2)} →`}
                </button>
              </>
            )}
          </div>

          {/* Divider */}
          <div style={{ textAlign: "center", color: "var(--cg-muted)", fontSize: 10, letterSpacing: 2, margin: "8px 0 16px" }}>
            ─── OR ───
          </div>

          {/* OPTION B — create new */}
          <button
            className="cg-cta-ghost"
            onClick={onCreateNew}
            style={{ width: "100%", padding: 12, fontSize: 12 }}
          >
            + CREATE A NEW AGENT (funded from {source.name})
          </button>

          {error && (
            <div style={{ marginTop: 12, padding: 10, fontSize: 12, color: "var(--red, #b13b3b)", background: "rgba(177,59,59,0.06)", border: "1px solid rgba(177,59,59,0.30)" }}>
              {error}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}


/* Add-agent modal (lightweight) */
/* Add-agent modal — two creation paths:
     - PRESET tab: pick one of the four leveraged presets from
       GET /agents/presets; agent is launched with risk_level + name.
     - PINE_SCRIPT tab: paste a TradingView strategy and translate it
       via /strategy/translate-pine; the resulting JSON becomes the
       agent's initial strategy_state on launch.
   The SURGE preset (20× leverage) shows a blowup_warning confirm step.
*/
function AddAgentModal({ onClose, onCreated }) {
  const [mode, setMode] = useState("preset");      // preset | pine
  const [presets, setPresets] = useState([]);
  const [selectedKey, setSelectedKey] = useState("medium");
  const [name, setName] = useState("");
  const [phase, setPhase] = useState("pick");      // pick → confirm (very_high) → submitting → pine_review
  const [error, setError] = useState("");
  // Pine path: translated strategy_state + unsupported features held
  // here between "translate" and "launch" steps.
  const [pineStrategy, setPineStrategy] = useState(null);
  const [pineWarnings, setPineWarnings] = useState([]);

  // Funding source — agents are funded by debiting either a sibling
  // agent's paper_balance or the (usually empty) participant lobby.
  // We fetch the user's existing agents on mount so we can show each
  // as a funding option with its actual free cash.
  const [sources, setSources] = useState([]);        // [{ id, label, available, isLobby? }]
  const [sourceId, setSourceId] = useState("");      // "" = lobby; otherwise agent UUID
  const [fundAmount, setFundAmount] = useState(500); // sensible default

  useEffect(() => {
    acGet("/agents/presets")
      .then(d => { if (d && Array.isArray(d.presets)) setPresets(d.presets); })
      .catch(() => setPresets(_LOCAL_PRESET_FALLBACK));
  }, []);

  useEffect(() => {
    // Pull existing agents + the participant's lobby balance.
    Promise.all([acGet("/agents"), acGet("/me").catch(() => null)]).then(([ag, me]) => {
      const list = (ag && (ag.agents || ag)) || [];
      const lobbyFromMe = me && me.participant ? Number(me.participant.paper_balance || 0) : 0;
      const opts = [];
      // Highest-balance sibling agent first — it's the most likely source.
      const agentOpts = list
        .map(a => ({
          id:        a.id,
          label:     `${a.name} ($${Number(a.paper_balance || 0).toLocaleString(undefined, { maximumFractionDigits: 2 })} free)`,
          available: Number(a.paper_balance || 0),
          isLobby:   false,
        }))
        .sort((a, b) => b.available - a.available);
      opts.push(...agentOpts);
      // Lobby option — usually $0 for campaign signups, but include for
      // legacy users / future flows that put seed there.
      opts.push({
        id:        "",
        label:     `Lobby ($${lobbyFromMe.toLocaleString(undefined, { maximumFractionDigits: 2 })})`,
        available: lobbyFromMe,
        isLobby:   true,
      });
      setSources(opts);
      // Auto-select the first non-zero option, falling back to whatever's first.
      const firstNonZero = opts.find(o => o.available > 0) || opts[0];
      if (firstNonZero) {
        setSourceId(firstNonZero.id);
        // Default fund amount: smaller of $500 or 50% of the source.
        // Floors at 10 so we don't auto-set below MIN_TRADE_SIZE.
        setFundAmount(Math.max(10, Math.min(500, Math.floor(firstNonZero.available / 2))));
      }
    }).catch(() => { /* stub mode — fall through, user can still type */ });
  }, []);

  const picked = presets.find(p => p.key === selectedKey) || presets[1] || presets[0];
  const pickedSource = sources.find(s => s.id === sourceId);
  const maxFund = pickedSource ? pickedSource.available : 0;
  const fundValid = fundAmount > 0 && fundAmount <= maxFund;

  async function launch() {
    setError(""); setPhase("submitting");
    try {
      const body = mode === "pine" && pineStrategy
        ? { strategy_state: pineStrategy, name: name.trim() || undefined }
        : { risk_level: selectedKey, name: name.trim() || undefined };
      if (!body.name) delete body.name;
      // Always include the funding params — backend defaults are no
      // longer good enough (lobby is empty for campaign signups).
      body.initial_balance = Number(fundAmount);
      if (sourceId) body.source_agent_id = sourceId;
      const res = await acSend("/agents", body);
      const agent = res && res.agent ? res.agent : res;
      onCreated(agent);
    } catch (e) {
      // Surface the backend's real message (acSend now puts it on
      // e.detail / e.message). Falls back to a generic line if neither
      // is available (e.g. network error).
      const msg = (e && (e.detail || e.message)) || "";
      setError(msg ? msg.replace(/^\d+:\s*/, "") : "Couldn't launch — try again.");
      setPhase(mode === "pine" ? "pine_review" : "pick");
    }
  }

  function onLaunchClick() {
    if (mode === "preset" && picked && picked.blowup_warning) { setPhase("confirm"); return; }
    launch();
  }

  function onPineTranslated(strategy, warnings) {
    setPineStrategy(strategy);
    setPineWarnings(Array.isArray(warnings) ? warnings : []);
    setPhase("pine_review");
  }

  // What's the modal showing right now?
  const showConfirm     = phase === "confirm";
  const showPineReview  = phase === "pine_review";
  const showPineInput   = mode === "pine" && !showPineReview;
  const showPresetPick  = mode === "preset" && !showConfirm;

  const title = showConfirm ? "Confirm 20× SURGE"
              : showPineReview ? "Review imported strategy"
              : "Launch another agent";

  return (
    <div className="cg-modal-backdrop" onClick={onClose}>
      <div className="cg-modal cg-modal-wide" onClick={e => e.stopPropagation()}>
        <div className="cg-modal-head">
          <div>
            <div className="cg-modal-eyebrow">// ADD_AGENT</div>
            <div className="cg-modal-title">{title}</div>
          </div>
          <button className="cg-modal-x" onClick={onClose}>×</button>
        </div>
        <div className="cg-modal-body">

          {/* Mode switcher — hidden during confirm/review (those are
              flow-locked sub-states the user gets back to via Back). */}
          {!showConfirm && !showPineReview && (
            <div className="cg-mode-tabs">
              <button
                className={"cg-mode-tab" + (mode === "preset" ? " active" : "")}
                onClick={() => setMode("preset")}
              >PRESET</button>
              <button
                className={"cg-mode-tab" + (mode === "pine" ? " active" : "")}
                onClick={() => setMode("pine")}
              >PINE SCRIPT ↗</button>
            </div>
          )}

          {showConfirm && picked && (
            <div className="cg-confirm-block">
              <p>SURGE runs <strong>20× leverage</strong> across 5 markets. At that size, a 1% adverse move is 20% of your margin and account blowup is a real outcome.</p>
              <p>The system enforces tight 0.5% stops, a 30-minute cooldown after 2 losing trades in a row, and auto-halves leverage if your drawdown crosses 50% — but you should pick this only if you accept the variance.</p>
              <div className="cg-confirm-actions">
                <button className="cg-cta-secondary" onClick={() => setPhase("pick")}>← Back</button>
                <button className="cg-cta-primary" onClick={launch}>I understand — launch SURGE</button>
              </div>
            </div>
          )}

          {showPresetPick && (
            <>
              <div className="cg-presets-grid">
                {presets.map(p => (
                  <button
                    key={p.key}
                    className={"cg-preset-card" + (selectedKey === p.key ? " selected" : "") + (p.blowup_warning ? " risky" : "")}
                    onClick={() => setSelectedKey(p.key)}
                  >
                    <div className="cg-preset-top">
                      <span className="cg-preset-name">{p.name_prefix || p.key.toUpperCase()}</span>
                      <span className="cg-preset-lev">{p.leverage_label || (p.leverage + "x")}</span>
                    </div>
                    <div className="cg-preset-tier">Targets {p.target_tier}</div>
                    <div className="cg-preset-row"><span>Markets</span><span>{(p.markets || []).length}</span></div>
                    <div className="cg-preset-row"><span>Stop / TP</span><span>{p.stop_loss_pct}% / {p.take_profit_pct}%</span></div>
                    <div className="cg-preset-row"><span>Min trades / day</span><span>{p.min_trades_per_day}</span></div>
                    {p.blowup_warning && <div className="cg-preset-warning">⚠ Blowup risk</div>}
                  </button>
                ))}
              </div>

              {picked && (
                <div className="cg-preset-detail">
                  <div className="cg-preset-detail-label">PERSONALITY</div>
                  <div className="cg-preset-detail-body">{picked.personality}</div>
                </div>
              )}

              <div className="cg-field">
                <label>AGENT_NAME <span className="opt">(optional — auto-generated as {(picked && picked.name_prefix) || "AGENT"}-NNN)</span></label>
                <input value={name} onChange={e => setName(e.target.value)} placeholder={(picked && picked.name_prefix) ? picked.name_prefix + "-042" : "RISKTAKER"} />
              </div>

              {/* Funding source — picks which existing pot the new agent
                  pulls capital from. After signup the participant lobby
                  is empty (entire seed went to the primary agent), so
                  the realistic source is one of the user's other agents. */}
              <FundingPicker
                sources={sources}
                sourceId={sourceId}
                onSourceChange={setSourceId}
                amount={fundAmount}
                onAmountChange={setFundAmount}
                max={maxFund}
              />

              {error && <div className="cg-modal-error">{error}</div>}

              <button
                className="cg-cta-primary"
                disabled={phase === "submitting" || !fundValid}
                onClick={onLaunchClick}
              >
                {phase === "submitting" ? "LAUNCHING…" : "LAUNCH AGENT →"}
              </button>
            </>
          )}

          {showPineInput && (
            <PineScriptImporter
              onTranslated={onPineTranslated}
              onCancel={() => setMode("preset")}
            />
          )}

          {showPineReview && pineStrategy && (
            <>
              <div className="cg-pine-summary">
                {pineStrategy.leverage != null && (
                  <div className="cg-pine-summary-row"><span>Leverage</span><span>{pineStrategy.leverage}x</span></div>
                )}
                {pineStrategy.markets && (
                  <div className="cg-pine-summary-row"><span>Markets</span><span>{(pineStrategy.markets || []).join(", ")}</span></div>
                )}
                {pineStrategy.position_size_pct != null && (
                  <div className="cg-pine-summary-row"><span>Position size</span><span>{pineStrategy.position_size_pct}%</span></div>
                )}
                {pineStrategy.stop_loss_pct != null && (
                  <div className="cg-pine-summary-row"><span>Stop loss</span><span>{pineStrategy.stop_loss_pct}%</span></div>
                )}
                {pineStrategy.take_profit_pct != null && (
                  <div className="cg-pine-summary-row"><span>Take profit</span><span>{pineStrategy.take_profit_pct}%</span></div>
                )}
              </div>

              {pineWarnings.length > 0 && (
                <div className="cg-pine-warnings">
                  <div className="cg-pine-warnings-head">⚠ {pineWarnings.length} feature{pineWarnings.length === 1 ? "" : "s"} didn't translate:</div>
                  <ul>
                    {pineWarnings.map((u, i) => (
                      <li key={i}>{typeof u === "string" ? u : (u.feature || u.name || "feature") + (u.reason ? " — " + u.reason : "")}</li>
                    ))}
                  </ul>
                </div>
              )}

              <div className="cg-field">
                <label>AGENT_NAME <span className="opt">(optional)</span></label>
                <input value={name} onChange={e => setName(e.target.value)} placeholder="MY_PINE_STRAT" />
              </div>

              <FundingPicker
                sources={sources}
                sourceId={sourceId}
                onSourceChange={setSourceId}
                amount={fundAmount}
                onAmountChange={setFundAmount}
                max={maxFund}
              />

              {error && <div className="cg-modal-error">{error}</div>}

              <div className="cg-pine-actions">
                <button className="cg-cta-secondary" onClick={() => setPhase("pick")} disabled={phase === "submitting"}>← Edit script</button>
                <button
                  className="cg-cta-primary"
                  disabled={phase === "submitting" || !fundValid}
                  onClick={launch}
                >
                  {phase === "submitting" ? "LAUNCHING…" : "LAUNCH AGENT →"}
                </button>
              </div>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

/* ===============================================================
   PINE SCRIPT IMPORTER — paste a TradingView strategy, get back a
   strategy_state JSON via /strategy/translate-pine. Used by both the
   signup wizard and the AddAgentModal (under the "Pine Script" tab).
   The parent gets the translated JSON via onTranslated(...) and decides
   what to do with it (create agent, fill onboarding step, etc).
   =============================================================== */
function PineScriptImporter({ onTranslated, onCancel }) {
  const [source, setSource] = useState("");
  const [phase, setPhase] = useState("input");    // input | translating | review
  const [result, setResult] = useState(null);     // { strategy_state, unsupported_features }
  const [error, setError] = useState("");
  const [remaining, setRemaining] = useState(null);

  const canTranslate = source.trim().length >= 20 && phase !== "translating";

  async function translate() {
    setError(""); setPhase("translating");
    try {
      const res = await acSend("/strategy/translate-pine", { source: source.trim() });
      if (!res || !res.strategy_state) {
        throw new Error("missing strategy_state in response");
      }
      setResult(res);
      setRemaining(typeof res.translations_remaining_today === "number" ? res.translations_remaining_today : null);
      setPhase("review");
    } catch (e) {
      const msg = (e && e.message) || "translation failed";
      setError(_friendlyPineError(msg));
      setPhase("input");
    }
  }

  function useThis() {
    if (!result || !result.strategy_state) return;
    onTranslated(result.strategy_state, result.unsupported_features || []);
  }

  return (
    <div className="cg-pine-importer">
      {phase !== "review" && (
        <>
          <div className="cg-field">
            <label>PINE_SCRIPT_SOURCE <span className="opt">(paste the //@version=5 strategy from TradingView)</span></label>
            <textarea
              className="cg-textarea cg-pine-textarea"
              rows={10}
              spellCheck={false}
              value={source}
              onChange={e => setSource(e.target.value)}
              placeholder={"//@version=5\nstrategy(\"My RSI bounce\", overlay=false)\nrsi = ta.rsi(close, 14)\nlongCondition = ta.crossover(rsi, 30)\nif (longCondition) strategy.entry(\"L\", strategy.long)\n..."}
              disabled={phase === "translating"}
            />
          </div>

          <div className="cg-pine-foot">
            <div className="cg-pine-hint">
              Haiku translates RSI / MA-cross / volume / stop / TP. Custom indicators and multi-timeframe calls are flagged as unsupported.
            </div>
            <div className="cg-pine-actions">
              {onCancel && <button className="cg-cta-secondary" onClick={onCancel} disabled={phase === "translating"}>← Back</button>}
              <button className="cg-cta-primary" disabled={!canTranslate} onClick={translate}>
                {phase === "translating" ? "TRANSLATING…" : "TRANSLATE →"}
              </button>
            </div>
          </div>

          {error && <div className="cg-modal-error">{error}</div>}
        </>
      )}

      {phase === "review" && result && (
        <div className="cg-pine-review">
          <div className="cg-pine-review-eyebrow">// TRANSLATED_STRATEGY</div>

          {/* Quick summary so the user doesn't have to read the raw JSON */}
          <div className="cg-pine-summary">
            {result.strategy_state.leverage != null && (
              <div className="cg-pine-summary-row"><span>Leverage</span><span>{result.strategy_state.leverage}x</span></div>
            )}
            {result.strategy_state.markets && (
              <div className="cg-pine-summary-row"><span>Markets</span><span>{(result.strategy_state.markets || []).join(", ")}</span></div>
            )}
            {result.strategy_state.position_size_pct != null && (
              <div className="cg-pine-summary-row"><span>Position size</span><span>{result.strategy_state.position_size_pct}%</span></div>
            )}
            {result.strategy_state.stop_loss_pct != null && (
              <div className="cg-pine-summary-row"><span>Stop loss</span><span>{result.strategy_state.stop_loss_pct}%</span></div>
            )}
            {result.strategy_state.take_profit_pct != null && (
              <div className="cg-pine-summary-row"><span>Take profit</span><span>{result.strategy_state.take_profit_pct}%</span></div>
            )}
            {result.strategy_state.max_positions != null && (
              <div className="cg-pine-summary-row"><span>Max positions</span><span>{result.strategy_state.max_positions}</span></div>
            )}
          </div>

          {/* Full JSON for the curious */}
          <details className="cg-pine-raw">
            <summary>Show raw JSON</summary>
            <pre className="cg-pine-raw-pre">{JSON.stringify(result.strategy_state, null, 2)}</pre>
          </details>

          {/* Unsupported features — honest accounting of what didn't translate */}
          {Array.isArray(result.unsupported_features) && result.unsupported_features.length > 0 && (
            <div className="cg-pine-warnings">
              <div className="cg-pine-warnings-head">⚠ Couldn't translate {result.unsupported_features.length} feature{result.unsupported_features.length === 1 ? "" : "s"}:</div>
              <ul>
                {result.unsupported_features.map((u, i) => (
                  <li key={i}>{typeof u === "string" ? u : (u.feature || u.name || "feature") + (u.reason ? " — " + u.reason : "")}</li>
                ))}
              </ul>
            </div>
          )}

          {remaining != null && (
            <div className="cg-pine-quota">{remaining} translation{remaining === 1 ? "" : "s"} left today</div>
          )}

          <div className="cg-pine-actions">
            <button className="cg-cta-secondary" onClick={() => { setPhase("input"); setResult(null); }}>← Edit script</button>
            <button className="cg-cta-primary" onClick={useThis}>USE THIS STRATEGY →</button>
          </div>
        </div>
      )}
    </div>
  );
}

// Map server errors to something a human can act on. We only inspect the
// shape of `send_failed_N` strings; specific 429 / 503 / 502 each get
// their own line.
function _friendlyPineError(raw) {
  if (/429/.test(raw)) return "Daily translation limit reached — try again tomorrow.";
  if (/503/.test(raw)) return "Translator is not configured on this server. Contact support.";
  if (/504/.test(raw)) return "Translator took too long. Try a shorter script.";
  if (/502/.test(raw)) return "Translator response wasn't usable. Try a simpler script.";
  return "Translation failed — try again.";
}

/* FundingPicker — surfaces the FUND_FROM dropdown + AMOUNT input the
   AddAgentModal uses on both the preset and Pine Script paths. Kept
   separate so the preset and pine modals don't drift on this UX.

   The "lobby" option is preserved (even though it's $0 for everyone
   who signed up via the campaign flow) so legacy users / future flows
   that put seed in the lobby still work. The button that submits the
   modal stays disabled when amount > available, with a hint underneath
   showing how much room is left. */
function FundingPicker({ sources, sourceId, onSourceChange, amount, onAmountChange, max }) {
  const insufficient = max > 0 && amount > max;
  return (
    <>
      <div className="cg-field">
        <label>FUND_FROM <span className="opt">(pull this agent's starting capital from another pot)</span></label>
        <select
          className="cg-pine-textarea"
          style={{ minHeight: 0, padding: "10px 12px", fontFamily: "inherit" }}
          value={sourceId}
          onChange={e => onSourceChange(e.target.value)}
        >
          {sources.length === 0 && <option value="">Loading…</option>}
          {sources.map(s => (
            <option key={s.id || "lobby"} value={s.id}>{s.label}</option>
          ))}
        </select>
      </div>

      <div className="cg-field">
        <label>STARTING_BALANCE <span className="opt">(USDT — debited from the source above)</span></label>
        <input
          type="number"
          min={1}
          max={max || undefined}
          step={10}
          value={amount}
          onChange={e => onAmountChange(Number(e.target.value) || 0)}
        />
        <div style={{ fontSize: 11, color: insufficient ? "var(--cg-neg, #FF6B6B)" : "var(--cg-muted)", marginTop: 4 }}>
          {insufficient
            ? `Only $${max.toLocaleString(undefined, { maximumFractionDigits: 2 })} available on that source.`
            : `Up to $${max.toLocaleString(undefined, { maximumFractionDigits: 2 })} available.`}
        </div>
      </div>
    </>
  );
}

/* Local fallback when /agents/presets isn't reachable (offline / stub mode).
   Mirrors the four presets so the picker remains usable. */
const _LOCAL_PRESET_FALLBACK = [
  { key: "low",       name_prefix: "CIPHER", leverage: 2,  leverage_label: "2x",  target_tier: "elite",
    markets: ["BTC-PERP"], stop_loss_pct: 1.5, take_profit_pct: 4.0, min_trades_per_day: 1,
    personality: "Disciplined capital-preservation trader at 2x on BTC only.", blowup_warning: false },
  { key: "medium",    name_prefix: "AXIOM",  leverage: 5,  leverage_label: "5x",  target_tier: "qualified",
    markets: ["BTC-PERP", "ETH-PERP"], stop_loss_pct: 1.2, take_profit_pct: 3.5, min_trades_per_day: 2,
    personality: "Balanced systematic trader at 5x on BTC + ETH.", blowup_warning: false },
  { key: "high",      name_prefix: "DELTA",  leverage: 10, leverage_label: "10x", target_tier: "emerging",
    markets: ["BTC-PERP", "ETH-PERP", "SOL-PERP"], stop_loss_pct: 0.8, take_profit_pct: 2.5, min_trades_per_day: 4,
    personality: "Aggressive momentum trader at 10x on BTC + ETH + SOL.", blowup_warning: false },
  { key: "very_high", name_prefix: "SURGE",  leverage: 20, leverage_label: "20x", target_tier: "speculative",
    markets: ["BTC-PERP", "ETH-PERP", "SOL-PERP", "ARB-PERP", "AVAX-PERP"], stop_loss_pct: 0.5, take_profit_pct: 1.5, min_trades_per_day: 8,
    personality: "Maximum-aggression high-velocity trader at 20x across 5 markets.", blowup_warning: true },
];

/* ===============================================================
   MY AGENT TAB
   =============================================================== */

/* ===============================================================
   STRATEGY COACH — chat modal that critiques + suggests personality
   =============================================================== */
function StrategyCoachModal({ user, agentId, onClose, onApplyPersonality }) {
  const [history, setHistory]   = useState(null);
  const [draft, setDraft]       = useState("");
  const [sending, setSending]   = useState(false);
  const [error, setError]       = useState("");
  const scrollRef = useRef(null);
  const toast = useToast && useToast();

  function acJwt()  { try { return window.AC_JWT || localStorage.getItem("agc_jwt") || null; } catch (_) { return null; } }
  function acBase() { return ((typeof window !== "undefined" && window.AC_API) || "").replace(/\/$/, ""); }

  useEffect(() => {
    if (!agentId) return;
    let cancelled = false;
    (async () => {
      try {
        const jwt = acJwt(); const base = acBase();
        if (!jwt || !base) return;
        const r = await fetch(`${base}/agents/${agentId}/coach?limit=50`,
          { headers: { Authorization: "Bearer " + jwt }, credentials: "omit" });
        if (!r.ok) { setHistory([]); return; }
        const data = await r.json();
        if (!cancelled) setHistory(data.messages || []);
      } catch (_) { if (!cancelled) setHistory([]); }
    })();
    return () => { cancelled = true; };
  }, [agentId]);

  useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [history && history.length, sending]);

  async function send() {
    const txt = draft.trim();
    if (!txt || sending) return;
    setSending(true); setError("");
    // Optimistic insert of the user message
    const userMsg = { role: "user", content: txt, _local: true };
    setHistory(h => [...(h || []), userMsg]);
    setDraft("");
    try {
      const jwt = acJwt(); const base = acBase();
      const r = await fetch(`${base}/agents/${agentId}/coach`, {
        method:  "POST",
        headers: { "Content-Type": "application/json", Authorization: "Bearer " + jwt },
        credentials: "omit",
        body:    JSON.stringify({ message: txt }),
      });
      if (!r.ok) {
        let detail = "Coach is unavailable";
        try { const j = await r.json(); detail = j.detail || detail; } catch (_) {}
        throw new Error(detail);
      }
      const reply = await r.json();
      setHistory(h => [...(h || []), reply]);
    } catch (e) {
      setError(e.message || "Coach is unavailable");
      // Remove the optimistic user message — request didn't go through
      setHistory(h => (h || []).filter(m => !m._local));
    } finally {
      setSending(false);
    }
  }

  async function applySuggestion(suggestion) {
    if (!suggestion) return;
    try {
      const jwt = acJwt(); const base = acBase();
      const r = await fetch(`${base}/agents/${agentId}`, {
        method:  "PUT",
        headers: { "Content-Type": "application/json", Authorization: "Bearer " + jwt },
        credentials: "omit",
        body:    JSON.stringify({ personality: suggestion }),
      });
      if (!r.ok) throw new Error("save_failed");
      toast && toast.push({ title: "Personality updated", meta: "Agent reloads on its next strategy review (~1 hour)", duration: 4000 });
      if (onApplyPersonality) onApplyPersonality(suggestion);
    } catch (_) {
      toast && toast.push({ title: "Couldn't apply suggestion", meta: "Try saving manually", duration: 4000 });
    }
  }

  return (
    <div className="cg-modal-backdrop" onClick={onClose}>
      <div className="cg-modal" style={{ width: 640, maxWidth: "94vw", maxHeight: "90vh", display: "flex", flexDirection: "column" }} onClick={(e) => e.stopPropagation()}>
        <div className="cg-modal-head">
          <div>
            <div className="cg-modal-eyebrow">// STRATEGY_COACH · CLAUDE_HAIKU_4.5</div>
            <div className="cg-modal-title">Coach my agent</div>
          </div>
          <button className="cg-modal-x" onClick={onClose}>×</button>
        </div>

        <div ref={scrollRef} style={{ flex: 1, overflowY: "auto", padding: "12px 16px", borderTop: "1px solid var(--border)", borderBottom: "1px solid var(--border)", minHeight: 280 }}>
          {history == null && (
            <div className="muted" style={{ padding: 24, textAlign: "center" }}>Loading…</div>
          )}
          {history && history.length === 0 && (
            <div className="muted" style={{ padding: 24, textAlign: "center", fontStyle: "italic", lineHeight: 1.6 }}>
              Tell the coach what kind of trader you want to build.<br/>
              "I want to trade only BTC, conservative, low drawdown" — and the coach will critique +
              suggest a personality you can apply with one click.
            </div>
          )}
          {history && history.map((m, i) => (
            <div key={m.id || i} style={{ display: "flex", marginBottom: 14, flexDirection: m.role === "user" ? "row-reverse" : "row" }}>
              <div style={{
                maxWidth: "78%",
                padding: "10px 14px",
                borderRadius: 8,
                background: m.role === "user" ? "rgba(29,158,117,0.10)" : "var(--bg)",
                border: "1px solid " + (m.role === "user" ? "rgba(29,158,117,0.30)" : "var(--border)"),
                fontSize: 13,
                lineHeight: 1.55,
              }}>
                <div className="muted mono" style={{ fontSize: 10, marginBottom: 4, letterSpacing: 1 }}>
                  {m.role === "user" ? "// YOU" : "// COACH"}
                </div>
                <div style={{ whiteSpace: "pre-wrap" }}>{m.content}</div>
                {m.suggested_personality && (
                  <div style={{ marginTop: 10, padding: 10, borderRadius: 6, background: "rgba(29,158,117,0.06)", border: "1px dashed var(--accent)" }}>
                    <div className="muted mono" style={{ fontSize: 10, marginBottom: 4, letterSpacing: 1 }}>// SUGGESTED PERSONALITY</div>
                    <div style={{ fontSize: 12, lineHeight: 1.55, marginBottom: 8 }}>{m.suggested_personality}</div>
                    <button
                      className="cg-cta-primary"
                      style={{ padding: "6px 12px", fontSize: 11 }}
                      onClick={() => applySuggestion(m.suggested_personality)}
                    >
                      ✓ APPLY THIS PERSONALITY
                    </button>
                  </div>
                )}
              </div>
            </div>
          ))}
          {sending && (
            <div style={{ display: "flex", marginBottom: 14 }}>
              <div className="muted" style={{ padding: "10px 14px", borderRadius: 8, background: "var(--bg)", border: "1px solid var(--border)", fontSize: 12 }}>
                <span className="cg-typing">Coach is thinking…</span>
              </div>
            </div>
          )}
        </div>

        {error && (
          <div style={{ padding: "8px 16px", fontSize: 12, color: "var(--err, #DC2626)", background: "rgba(220,38,38,0.06)" }}>
            {error}
          </div>
        )}

        <div style={{ padding: 12, display: "flex", gap: 8 }}>
          <textarea
            value={draft}
            onChange={(e) => setDraft(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); send(); }
            }}
            placeholder="Describe what you want, or ask the coach to critique your current personality…"
            rows={3}
            style={{ flex: 1, resize: "none", padding: 10, borderRadius: 6, border: "1px solid var(--border)", background: "var(--bg)", color: "var(--fg)", fontSize: 13, fontFamily: "inherit" }}
          />
          <button
            className="cg-cta-primary"
            disabled={sending || !draft.trim()}
            onClick={send}
            style={{ alignSelf: "stretch", padding: "0 16px" }}
          >
            {sending ? "…" : "SEND"}
          </button>
        </div>
        <div className="muted mono" style={{ padding: "0 12px 10px", fontSize: 10 }}>
          // Tip: ⌘/Ctrl+Enter to send · The coach reads your agent's stats + current personality on every message
        </div>
      </div>
    </div>
  );
}


/* ===============================================================
   KYA BADGE — shows the agent's Billions Network / OpenClaw identifier.
   Compact: "kya:0xab12…3f4d · assigned". Status tooltip explains the
   verification state. Returns null when the agent has no kya_id yet
   (legacy rows pre-2026-06-25 migration).
   =============================================================== */
function KyaBadge({ id, status }) {
  if (!id) return null;
  const short = id.length > 12 ? `${id.slice(0, 6)}…${id.slice(-4)}` : id;
  const palette = {
    assigned: { fg: "var(--cg-muted)", dot: "var(--amber, #d29022)", label: "ID assigned — human-link verification pending" },
    verified: { fg: "var(--accent)",   dot: "var(--accent)",          label: "Verified by Billions / OpenClaw — face-scan linkage on-chain" },
    unassigned: { fg: "var(--cg-muted)", dot: "var(--cg-muted)",      label: "No KYA identity yet" },
  }[status] || { fg: "var(--cg-muted)", dot: "var(--cg-muted)", label: "" };

  function copy() {
    try { navigator.clipboard && navigator.clipboard.writeText(id); } catch (_) {}
  }

  return (
    <div
      title={palette.label}
      onClick={copy}
      style={{
        display: "inline-flex", alignItems: "center", gap: 6,
        fontSize: 10, letterSpacing: 0.5,
        color: palette.fg,
        fontFamily: "JetBrains Mono, monospace",
        cursor: "pointer",
        padding: "2px 0",
        marginBottom: 6,
      }}
    >
      <span style={{ width: 6, height: 6, borderRadius: "50%", background: palette.dot, display: "inline-block" }} />
      <span>kya:{short}</span>
      <span style={{ color: "var(--cg-muted)" }}>·</span>
      <span style={{ textTransform: "uppercase", letterSpacing: 1 }}>{status}</span>
    </div>
  );
}


/* ===============================================================
   RISK BANNER — shown when agent.risk_state is set
   =============================================================== */
function RiskBanner({ agent }) {
  const rs = agent && agent.risk_state;
  if (!rs) return null;

  const palette = {
    soft_cut: { border: "rgba(210,144,34,0.45)", bg: "rgba(210,144,34,0.06)", fg: "var(--amber, #d29022)",
                title: "⚠ SOFT CUT · Position sizing reduced",
                body:  `${agent.name} hit a daily drawdown above 5%. New positions are sized at 50% until the drawdown recovers or the UTC day rolls. Existing positions are unaffected — your strategy keeps running, just smaller.` },
    hard_cut: { border: "rgba(177,59,59,0.55)", bg: "rgba(177,59,59,0.08)", fg: "var(--red, #b13b3b)",
                title: "🛑 HARD CUT · Positions closed",
                body:  `${agent.name} hit a daily drawdown above 7.5%. All open positions were force-closed and no new trades will open until UTC midnight, when the daily limit resets.` },
    halted:   { border: "var(--red, #b13b3b)", bg: "rgba(177,59,59,0.12)", fg: "var(--red, #b13b3b)",
                title: "⚠ NO CREDIT · Trailing-floor breach",
                body:  `${agent.name} hit a trailing drawdown above 12% — the absolute risk floor. This agent can no longer qualify for a credit line, but it keeps trading under the risk engine's cuts (position sizing halved on soft-cut days, no new entries on hard-cut days until UTC roll). The data still flows to the leaderboard and to model training. If you'd rather redeploy the remaining $${Number(agent.paper_balance || 0).toFixed(2)} into a fresh agent, use Dashboard → MOVE CAPITAL.` },
  }[rs];

  if (!palette) return null;
  const reason = agent.risk_state_reason;
  const at     = agent.risk_state_at;

  return (
    <div style={{
      border: "1px solid " + palette.border,
      background: palette.bg,
      padding: "14px 18px",
      marginBottom: 16,
      fontFamily: "JetBrains Mono, monospace",
    }}>
      <div style={{ fontSize: 12, color: palette.fg, letterSpacing: 1.2, fontWeight: 700, marginBottom: 6 }}>
        {palette.title}
      </div>
      <div style={{ fontSize: 12, color: "var(--fg)", lineHeight: 1.6, marginBottom: reason ? 8 : 0 }}>
        {palette.body}
      </div>
      {reason && (
        <div style={{ fontSize: 10, color: "var(--cg-muted)", letterSpacing: 0.5, lineHeight: 1.5 }}>
          {reason}{at ? ` · ${new Date(at).toLocaleString()}` : ""}
        </div>
      )}
    </div>
  );
}


/* ===============================================================
   STRATEGY JOURNAL PANEL
   Renders campaign.strategy_journal for a single agent — the
   chronological "what my agent has been doing and thinking" narrative.
   Kinds carry different visual treatment:
     - design    → initial mandate + first proposal (once, at signup)
     - review    → nightly self-reflection with assessment pill
     - mutation  → strategy change outside a review (future)
     - user_note → user-authored input via coach chat (future)
   =============================================================== */


/* ===============================================================
   EXPORTS
   =============================================================== */
Object.assign(window, {
  ReallocateCapitalModal, AddAgentModal, PineScriptImporter,
  FundingPicker, StrategyCoachModal, KyaBadge, RiskBanner,
});
