// ====== MCP server editor — platform-level catalog (admin) ======
//
// MCP store(2026-05-26):MCP servers 持久化到 .baizhi-data SQLite
// (`mcp_servers` 表,平台级)。admin 增删改通过 8787 CRUD endpoints。
// 真 secret 不进 DB —— `auth_env_key` 只是 env 变量名,真值仍在 .env。
//
// 字段 → backend:
//   server_id / name / description / transport
//   transport=http/sse: http_url / headers_template (dict) / auth_env_key
//   transport=stdio: command / args (list)

function MCPEditor({
  mcp,
  onUpdate,
  onDelete,
  onDuplicate,
  saveState,
  onSave,
  onCheck,
  checkState,
  userEnabled,
  bindingState,
  onSetUserEnabled,
}) {
  // headers 在前端 UI 是 [{k,v}] 表格;decorateMcp 已转好。提交时
  // updateMcpServer 把它 collapse 回 dict 形态。
  const headers = mcp.headers || [];
  const runtimeOptions = mcp.runtime_options || {};
  const setHeader = (i, patch) => {
    const next = headers.map((h, idx) => (idx === i ? { ...h, ...patch } : h));
    const shouldPersist = Boolean(next[i]?.k) && Boolean(next[i].k.trim());
    onUpdate(patchForHeaderRows(next, { persist: shouldPersist }));
  };
  const addHeader = () => {
    const next = [...headers, { k: "", v: "" }];
    onUpdate(patchForHeaderRows(next, { persist: false }));
  };
  const removeHeader = (i) => {
    const next = headers.filter((_, idx) => idx !== i);
    onUpdate(patchForHeaderRows(next));
  };
  const setRuntimeOption = (key, value) => {
    const next = { ...runtimeOptions };
    const raw = String(value || "").trim();
    if (!raw) {
      delete next[key];
    } else {
      const n = Number(raw);
      if (Number.isFinite(n) && n > 0) next[key] = n;
    }
    onUpdate({ runtime_options: next });
  };

  // 标准 Claude Desktop / Cursor MCP config JSON 形态(参考用,Copy 用)
  const configJson = {
    mcpServers: {
      [mcp.name]: {
        type: mcp.transport === "http" ? "streamableHttp" : mcp.transport,
        description: mcp.description,
        isActive: mcp.available,
        name: mcp.server_id,
        ...(mcp.transport === "http" ? { baseUrl: mcp.url } : {}),
        ...(mcp.transport === "sse" ? { url: mcp.url } : {}),
        ...(mcp.command ? { command: mcp.command } : {}),
        ...(mcp.args ? { args: typeof mcp.args === "string" ? mcp.args.split(/\s+/).filter(Boolean) : mcp.args } : {}),
        ...(headers.length > 0 ? { headers: dictFromKV(headers) } : {}),
      },
    },
  };
  const latestCheck = checkState?.result || mcp.checkStatus || null;
  const configStatus = latestCheck?.config || mcp.configStatus || {
    configured: !!mcp.available,
    issues: mcp.available ? [] : ["env / config is not ready"],
    env_key: mcp.auth_env_key || "",
    env_present: !!mcp.available,
  };
  const mcpProbe = latestCheck?.mcp || {
    status: mcp.available ? "not checked" : "skipped",
    ok: false,
    error: mcp.available ? null : "configuration unavailable",
  };
  const toolsProbe = latestCheck?.tools || {
    status: "not checked",
    ok: false,
    count: Array.isArray(mcp.tools) ? mcp.tools.length : 0,
    items: mcp.tools || [],
    error: null,
  };
  const checkBusy = checkState?.status === "checking";
  const bindingBusy = bindingState?.status === "saving";

  return (
    <div className="detail">
      <div className="detail-head">
        <div className="av" style={{ background: mcp.color }}>{mcp.initials}</div>
        <div className="titles">
          <h1>
            {mcp.name}
            <span
              className={"status-dot " + (mcp.available ? "ok" : "warn")}
              title={mcp.available ? "Available — env / config OK" : "Unavailable — auth env var not set"}
            />
          </h1>
          <div className="sub">
            <span style={{ color: "var(--text-muted)" }}>platform catalog</span>
            {" · "}<span style={{ color: "var(--text)", fontFamily: "var(--font-mono)" }}>{mcp.server_id}</span>
            {" · transport "}{mcp.transport}
            {" · "}
            {mcp.available
              ? <span style={{ color: "var(--accent)" }}>available</span>
              : <span style={{ color: "var(--orange)" }}>unavailable</span>}
          </div>
        </div>
        <div className="actions">
          {saveState?.status === "saving" && <span style={{ color: "var(--text-muted)", fontSize: 12 }}>Saving…</span>}
          {saveState?.status === "saved" && <span style={{ color: "var(--accent)", fontSize: 12 }}>Saved</span>}
          {saveState?.status === "error" && <span style={{ color: "#b91c1c", fontSize: 12 }} title={saveState.error}>Save failed</span>}
          {onCheck && (
            <button className="btn" onClick={onCheck} disabled={checkBusy} title="Check config, MCP connection, and tools/list">
              <Icon name="loop" size={12} /> {checkBusy ? "Checking" : "Check"}
            </button>
          )}
          <button
            className="btn"
            onClick={(e) => window.copyText?.(JSON.stringify(configJson, null, 2), { anchor: e.currentTarget })}
            title="Copy mcpServers JSON config"
          >
            <Icon name="copy" size={12} /> Copy config
          </button>
          {onSave && <button className="btn" onClick={onSave}><Icon name="save" size={12} /> Save</button>}
          <button className="btn danger" onClick={onDelete} title="Delete this MCP server from platform catalog. Agents that referenced it will silently drop the tool.">
            <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>Health</h3>
              {latestCheck?.checked_at && (
                <span className="desc">checked {new Date(latestCheck.checked_at).toLocaleTimeString()}</span>
              )}
            </div>
            <div className="body padded">
              <div className="health-grid">
                <HealthTile
                  title="Configured"
                  ok={!!configStatus.configured}
                  status={configStatus.configured ? "ready" : "needs attention"}
                  detail={configStatus.configured
                    ? (configStatus.env_key ? `${configStatus.env_key} present` : "required fields present")
                    : (configStatus.issues || []).join("; ")}
                />
                <HealthTile
                  title="MCP"
                  ok={!!mcpProbe.ok}
                  status={mcpProbe.status}
                  detail={mcpProbe.ok ? "connect + initialize succeeded" : (mcpProbe.error || "not checked")}
                />
                <HealthTile
                  title="Tools"
                  ok={!!toolsProbe.ok}
                  status={toolsProbe.ok ? `${toolsProbe.count} available` : toolsProbe.status}
                  detail={toolsProbe.ok ? "tools/list returned usable tools" : (toolsProbe.error || "not checked")}
                />
                <HealthTile
                  title="Runs"
                  ok={!!userEnabled}
                  status={userEnabled ? "enabled" : "not enabled"}
                  detail={userEnabled
                    ? "included in future runs for this user"
                    : "check can pass but runs will not advertise these tools"}
                />
              </div>
              {onSetUserEnabled && (
                <div style={{ marginTop: 12, display: "flex", justifyContent: "flex-end" }}>
                  <button
                    className={"btn sm" + (!userEnabled ? " primary" : "")}
                    disabled={bindingBusy}
                    onClick={() => onSetUserEnabled(!userEnabled)}
                    title={userEnabled ? "Remove this MCP from future runs for this user" : "Include this MCP in future runs for this user"}
                  >
                    <Icon name={userEnabled ? "x" : "plus"} size={11} />
                    {bindingBusy ? "Saving" : (userEnabled ? "Disable for runs" : "Enable for runs")}
                  </button>
                </div>
              )}
              {(checkState?.error || (configStatus.issues || []).length > 0) && (
                <div style={{ marginTop: 12, fontSize: 12, color: "var(--orange)" }}>
                  {checkState?.error || (configStatus.issues || []).join("; ")}
                </div>
              )}
              {bindingState?.error && (
                <div style={{ marginTop: 12, fontSize: 12, color: "var(--orange)" }}>
                  {bindingState.error}
                </div>
              )}
              {(toolsProbe.items || []).length > 0 && (
                <div style={{ marginTop: 14 }}>
                  <div style={{ fontSize: 12, fontWeight: 650, marginBottom: 8 }}>Tools</div>
                  <div style={{ display: "grid", gap: 6 }}>
                    {(toolsProbe.items || []).map((tool) => (
                      <div key={tool.name} className="mcp-tool-row">
                        <span className="mono">{tool.name}</span>
                        <span>{tool.description || "No description"}</span>
                      </div>
                    ))}
                  </div>
                </div>
              )}
            </div>
          </div>

          <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>Server ID <span style={{ color: "var(--text-muted)", fontWeight: 400, fontSize: 11 }}>(immutable)</span></label>
                <input
                  className="input-lg"
                  style={{ fontFamily: "var(--font-mono)" }}
                  value={mcp.server_id}
                  readOnly
                  onFocus={(e) => e.target.select()}
                />
              </div>
              <div className="field-lg">
                <label>Transport</label>
                <select
                  className="input-lg"
                  value={mcp.transport}
                  onChange={(e) => onUpdate({ transport: e.target.value })}
                >
                  <option value="http">http / streamableHttp</option>
                  <option value="stdio">stdio</option>
                  <option value="sse">sse</option>
                </select>
              </div>
              <div className="field-lg" style={{ gridColumn: "1 / -1" }}>
                <label>Display name</label>
                <input
                  className="input-lg"
                  value={mcp.name}
                  onChange={(e) => onUpdate({ name: e.target.value })}
                />
              </div>
              <div className="field-lg" style={{ gridColumn: "1 / -1" }}>
                <label>Description</label>
                <textarea
                  className="input-lg"
                  style={{ minHeight: 56, resize: "vertical" }}
                  value={mcp.description}
                  onChange={(e) => onUpdate({ description: e.target.value })}
                />
              </div>
            </div>
          </div>

          <div className="card-section">
            <div className="head"><h3>Connection</h3></div>
            <div className="body padded">
              {mcp.transport === "stdio" ? (
                <div style={{ display: "grid", gridTemplateColumns: "1fr", gap: 14 }}>
                  <div className="field-lg">
                    <label>Command</label>
                    <input
                      className="input-lg"
                      style={{ fontFamily: "var(--font-mono)" }}
                      placeholder="npx, uvx, python …"
                      value={mcp.command}
                      onChange={(e) => onUpdate({ command: e.target.value })}
                    />
                  </div>
                  <div className="field-lg">
                    <label>Arguments <span style={{ color: "var(--text-muted)", fontWeight: 400, fontSize: 11 }}>(space-separated)</span></label>
                    <input
                      className="input-lg"
                      style={{ fontFamily: "var(--font-mono)" }}
                      placeholder="-y @modelcontextprotocol/server-…"
                      value={mcp.args}
                      onChange={(e) => onUpdate({ args: e.target.value })}
                    />
                  </div>
                </div>
              ) : (
                <div className="field-lg">
                  <label>Endpoint URL</label>
                  <input
                    className="input-lg"
                    style={{ fontFamily: "var(--font-mono)" }}
                    placeholder="https://…"
                    value={mcp.url}
                    onChange={(e) => onUpdate({ url: e.target.value, http_url: e.target.value })}
                  />
                </div>
              )}
            </div>
          </div>

          {(mcp.transport === "http" || mcp.transport === "sse") && (
            <div className="card-section">
              <div className="head">
                <h3>Headers</h3>
                <span className="desc">
                  {headers.length} set · <code style={{ fontFamily: "var(--font-mono)", fontSize: 11 }}>${"{VAR}"}</code> 在 runtime 从 env 解析
                </span>
                <div style={{ flex: 1 }} />
                <button className="btn sm" onClick={addHeader}><Icon name="plus" size={11} /> Add header</button>
              </div>
              <div className="body padded">
                {headers.length === 0 ? (
                  <div className="attach-empty" style={{ padding: "10px 0" }}>
                    No headers configured.
                  </div>
                ) : headers.map((e, i) => (
                  <div className="kv-row" key={i}>
                    <input
                      style={{ fontFamily: "var(--font-mono)" }}
                      placeholder="header"
                      value={e.k}
                      onChange={(ev) => setHeader(i, { k: ev.target.value })}
                    />
                    <input
                      style={{ fontFamily: "var(--font-mono)" }}
                      placeholder="value (use ${VAR} for secrets)"
                      value={e.v}
                      onChange={(ev) => setHeader(i, { v: ev.target.value })}
                    />
                    <button className="btn ghost icon" onClick={() => removeHeader(i)} title="Remove">
                      <Icon name="x" size={12} />
                    </button>
                  </div>
                ))}
              </div>
            </div>
          )}

          <div className="card-section">
            <div className="head"><h3>Auth env var <span style={{ color: "var(--text-muted)", fontWeight: 400, fontSize: 11 }}>(name only — value stays in .env)</span></h3></div>
            <div className="body padded">
              <input
                className="input-lg"
                style={{ fontFamily: "var(--font-mono)" }}
                placeholder="MY_API_KEY"
                value={mcp.auth_env_key || ""}
                onChange={(e) => onUpdate({ auth_env_key: e.target.value })}
              />
              {mcp.auth_env_key && (
                <div style={{ marginTop: 10, fontSize: 12, color: "var(--text-muted)" }}>
                  {mcp.available
                    ? <span style={{ color: "var(--accent)" }}>✓ <code style={{ fontFamily: "var(--font-mono)" }}>{mcp.auth_env_key}</code> is set in runtime env</span>
                    : <span style={{ color: "var(--orange)" }}>✗ <code style={{ fontFamily: "var(--font-mono)" }}>{mcp.auth_env_key}</code> missing — server will be unavailable</span>}
                </div>
              )}
            </div>
          </div>

          <div className="card-section">
            <div className="head">
              <h3>Runtime options</h3>
              <span className="desc">Blank uses server defaults</span>
            </div>
            <div className="body padded">
              <div style={{ display: "grid", gridTemplateColumns: "repeat(2, minmax(0, 1fr))", gap: 12 }}>
                <RuntimeOptionField
                  label="Connect timeout (s)"
                  value={runtimeOptions.connect_timeout_seconds}
                  placeholder="30"
                  onChange={(v) => setRuntimeOption("connect_timeout_seconds", v)}
                />
                <RuntimeOptionField
                  label="Tool/list timeout (s)"
                  value={runtimeOptions.operation_timeout_seconds}
                  placeholder="120"
                  onChange={(v) => setRuntimeOption("operation_timeout_seconds", v)}
                />
                <RuntimeOptionField
                  label="SSE read timeout (s)"
                  value={runtimeOptions.sse_read_timeout_seconds}
                  placeholder="300"
                  onChange={(v) => setRuntimeOption("sse_read_timeout_seconds", v)}
                />
                <RuntimeOptionField
                  label="SSE session TTL (s)"
                  value={runtimeOptions.sse_session_ttl_seconds}
                  placeholder="300"
                  onChange={(v) => setRuntimeOption("sse_session_ttl_seconds", v)}
                />
                <RuntimeOptionField
                  label="Close timeout (s)"
                  value={runtimeOptions.close_timeout_seconds}
                  placeholder="10"
                  onChange={(v) => setRuntimeOption("close_timeout_seconds", v)}
                />
                <RuntimeOptionField
                  label="Error chars"
                  value={runtimeOptions.error_content_max_chars}
                  placeholder="4000"
                  onChange={(v) => setRuntimeOption("error_content_max_chars", v)}
                />
                <RuntimeOptionField
                  label="Exception chars"
                  value={runtimeOptions.exception_message_max_chars}
                  placeholder="1500"
                  onChange={(v) => setRuntimeOption("exception_message_max_chars", v)}
                />
              </div>
            </div>
          </div>

          <div className="card-section">
            <div className="head">
              <h3>mcpServers config</h3>
              <span className="desc">
                Standard JSON (Claude Desktop / Cursor / Cline 兼容)
              </span>
            </div>
            <div className="body padded">
              <pre style={{
                background: "#1e1e1e", color: "#e8e8e8",
                padding: "12px 14px", borderRadius: 6,
                fontFamily: "var(--font-mono)", fontSize: 11.5,
                lineHeight: 1.55, overflowX: "auto", margin: 0,
              }}>
                {JSON.stringify(configJson, null, 2)}
              </pre>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

function HealthTile({ title, ok, status, detail }) {
  return (
    <div className={"health-tile " + (ok ? "ok" : "warn")}>
      <div className="health-title">
        <span className={"status-dot " + (ok ? "ok" : "warn")} />
        <span>{title}</span>
      </div>
      <div className="health-status">{status || "not checked"}</div>
      <div className="health-detail" title={detail || ""}>{detail || "not checked"}</div>
    </div>
  );
}

function RuntimeOptionField({ label, value, placeholder, onChange }) {
  return (
    <label className="field-lg">
      <span>{label}</span>
      <input
        className="input-lg"
        style={{ fontFamily: "var(--font-mono)" }}
        value={value ?? ""}
        placeholder={placeholder}
        onChange={(e) => onChange(e.target.value)}
      />
    </label>
  );
}

function dictFromKV(kvs) {
  const out = {};
  for (const { k, v } of kvs || []) {
    if (k) out[k] = v;
  }
  return out;
}

function patchForHeaderRows(next, { persist = true } = {}) {
  const patch = { headers: next };
  if (persist) {
    patch.headers_template = dictFromKV(next);
  }
  return patch;
}

window.MCPEditor = MCPEditor;
