<script>
  import { onMount } from "svelte";
  import { Tabs } from "bits-ui";
  import { api } from "../lib/api.js";
  import Icon from "../lib/Icon.svelte";

  let { notify } = $props();
  let tab = $state("tools");
  let tools = $state([]);
  let skills = $state([]);
  let servers = $state([]);
  let config = $state(null);
  let loading = $state(true);
  let saving = $state(false);
  let error = $state("");
  let query = $state("");
  let filter = $state("all");
  let mcpToolSettingsDraft = $state({});

  const filters = [
    ["all", "全部"],
    ["read_only", "只读"],
    ["concurrency_safe", "可并发"],
    ["side_effect", "有副作用"],
    ["exclusive", "独占"]
  ];

  function matches(tool) {
    if (filter === "read_only") return tool.read_only;
    if (filter === "concurrency_safe") return tool.concurrency_safe;
    if (filter === "side_effect") return !tool.read_only;
    if (filter === "exclusive") return tool.exclusive;
    return true;
  }

  const filtered = $derived.by(() => {
    const q = query.trim().toLowerCase();
    return tools.filter(
      (tool) =>
        matches(tool) &&
        (!q || tool.name.toLowerCase().includes(q) || (tool.description || "").toLowerCase().includes(q))
    );
  });

  const connected = $derived(servers.filter((server) => server.connected).length);

  // Merge MCP servers from the loaded config (source of truth, includes
  // disabled servers) with live connection status by name.
  const mcpList = $derived.by(() => {
    const byName = new Map(servers.map((server) => [server.name, server]));
    return (config?.mcp?.servers || []).map((server) => {
      const status = byName.get(server.name);
      return {
        name: server.name,
        transport: server.transport || "stdio",
        enabled: server.enabled !== false,
        connected: status?.connected ?? false,
        error: status?.error || "",
        tools: status?.tools || []
      };
    });
  });

  const mcpConnected = $derived(mcpList.filter((server) => server.connected).length);
  const mcpToolCount = $derived(
    mcpList.reduce((sum, server) => sum + (server.tools?.length || 0), 0)
  );
  const pendingMcpToolSettingCount = $derived(Object.keys(mcpToolSettingsDraft).length);

  function schema(tool) {
    return tool.parameters_schema ? JSON.stringify(tool.parameters_schema, null, 2) : "{}";
  }

  async function load() {
    loading = true;
    error = "";
    try {
      const [toolRes, skillRes, status, cfg] = await Promise.all([
        api("/api/tools"),
        api("/api/skills"),
        api("/api/status"),
        api("/api/config")
      ]);
      tools = toolRes.tools || [];
      skills = skillRes.skills || [];
      servers = status.mcp || [];
      config = cfg.config || null;
    } catch (caught) {
      error = caught.message;
    } finally {
      loading = false;
    }
  }

  async function toggleSkill(skill) {
    saving = true;
    try {
      const result = await api(`/api/skills/${encodeURIComponent(skill.name)}`, {
        method: "PUT",
        body: JSON.stringify({ enabled: !skill.enabled })
      });
      skills = result.skills || skills;
      notify(skill.enabled ? `已禁用 Skill「${skill.name}」` : `已启用 Skill「${skill.name}」`);
    } catch (caught) {
      notify(caught.message, true);
    } finally {
      saving = false;
    }
  }

  async function toggleMcp(server) {
    if (!config?.mcp?.servers) return;
    saving = true;
    try {
      const next = JSON.parse(JSON.stringify(config));
      const target = next.mcp.servers.find((item) => item.name === server.name);
      if (!target) return;
      target.enabled = !server.enabled;
      const result = await api("/api/config", { method: "PUT", body: JSON.stringify({ config: next }) });
      config = result.config || next;
      await api("/api/config/reload", { method: "POST" });
      notify(
        server.enabled
          ? `已禁用 MCP「${server.name}」，重载后生效`
          : `已启用 MCP「${server.name}」，重载后生效`
      );
      setTimeout(load, 1500);
    } catch (caught) {
      notify(caught.message, true);
    } finally {
      saving = false;
    }
  }

  function mcpToolSettingKey(server, tool) {
    return JSON.stringify([server.name, tool.name]);
  }

  function configuredMcpToolSettings(server, tool) {
    const configured = config?.mcp?.servers
      ?.find((item) => item.name === server.name)
      ?.tool_settings?.[tool.name];
    return {
      read_only: configured?.read_only === true,
      exclusive: configured?.exclusive === true
    };
  }

  function mcpToolSettingsFor(server, tool) {
    return mcpToolSettingsDraft[mcpToolSettingKey(server, tool)]?.settings
      || configuredMcpToolSettings(server, tool);
  }

  function updateMcpToolSetting(server, tool, setting, enabled) {
    const key = mcpToolSettingKey(server, tool);
    const current = mcpToolSettingsFor(server, tool);
    mcpToolSettingsDraft = {
      ...mcpToolSettingsDraft,
      [key]: {
        serverName: server.name,
        toolName: tool.name,
        settings: { ...current, [setting]: enabled }
      }
    };
  }

  function discardMcpToolSettings() {
    mcpToolSettingsDraft = {};
  }

  async function applyMcpToolSettings() {
    if (!config?.mcp?.servers || !pendingMcpToolSettingCount) return;
    saving = true;
    try {
      const next = JSON.parse(JSON.stringify(config));
      for (const entry of Object.values(mcpToolSettingsDraft)) {
        const target = next.mcp.servers.find((item) => item.name === entry.serverName);
        if (!target) continue;

        target.tool_settings ||= {};
        if (entry.settings.read_only || entry.settings.exclusive) {
          target.tool_settings[entry.toolName] = entry.settings;
        } else {
          delete target.tool_settings[entry.toolName];
          if (!Object.keys(target.tool_settings).length) delete target.tool_settings;
        }
      }

      const result = await api("/api/config", { method: "PUT", body: JSON.stringify({ config: next }) });
      config = result.config || next;
      await api("/api/config/reload", { method: "POST" });
      const count = pendingMcpToolSettingCount;
      discardMcpToolSettings();
      notify(`已保存 ${count} 项 MCP 工具执行属性，重载后生效`);
      setTimeout(load, 1500);
    } catch (caught) {
      notify(caught.message, true);
    } finally {
      saving = false;
    }
  }

  function changeTab(value) {
    if (tab === "mcp" && value !== "mcp") discardMcpToolSettings();
    tab = value;
  }

  function refresh() {
    discardMcpToolSettings();
    load();
  }

  onMount(load);
