// ====== Agent editor ======
//
// PR 18: sticky bottom SaveBar gives the user explicit feedback that their
// edits were saved. The auto-save path (parent's onUpdate on every keystroke)
// stays for keystroke-level safety; the bar shows status + offers a manual
// "Save" click so users who expect a button see one. Closes bug.md P1
// "/manage Agent 管理页没有底部 Save 按钮 / 用户无法判断修改是否已经保存".

function relTime(ts) {
  if (!ts) return "";
  const s = Math.max(0, Math.floor((Date.now() - ts) / 1000));
  if (s < 5) return "just now";
  if (s < 60) return `${s}s ago`;
  if (s < 3600) return `${Math.floor(s / 60)}m ago`;
  return `${Math.floor(s / 3600)}h ago`;
}

function SaveBar({ saveState, onSave, extraButtons }) {
  // tick once per second so the "Saved Xs ago" pill stays fresh without
  // requiring the parent to re-render on a timer.
  const [, setTick] = React.useState(0);
  React.useEffect(() => {
    const id = setInterval(() => setTick((n) => n + 1), 1000);
    return () => clearInterval(id);
  }, []);

  const st = saveState || { status: "idle" };
  const status = st.status;
  let pill;
  if (status === "saving") {
    pill = <span className="save-pill saving"><span className="dot"></span> Saving…</span>;
  } else if (status === "dirty") {
    pill = <span className="save-pill idle">Unsaved changes</span>;
  } else if (status === "saved") {
    pill = <span className="save-pill saved"><Icon name="check" size={11} /> Saved {relTime(st.lastSavedAt)}</span>;
  } else if (status === "error") {
    pill = <span className="save-pill err" title={st.error || ""}><Icon name="x" size={11} /> Save failed</span>;
  } else if (status === "readonly") {
    pill = <span className="save-pill ro">Read-only</span>;
  } else {
    pill = <span className="save-pill idle">No unsaved changes</span>;
  }

  const disabled = status === "saving" || status === "readonly";
  return (
    <div className="save-bar">
      {pill}
      <div style={{ flex: 1 }} />
      <button
        className="btn primary"
        onClick={onSave}
        disabled={disabled}
        title="Force-save current state"
      >
        <Icon name="check" size={12} /> Save
      </button>
      {extraButtons}
    </div>
  );
}

// expose for SkillEditor (sibling file) — same component, different button slot
window.SaveBar = SaveBar;
window.relTime = relTime;


function PickerPopover({ open, onClose, items, selectedIds, onToggle, groupLabel }) {
  const [q, setQ] = React.useState("");
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const fn = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose(); };
    document.addEventListener("mousedown", fn);
    return () => document.removeEventListener("mousedown", fn);
  }, [open, onClose]);
  if (!open) return null;
  const filt = items.filter((it) => !q || it.name.toLowerCase().includes(q.toLowerCase()) || (it.description||"").toLowerCase().includes(q.toLowerCase()));
  return (
    <div className="picker-pop" ref={ref}>
      <div className="search">
        <span className="ic"><Icon name="search" size={12} /></span>
        <input autoFocus value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search…" />
      </div>
      <div className="group-label">{groupLabel}</div>
      <div style={{ maxHeight: 280, overflowY: "auto" }}>
        {filt.length === 0 ? (
          <div style={{ padding: "8px", color: "var(--text-muted)", fontSize: 12 }}>No matches</div>
        ) : filt.map((it, i) => {
          const on = selectedIds.includes(it.id);
          return (
            <div key={`${it.skill_id || it.id}-${i}`} className={"opt" + (on ? " on" : "")} onClick={() => onToggle(it.id)}>
              <span className="ic" style={{ background: it.color }}>{it.initials}</span>
              <div style={{ minWidth: 0, flex: 1 }}>
                <div>{it.name}</div>
                <div className="ds">{it.description || it.desc}</div>
              </div>
              <span className="check"><Icon name="check" size={12} /></span>
            </div>
          );
        })}
      </div>
    </div>
  );
}

const REMOTE_SKILL_PAGE_SIZE = 50;
const REMOTE_SKILL_SEARCH_DEBOUNCE_MS = 300;
const REMOTE_SKILL_SCROLL_THRESHOLD_PX = 120;

function mergeUniqueSkillItems(current, incoming) {
  const byId = new Map();
  [...(current || []), ...(incoming || [])].forEach((item) => {
    const id = item.skill_id || item.id;
    if (id) byId.set(id, item);
  });
  return [...byId.values()];
}

function RemoteSkillPickerPopover({
  open,
  onClose,
  selectedIds,
  onToggle,
  loadSkillPage,
  groupLabel,
}) {
  const [query, setQuery] = React.useState("");
  const [items, setItems] = React.useState([]);
  const [cursor, setCursor] = React.useState(null);
  const [hasMore, setHasMore] = React.useState(false);
  const [loading, setLoading] = React.useState(false);
  const [loadingMore, setLoadingMore] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [pendingSkillId, setPendingSkillId] = React.useState(null);
  const ref = React.useRef(null);
  const listRef = React.useRef(null);
  const requestSeqRef = React.useRef(0);
  const skipQueryEffectRef = React.useRef(false);
  const loadingMoreRef = React.useRef(false);

  async function loadFirstPage(requestedQuery) {
    const requestSeq = ++requestSeqRef.current;
    setItems([]);
    setCursor(null);
    setHasMore(false);
    setError(null);
    setLoading(true);
    setLoadingMore(false);
    loadingMoreRef.current = false;
    if (listRef.current) listRef.current.scrollTop = 0;
    try {
      const response = await loadSkillPage({
        cursor: null,
        limit: REMOTE_SKILL_PAGE_SIZE,
        q: requestedQuery.trim(),
        sort: "name",
      });
      if (requestSeq !== requestSeqRef.current) return;
      setItems(mergeUniqueSkillItems([], response?.items));
      setCursor(response?.next_cursor || null);
      setHasMore(!!response?.has_more);
    } catch (err) {
      if (requestSeq !== requestSeqRef.current) return;
      setError(err?.message || String(err));
    } finally {
      if (requestSeq === requestSeqRef.current) setLoading(false);
    }
  }

  async function loadNextPage() {
    if (!open || loading || loadingMoreRef.current || !hasMore || !cursor) return;
    const requestSeq = ++requestSeqRef.current;
    loadingMoreRef.current = true;
    setLoadingMore(true);
    setError(null);
    try {
      const response = await loadSkillPage({
        cursor,
        limit: REMOTE_SKILL_PAGE_SIZE,
        q: query.trim(),
        sort: "name",
      });
      if (requestSeq !== requestSeqRef.current) return;
      setItems((current) => mergeUniqueSkillItems(current, response?.items));
      setCursor(response?.next_cursor || null);
      setHasMore(!!response?.has_more);
    } catch (err) {
      if (requestSeq !== requestSeqRef.current) return;
      setError(err?.message || String(err));
    } finally {
      if (requestSeq === requestSeqRef.current) {
        loadingMoreRef.current = false;
        setLoadingMore(false);
      }
    }
  }

  React.useEffect(() => {
    if (!open) {
      requestSeqRef.current += 1;
      setQuery("");
      setItems([]);
      setCursor(null);
      setHasMore(false);
      setLoading(false);
      setLoadingMore(false);
      loadingMoreRef.current = false;
      setError(null);
      setPendingSkillId(null);
      return;
    }
    skipQueryEffectRef.current = true;
    loadFirstPage("");
  }, [open]);

  React.useEffect(() => {
    if (!open) return;
    if (skipQueryEffectRef.current) {
      skipQueryEffectRef.current = false;
      return;
    }
    const timer = setTimeout(() => {
      loadFirstPage(query);
    }, REMOTE_SKILL_SEARCH_DEBOUNCE_MS);
    return () => clearTimeout(timer);
  }, [query, open]);

  React.useEffect(() => {
    if (!open) return;
    const closeOnOutsideClick = (event) => {
      if (ref.current && !ref.current.contains(event.target)) onClose();
    };
    document.addEventListener("mousedown", closeOnOutsideClick);
    return () => document.removeEventListener("mousedown", closeOnOutsideClick);
  }, [open, onClose]);

  if (!open) return null;

  const onScroll = (event) => {
    const { scrollHeight, scrollTop, clientHeight } = event.currentTarget;
    if (
      scrollHeight - scrollTop - clientHeight
      < REMOTE_SKILL_SCROLL_THRESHOLD_PX
    ) {
      loadNextPage();
    }
  };

  const retry = () => {
    if (items.length && cursor) loadNextPage();
    else loadFirstPage(query);
  };

  const toggleSkill = async (item) => {
    const id = item.skill_id || item.id;
    if (!id || pendingSkillId) return;
    setPendingSkillId(id);
    try {
      await onToggle(item);
    } finally {
      setPendingSkillId(null);
    }
  };

  return (
    <div className="picker-pop" ref={ref}>
      <div className="search">
        <span className="ic"><Icon name="search" size={12} /></span>
        <input
          autoFocus
          value={query}
          onChange={(event) => {
            requestSeqRef.current += 1;
            setItems([]);
            setCursor(null);
            setHasMore(false);
            setError(null);
            setLoading(true);
            setLoadingMore(false);
            loadingMoreRef.current = false;
            setQuery(event.target.value);
          }}
          placeholder="Search…"
        />
      </div>
      <div className="group-label">{groupLabel}</div>
      <div ref={listRef} onScroll={onScroll} style={{ maxHeight: 280, overflowY: "auto" }}>
        {loading && items.length === 0 ? (
          <div style={{ padding: "10px", color: "var(--text-muted)", fontSize: 12 }}>Loading skills…</div>
        ) : error && items.length === 0 ? (
          <div style={{ padding: "10px", color: "var(--text-muted)", fontSize: 12 }}>
            <div>Load skills failed</div>
            <button className="btn sm" style={{ marginTop: 8 }} onClick={retry}>Retry</button>
          </div>
        ) : items.length === 0 ? (
          <div style={{ padding: "8px", color: "var(--text-muted)", fontSize: 12 }}>No matches</div>
        ) : (
          <>
            {items.map((item, index) => {
              const id = item.skill_id || item.id;
              const on = selectedIds.includes(id);
              const unavailable = !on && !item.latest_version;
              const pending = pendingSkillId === id;
              return (
                <div
                  key={`${id}-${index}`}
                  className={"opt" + (on ? " on" : "")}
                  aria-disabled={unavailable || pending}
                  style={unavailable || pending ? { opacity: 0.55, cursor: "default" } : undefined}
                  onClick={() => { if (!unavailable && !pending) toggleSkill(item); }}
                >
                  <span className="ic" style={{ background: item.color }}>{item.initials}</span>
                  <div style={{ minWidth: 0, flex: 1 }}>
                    <div>{item.name}</div>
                    <div className="ds">{item.description || item.desc}</div>
                  </div>
                  <span className="check"><Icon name={pending ? "clock" : "check"} size={12} /></span>
                </div>
              );
            })}
            {loadingMore && (
              <div style={{ padding: "8px", color: "var(--text-muted)", fontSize: 12, textAlign: "center" }}>Loading more…</div>
            )}
            {error && (
              <div style={{ padding: "8px", textAlign: "center" }}>
                <button className="btn sm" onClick={retry}>Retry</button>
              </div>
            )}
          </>
        )}
      </div>
    </div>
  );
}