</script>

<section class="page active content-page">
  <div class="toolbar">
    <Tabs.Root value={tab} onValueChange={changeTab}>
      <Tabs.List class="tabs" aria-label="工具与技能">
        <Tabs.Trigger value="tools">工具</Tabs.Trigger>
        <Tabs.Trigger value="skills">Skills</Tabs.Trigger>
        <Tabs.Trigger value="mcp">MCP</Tabs.Trigger>
      </Tabs.List>
    </Tabs.Root>
    <button class="secondary" onclick={refresh}><Icon name="refresh" size={16} />刷新</button>
  </div>

  {#if loading}
    <div class="loading">加载中…</div>
  {:else if error}
    <div class="empty-card error-text">{error}</div>
  {:else if tab === "tools"}
    <form class="filters-bar" onsubmit={(event) => event.preventDefault()}>
      <label class="search grow"><Icon name="search" size={16} /><input bind:value={query} placeholder="搜索工具名称或描述" /></label>
      <div class="chips">
        {#each filters as [value, label]}
          <button type="button" class="chip" class:active={filter === value} onclick={() => (filter = value)}>{label}</button>
        {/each}
      </div>
    </form>
    <div class="legend">
      <span class="cap signal">● 只读</span>
      <span class="cap info">● 可并发</span>
      <span class="cap accent">● 有副作用</span>
      <span class="cap danger">● 独占</span>
    </div>
    <div class="tool-grid">
      {#each filtered as tool (tool.name)}
        <article class="card">
          <div class="card-row">
            <div>
              <h3 class="mono">{tool.name}</h3>
              <div class="meta"><span>{tool.call_count} 次调用</span></div>
            </div>
            <span class="badge" class:src-mcp={tool.source === "mcp"}>{tool.source}</span>
          </div>
          <p>{tool.description}</p>
          <div class="cap-row">
            {#if tool.read_only}<span class="cap signal">● 只读</span>{/if}
            {#if tool.concurrency_safe}<span class="cap info">● 可并发</span>{/if}
            {#if !tool.read_only}<span class="cap accent">● 有副作用</span>{/if}
            {#if tool.exclusive}<span class="cap danger">● 独占</span>{/if}
          </div>
          <details class="schema-wrap">
            <summary>参数 schema</summary>
            <pre class="mono">{schema(tool)}</pre>
          </details>
        </article>
      {:else}
        <div class="empty-card">没有匹配的工具</div>
      {/each}
    </div>
  {:else if tab === "skills"}
    <div class="cards">
      {#each skills as skill (skill.name)}
        <article class="card">
          <div class="card-row">
            <div>
              <h3 class="mono">{skill.name}</h3>
              <p>{skill.description}</p>
              <div class="meta"><span>{skill.source}</span></div>
            </div>
            <div class="card-actions">
              {#if skill.always}<span class="badge ok">always</span>{/if}
              <button
                class="switch"
                data-state={skill.enabled ? "checked" : "unchecked"}
                aria-label={skill.enabled ? `禁用 ${skill.name}` : `启用 ${skill.name}`}
                onclick={() => toggleSkill(skill)}
                disabled={saving}
              >
                <span class="switch-thumb" data-state={skill.enabled ? "checked" : "unchecked"}></span>
              </button>
            </div>
          </div>
        </article>
      {:else}
        <div class="empty-card">暂无 Skills</div>
      {/each}
    </div>
  {:else}
    <div class="metrics">
      <div class="metric"><b>{mcpList.length}</b><small>MCP 服务器</small></div>
      <div class="metric"><b>{mcpConnected}</b><small>已连接</small></div>
      <div class="metric"><b>{mcpToolCount}</b><small>工具总数</small></div>
    </div>
    <div class="mcp-draft-bar" class:has-draft={pendingMcpToolSettingCount > 0}>
      <span>
        {#if pendingMcpToolSettingCount}
          已暂存 {pendingMcpToolSettingCount} 项执行属性；离开 MCP 标签或刷新页面会丢弃未保存更改。
        {:else}
          修改工具属性后，可一次保存并应用。
        {/if}
      </span>
      {#if pendingMcpToolSettingCount}
        <div class="card-actions">
          <button class="secondary" onclick={discardMcpToolSettings} disabled={saving}>放弃修改</button>
          <button onclick={applyMcpToolSettings} disabled={saving}>保存并应用</button>
        </div>
      {/if}
    </div>
    <div class="cards">
      {#each mcpList as server (server.name)}
        <article class="card" class:disabled={!server.enabled}>
          <div class="card-row">
            <div>
              <h3 class="mono">{server.name}</h3>
              <div class="meta">
                <span>{server.transport}</span>
                <span>{server.tools?.length || 0} 个工具</span>
              </div>
            </div>
            <div class="card-actions">
              {#if !server.enabled}
                <span class="badge fail">已禁用</span>
              {:else if server.connected}
                <span class="badge ok">已连接</span>
              {:else}
                <span class="badge fail">未连接</span>
              {/if}
              <button
                class="switch"
                data-state={server.enabled ? "checked" : "unchecked"}
                aria-label={server.enabled ? `禁用 ${server.name}` : `启用 ${server.name}`}
                onclick={() => toggleMcp(server)}
                disabled={saving}
              >
                <span class="switch-thumb" data-state={server.enabled ? "checked" : "unchecked"}></span>
              </button>
            </div>
          </div>
          {#if server.error}<div class="mcp-error">{server.error}</div>{/if}
          {#if server.tools?.length}
            <details class="schema-wrap">
              <summary>{server.tools.length} 个工具</summary>
              <ul class="mcp-tools">
                {#each server.tools as tool (tool.name)}
                  {@const settings = mcpToolSettingsFor(server, tool)}
                  <li>
                    <div class="mcp-tool-heading">
                      <code>{tool.name}</code>
                      <div class="cap-row compact">
                        {#if settings.read_only}<span class="cap signal">● 只读</span>{:else}<span class="cap accent">● 有副作用</span>{/if}
                        {#if settings.read_only && !settings.exclusive}<span class="cap info">● 可并发</span>{/if}
                        {#if settings.exclusive}<span class="cap danger">● 独占</span>{/if}
                      </div>
                    </div>
                    {#if tool.description}<span>{tool.description}</span>{/if}
                    <fieldset class="mcp-tool-settings">
                      <legend>执行属性</legend>
                      <label title="声明该工具没有外部副作用；只读且非独占的工具会自动并发执行。">
                        <input
                          type="checkbox"
                          checked={settings.read_only}
                          onchange={(event) => updateMcpToolSetting(server, tool, "read_only", event.currentTarget.checked)}
                          disabled={saving || !server.enabled}
                        />
                        只读
                      </label>
                      <label title="声明该工具必须单独执行，即使它是只读工具也不会与其他工具并发。">
                        <input
                          type="checkbox"
                          checked={settings.exclusive}
                          onchange={(event) => updateMcpToolSetting(server, tool, "exclusive", event.currentTarget.checked)}
                          disabled={saving || !server.enabled}
                        />
                        独占
                      </label>
                      <small>“可并发”由“只读且非独占”自动计算，不单独保存。</small>
                    </fieldset>
                  </li>
                {/each}
              </ul>
            </details>
          {:else if server.enabled}
            <p class="mcp-empty">连接后无可用工具</p>
          {/if}
        </article>
      {:else}
        <div class="empty-card">暂无 MCP 服务器</div>
      {/each}
    </div>
  {/if}
</section>

<style>
  .filters-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
  .legend { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
  .tool-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 11px; }
  .cap-row { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
  .badge.src-mcp { background: var(--info-soft); color: var(--info); }
  .schema-wrap { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 10px; }
  .schema-wrap summary { cursor: pointer; color: var(--muted); font-size: 12px; }
  .schema-wrap pre { max-height: 300px; margin: 8px 0 0; padding: 10px 11px; overflow: auto; border: 1px solid var(--line); border-radius: 8px; color: var(--text-soft); background: var(--code-bg); font-size: 12px; line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere; }
  .card-actions { display: flex; align-items: center; gap: 10px; }
  .card.disabled { opacity: 0.65; }
  .mcp-draft-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 0 0 12px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 9px; color: var(--muted); font-size: 12px; line-height: 1.5; }
  .mcp-draft-bar.has-draft { border-color: var(--accent-border); background: var(--accent-soft); color: var(--text-soft); }
  .mcp-tools { display: grid; gap: 7px; margin: 10px 0 0; padding: 0; list-style: none; }
  .mcp-tools li { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; }
  .mcp-tool-heading { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
  .cap-row.compact { margin-top: 0; }
  .mcp-tools code { font: 600 12px/1.5 var(--font-mono); color: var(--text); }
  .mcp-tools span { font-size: 12px; color: var(--muted); line-height: 1.5; }
  .mcp-tool-settings { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; margin: 6px 0 0; padding: 7px 0 0; border: 0; border-top: 1px solid var(--line); }
  .mcp-tool-settings legend { padding: 0; color: var(--muted); font-size: 11px; }
  .mcp-tool-settings label { display: inline-flex; align-items: center; gap: 5px; color: var(--text-soft); font-size: 12px; cursor: pointer; }
  .mcp-tool-settings input { accent-color: var(--brand); }
  .mcp-tool-settings label:has(input:disabled) { cursor: not-allowed; opacity: 0.65; }
  .mcp-tool-settings small { color: var(--muted); font-size: 11px; line-height: 1.5; }
  .mcp-error { margin-top: 10px; padding: 8px 10px; border-radius: 8px; background: var(--danger-soft); color: var(--danger); font-size: 12px; line-height: 1.5; word-break: break-all; }
  .mcp-empty { margin: 10px 0 0; color: var(--muted); font-size: 12px; }
  @media (max-width: 800px) {
    .tool-grid { grid-template-columns: 1fr; }
  }
</style>