function uniquePickerItems(items) {
  const seen = new Set();
  const out = [];
  for (const item of items || []) {
    const key = item.id || item.skill_id || item.name;
    if (!key || seen.has(key)) continue;
    seen.add(key);
    out.push(item);
  }
  return out;
}

const PLATFORM_BUILTIN_TOOLS = [
  "publish_artifact",
  "list_skills",
  "load_skill",
  "load_skill_resource",
  "run_skill_script",
  "skill_read_file",
  "skill_append_file",
  "skill_write_file",
  "skill_list_files",
  "create_cron_schedule",
  "list_cron_schedules",
  "get_cron_schedule",
  "pause_cron_schedule",
  "resume_cron_schedule",
  "delete_cron_schedule",
];
const ADK_ENVIRONMENT_TOOLS = ["Execute", "ReadFile", "WriteFile", "EditFile"];
const APP_GENERIC_TOOL_NAMES = [
  "schema.create_table",
  "schema.list_tables",
  "data.create",
  "data.list",
  "data.get",
  "data.update",
  "data.delete",
];
const APP_OPERATIONS = ["list", "get", "create", "update", "delete"];

function appToolNames(app) {
  const names = APP_GENERIC_TOOL_NAMES.map((name) => `${app.app_id}.${name}`);
  const tables = app.manifest?.tables || {};
  Object.entries(tables).forEach(([resource, table]) => {
    (table.operations || APP_OPERATIONS).forEach((operation) => {
      names.push(`${app.app_id}.${resource}.${operation}`);
    });
  });
  return [...new Set(names)];
}

function mcpToolNames(mcp) {
  return (mcp.tools || [])
    .map((tool) => typeof tool === "string" ? tool : tool.name)
    .filter(Boolean);
}

function AgentEditor({
  agent,
  skills,
  mcps,
  localTools = [],
  apps = [],
  loadSkillPage,
  onUpdate,
  onInstallSkillTemplate,
  onDelete,
  saveState,
  onSave,
}) {
  const [skillPickerOpen, setSkillPickerOpen] = React.useState(false);
  const [mcpPickerOpen, setMcpPickerOpen] = React.useState(false);
  const [localToolPickerOpen, setLocalToolPickerOpen] = React.useState(false);

  const installedSkillItems = uniquePickerItems(skills);
  const mcpItems = uniquePickerItems(mcps);
  const localToolItems = uniquePickerItems(localTools);
  const installedById = new Map([
    ...installedSkillItems.map((s) => [s.id, s]),
    ...((agent.skills || []).map((s) => [s.id || s.skill_id, s])),
  ]);
  const agentSkillTemplateIdByInstanceId = new Map(
    (agent.skills || [])
      .filter((s) => (s.id || s.skill_id) && s.forked_from?.template_id)
      .map((s) => [s.id || s.skill_id, s.forked_from.template_id])
  );
  const templateIdToAgentInstanceId = new Map(
    (agent.skills || [])
      .filter((s) => (s.id || s.skill_id) && s.forked_from?.template_id)
      .map((s) => [s.forked_from.template_id, s.id || s.skill_id])
  );
  const selectedSkillTemplateIds = (agent.enabledSkills || [])
    .map((id) => agentSkillTemplateIdByInstanceId.get(id))
    .filter(Boolean);
  const enabledSkills = (agent.enabledSkills || []).map((id) => (
    installedById.get(id) || {
      id,
      name: id,
      description: "Agent-owned skill instance",
      color: "var(--accent)",
      initials: "SK",
    }
  ));
  const enabledMCPs = mcpItems.filter((m) => agent.enabledMCPs.includes(m.id));
  const executorApps = apps.filter((app) => app.executor_agent_id === agent.id);
  const appTools = executorApps.flatMap((app) => appToolNames(app));
  const selectedLocalToolNames = agent.enabledLocalTools || agent.localToolNames || [];
  const enabledLocalTools = localToolItems.filter((t) => selectedLocalToolNames.includes(t.id));
  const toggleSkill = (id) => {
    const cur = agent.enabledSkills;
    onUpdate({ enabledSkills: cur.includes(id) ? cur.filter((x) => x !== id) : [...cur, id] });
  };
  const installSkillTemplate = async (template) => {
    const templateId = template.skill_id || template.id;
    const instanceId = templateIdToAgentInstanceId.get(templateId);
    if (instanceId) {
      onUpdate({ enabledSkills: (agent.enabledSkills || []).filter((id) => id !== instanceId) });
      return;
    }
    if (!onInstallSkillTemplate) return;
    const version = template.latest_version || template.version;
    if (!version) return;
    await onInstallSkillTemplate(templateId, version);
  };
  const toggleMCP = (id) => {
    const cur = agent.enabledMCPs;
    onUpdate({ enabledMCPs: cur.includes(id) ? cur.filter((x) => x !== id) : [...cur, id] });
  };
  const toggleLocalTool = (id) => {
    const cur = selectedLocalToolNames;
    onUpdate({ enabledLocalTools: cur.includes(id) ? cur.filter((x) => x !== id) : [...cur, id] });
  };

  return (
    <div className="detail">
      <div className="detail-head">
        <div className="av" style={{ background: agent.color }}>{agent.initials}</div>
        <div className="titles">
          <h1>{agent.name}</h1>
          <div className="sub">agent · <span style={{ color: "var(--text)" }}>{agent.id}</span> · model {agent.model}</div>
        </div>
        <div className="actions">
          <button className="btn danger" onClick={onDelete}><Icon name="trash" size={12} /> Delete</button>
        </div>
      </div>

      <div className="detail-body">
        <div className="form-grid">
          <div className="card-section">
            <div className="head"><h3>Identity</h3></div>
            <div className="body padded" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
              <div className="field-lg">
                <label>Agent name</label>
                <input
                  className="input-lg"
                  value={agent.name}
                  onChange={(e) => onUpdate({ name: e.target.value, initials: e.target.value.split(" ").map((w) => w[0]).slice(0, 2).join("").toUpperCase() || "A" })}
                />
              </div>
              <div className="field-lg">
                <label>
                  Max model rounds
                  <span className="hint">blank uses runtime default</span>
                </label>
                <input
                  className="input-lg"
                  type="number"
                  min="1"
                  step="1"
                  value={agent.maxAgentRounds || ""}
                  placeholder="20"
                  onChange={(e) => {
                    const raw = e.target.value;
                    const next = raw === "" ? null : Math.max(1, parseInt(raw, 10) || 1);
                    onUpdate({ maxAgentRounds: next });
                  }}
                />
              </div>
              <div className="field-lg" style={{ gridColumn: "1 / -1" }}>
                <label>Short description</label>
                <input
                  className="input-lg"
                  value={agent.desc}
                  placeholder="Describe what this agent does"
                  onChange={(e) => onUpdate({ desc: e.target.value })}
                />
              </div>
            </div>
          </div>

          <div className="card-section">
            <div className="head">
              <h3>Default model configuration</h3>
              <span className="desc">Saved on this Agent and used by every new run</span>
            </div>
            <div className="body padded" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
              <div className="field-lg">
                <label>Provider</label>
                <select
                  className="input-lg"
                  value={agent.modelProvider || "baizhi"}
                  onChange={(e) => {
                    const provider = e.target.value;
                    const model = defaultModelForProvider(provider);
                    onUpdate({
                      modelProvider: provider,
                      model,
                      max_tokens: Math.min(agent.max_tokens, modelContextWindow(model)),
                    });
                  }}
                >
                  {(window.MODEL_PROVIDERS || []).map((item) => (
                    <option key={item.id} value={item.id}>{item.label}</option>
                  ))}
                </select>
              </div>
              <div className="field-lg">
                <label>
                  Model
                  <span style={{ color: "var(--text-muted)", fontWeight: 400, fontSize: 11, marginLeft: 6 }}>
                    window {modelContextWindow(agent.model).toLocaleString()}
                  </span>
                </label>
                <input
                  className="input-lg"
                  value={agent.model || ""}
                  onChange={(e) => {
                    const model = e.target.value;
                    const provider = modelProviderFor(model);
                    onUpdate({
                      model,
                      ...(provider ? { modelProvider: provider } : {}),
                      max_tokens: Math.min(agent.max_tokens, modelContextWindow(model)),
                    });
                  }}
                  list="manage-model-options"
                  placeholder={defaultModelForProvider(agent.modelProvider || "baizhi")}
                />
                <datalist id="manage-model-options">
                  {modelOptionsForProvider(agent.modelProvider || "baizhi").map((m) => <option key={m.id} value={m.id}>{m.label}</option>)}
                </datalist>
              </div>
              <div className="field-lg">
                <label>Endpoint <span className="hint">Server managed</span></label>
                <input className="input-lg" value={modelEndpointForProvider(agent.modelProvider)} readOnly />
              </div>
              <div className="field-lg">
                <label>Credential <span className="hint">Server managed</span></label>
                <input className="input-lg" value="Server managed" readOnly />
              </div>
              <div className="field-lg">
                <label>Temperature <span className="hint">{agent.temperature.toFixed(2)}</span></label>
                <input type="range" min="0" max="2" step="0.05" value={agent.temperature} onChange={(e) => onUpdate({ temperature: parseFloat(e.target.value) })} />
              </div>
              <div className="field-lg">
                <label>Top P <span className="hint">{agent.top_p.toFixed(2)}</span></label>
                <input type="range" min="0" max="1" step="0.05" value={agent.top_p} onChange={(e) => onUpdate({ top_p: parseFloat(e.target.value) })} />
              </div>
              <div className="field-lg" style={{ gridColumn: "1 / -1" }}>
                <label>Max output <span className="hint">{agent.max_tokens.toLocaleString()} / 409,600</span></label>
                <input type="range" min="256" max="409600" step="128" value={Math.min(agent.max_tokens, 409600)} onChange={(e) => onUpdate({ max_tokens: parseInt(e.target.value, 10) })} />
              </div>
              <label className="field-lg" style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <input type="checkbox" checked={agent.reasoning_enabled !== false} onChange={(e) => onUpdate({ reasoning_enabled: e.target.checked })} />
                Request thinking
              </label>
              <label className="field-lg" style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <input type="checkbox" checked={agent.reasoning_visible !== false} onChange={(e) => onUpdate({ reasoning_visible: e.target.checked })} />
                Show thinking trace
              </label>
            </div>
          </div>

          <div className="card-section">
            <div className="head">
              <h3>Agent prompt</h3>
              <span className="desc">System message used at the start of every conversation</span>
            </div>
            <div className="body padded">
              <PromptTemplateEditor
                className="textarea-lg"
                style={{ minHeight: 200 }}
                value={agent.system}
                agent={agent}
                onChange={(system, promptVariables) => {
                  onUpdate({ system, promptVariables });
                }}
                placeholder="You are a careful, concise assistant. When asked for code, respond with…"
              />
            </div>
          </div>

          <div className={"card-section popover-section" + (skillPickerOpen ? " popover-open" : "")}>
            <div className="head" style={{ position: "relative" }}>
              <h3>Skills</h3>
              <span className="desc">{enabledSkills.length} attached</span>
              <div style={{ flex: 1 }} />
              <button className="btn sm" onClick={() => setSkillPickerOpen((v) => !v)}>
                <Icon name="plus" size={12} /> Add skill
              </button>
              <RemoteSkillPickerPopover
                open={skillPickerOpen}
                onClose={() => setSkillPickerOpen(false)}
                selectedIds={selectedSkillTemplateIds}
                onToggle={installSkillTemplate}
                loadSkillPage={loadSkillPage}
                groupLabel="Marketplace skills"
              />
            </div>
            <div className="body">
              {enabledSkills.length === 0 ? (
                <div className="attach-empty">
                  <div className="icon"><Icon name="wand" size={18} /></div>
                  No skills attached. Click <b>Add skill</b> to give this agent capabilities.
                </div>
              ) : enabledSkills.map((s) => (
                <div className="attach-row" key={s.id}>
                  <div className="av" style={{ background: s.color }}>{s.initials}</div>
                  <div className="meta">
                    <div className="nm">{s.name}</div>
                    <div className="ds">{s.description}</div>
                  </div>
                  <span className="tag">{s.version ? `v${s.version}` : "version unavailable"}</span>
                  <button className="btn ghost icon sm" onClick={() => toggleSkill(s.id)} title="Remove"><Icon name="x" size={12} /></button>
                </div>
              ))}
            </div>
          </div>

          <div className={"card-section popover-section" + (localToolPickerOpen ? " popover-open" : "")}>
            <div className="head" style={{ position: "relative" }}>
              <h3>Local Tools</h3>
              <span className="desc" title="Default local runtime tools available whenever this agent runs.">{enabledLocalTools.length} enabled</span>
              <div style={{ flex: 1 }} />
              <button className="btn sm" onClick={() => setLocalToolPickerOpen((v) => !v)}>
                <Icon name="plus" size={12} /> Add tool
              </button>
              <PickerPopover
                open={localToolPickerOpen}
                onClose={() => setLocalToolPickerOpen(false)}
                items={localToolItems}
                selectedIds={selectedLocalToolNames}
                onToggle={toggleLocalTool}
                groupLabel="All local tools"
              />
            </div>
            <div className="body">
              {enabledLocalTools.length === 0 ? (
                <div className="attach-empty">
                  <div className="icon"><Icon name="tool" size={18} /></div>
                  No local tools attached. Click <b>Add tool</b> to make one available by default.
                </div>
              ) : enabledLocalTools.map((t) => (
                <div className="attach-row" key={t.id}>
                  <div className="av" style={{ background: t.color }}>{t.initials}</div>
                  <div className="meta">
                    <div className="nm">{t.label || t.name}</div>
                    <div className="ds">{t.description}</div>
                  </div>
                  <span className="tag">{t.group || "Local"}</span>
                  <button className="btn ghost icon sm" onClick={() => toggleLocalTool(t.id)} title="Remove"><Icon name="x" size={12} /></button>
                </div>
              ))}
            </div>
          </div>

          <div className={"card-section popover-section" + (mcpPickerOpen ? " popover-open" : "")}>
            <div className="head" style={{ position: "relative" }}>
              <h3>MCP Servers</h3>
              <span className="desc" title="Per-agent MCP binding persists to backend agent.mcp_ids. Runtime advertises these MCPs when this agent runs.">{enabledMCPs.length} enabled</span>
              <div style={{ flex: 1 }} />
              <button className="btn sm" onClick={() => setMcpPickerOpen((v) => !v)}>
                <Icon name="plus" size={12} /> Add MCP
              </button>
              <PickerPopover
                open={mcpPickerOpen}
                onClose={() => setMcpPickerOpen(false)}
                items={mcpItems}
                selectedIds={agent.enabledMCPs}
                onToggle={toggleMCP}
                groupLabel="All MCP servers"
              />
            </div>
            <div className="body">
              {enabledMCPs.length === 0 ? (
                <div className="attach-empty">
                  <div className="icon"><Icon name="tool" size={18} /></div>
                  No MCP servers attached.
                </div>
              ) : enabledMCPs.map((m) => (
                <div className="attach-row" key={m.id}>
                  <div className="av" style={{ background: m.color }}>{m.initials}</div>
                  <div className="meta">
                    <div className="nm">
                      {m.name}
                      <span className={"status-dot " + (m.status === "connected" ? "ok" : m.status === "warn" ? "warn" : "err")} style={{ marginLeft: 8 }}></span>
                    </div>
                    <div className="ds">{m.description}</div>
                  </div>
                  <span className="tag">{(m.tools || []).length} tools</span>
                  <button className="btn ghost icon sm" onClick={() => toggleMCP(m.id)} title="Remove"><Icon name="x" size={12} /></button>
                </div>
              ))}
            </div>
          </div>

          <div className="card-section effective-tools-section">
            <div className="head">
              <h3>Effective Tools</h3>
              <span className="desc">Available to this Agent at runtime</span>
            </div>
            <div className="body effective-tools-body">
              <ToolGroup title="Built-in Tools" items={PLATFORM_BUILTIN_TOOLS} />
              <ToolGroup title="Environment Tools" items={ADK_ENVIRONMENT_TOOLS} />
              <ToolGroup
                title="Bound MCP Tools"
                items={enabledMCPs.flatMap((m) => mcpToolNames(m).map((tool) => `${m.name}.${tool}`))}
                empty="No discovered tools from bound MCP servers."
              />
              <ToolGroup title="App Tools" items={appTools} empty="No App is assigned to this Agent as Executor." />
            </div>
          </div>
        </div>
      </div>
      <SaveBar saveState={saveState} onSave={onSave} />
    </div>
  );
}

function ToolGroup({ title, items, empty }) {
  return (
    <div className="effective-tool-group">
      <div className="effective-tool-group-head">
        <span>{title}</span>
        <span className="desc">{items.length}</span>
      </div>
      {items.length === 0 ? (
        <div className="effective-tool-empty">{empty || "None"}</div>
      ) : (
        <div className="effective-tool-list">
          {items.map((name) => <span className="effective-tool-chip" key={name}>{name}</span>)}
        </div>
      )}
    </div>
  );
}

window.AgentEditor = AgentEditor;
