import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import {
  getGithubMirrors, addGithubMirror, deleteGithubMirror, testMirrorLocal,
  getServerNetworkConfig, setServerMirror,
  getServerSoftwareList, addServerSoftware, updateServerSoftware, deleteServerSoftware,
  getSoftwareCategories, addSoftwareCategory, updateSoftwareCategory, deleteSoftwareCategory,
  generateInstallCommand,
  type GithubMirror, type MirrorTestResult, type ServerSoftware, type SaveSoftwareInput,
  type SoftwareCategory,
} from "../../lib/commands";

function CopyBtn({ value }: { value: string }) {
  const [copied, setCopied] = useState(false);
  const handleCopy = async () => {
    await navigator.clipboard.writeText(value);
    setCopied(true);
    setTimeout(() => setCopied(false), 1500);
  };
  return (
    <button onClick={handleCopy}
      className="shrink-0 px-1.5 py-0.5 rounded text-[11px] border border-gray-200 text-gray-400 hover:text-blue-500 hover:border-blue-300 hover:bg-blue-50 transition-colors">
      {copied ? "已复制" : "复制"}
    </button>
  );
}

const PRESET_COLORS = ["#3b82f6", "#10b981", "#f59e0b", "#8b5cf6", "#ef4444", "#ec4899", "#6b7280"];

// ── 镜像管理面板 ────────────────────────────────────────────────────────────

function MirrorManager({ serverId }: { serverId: string }) {
  const qc = useQueryClient();
  const [testResults, setTestResults] = useState<Record<string, MirrorTestResult>>({});
  const [testing, setTesting] = useState<string | null>(null);
  const [showAddMirror, setShowAddMirror] = useState(false);
  const [newName, setNewName] = useState("");
  const [newUrl, setNewUrl] = useState("");

  const { data: mirrors = [] } = useQuery({
    queryKey: ["github-mirrors"],
    queryFn: getGithubMirrors,
  });

  const { data: netConfig } = useQuery({
    queryKey: ["server-network-config", serverId],
    queryFn: () => getServerNetworkConfig(serverId),
  });

  const setMirrorMut = useMutation({
    mutationFn: (mirrorId: string) => setServerMirror(serverId, mirrorId),
    onSuccess: () => qc.invalidateQueries({ queryKey: ["server-network-config", serverId] }),
  });

  const addMirrorMut = useMutation({
    mutationFn: () => addGithubMirror({ name: newName.trim(), url_prefix: newUrl.trim() }),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["github-mirrors"] });
      setShowAddMirror(false);
      setNewName("");
      setNewUrl("");
    },
  });

  const deleteMirrorMut = useMutation({
    mutationFn: (id: string) => deleteGithubMirror(id),
    onSuccess: () => qc.invalidateQueries({ queryKey: ["github-mirrors"] }),
  });

  const handleTest = async (mirror: GithubMirror) => {
    setTesting(mirror.id);
    try {
      const result = await testMirrorLocal(mirror.id);
      setTestResults((prev) => ({ ...prev, [mirror.id]: result }));
    } catch {
      setTestResults((prev) => ({
        ...prev,
        [mirror.id]: { mirror_id: mirror.id, reachable: false, error: "测试失败" },
      }));
    }
    setTesting(null);
  };

  const currentMirrorId = netConfig?.github_mirror_id;
  const currentMirror = mirrors.find((m) => m.id === currentMirrorId);

  // 服务器端测试命令
  const serverTestCmd = (m: GithubMirror) => {
    const url = m.url_prefix.includes("github.com")
      ? "https://github.com/robots.txt"
      : `${m.url_prefix.replace(/\/$/, "")}/https://github.com/robots.txt`;
    return `curl -sI --max-time 8 "${url}" | head -1`;
  };

  return (
    <div className="space-y-2">
      <div className="flex items-center justify-between">
        <h5 className="text-[11px] font-semibold text-gray-500">GitHub 镜像</h5>
        <div className="flex items-center gap-2">
          {currentMirror && (
            <span className="text-[10px] text-green-600 bg-green-50 px-1.5 py-0.5 rounded border border-green-200">
              当前: {currentMirror.name}
            </span>
          )}
          <button onClick={() => setShowAddMirror((v) => !v)}
            className="text-[11px] text-gray-400 hover:text-blue-500 transition-colors">
            {showAddMirror ? "取消" : "+ 自定义"}
          </button>
        </div>
      </div>

      {/* 添加自定义镜像 */}
      {showAddMirror && (
        <div className="flex gap-2 items-end">
          <input value={newName} onChange={(e) => setNewName(e.target.value)} placeholder="名称"
            className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-white w-24 focus:outline-none focus:border-blue-300" />
          <input value={newUrl} onChange={(e) => setNewUrl(e.target.value)} placeholder="https://mirror.example.com"
            className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-white flex-1 focus:outline-none focus:border-blue-300" />
          <button onClick={() => addMirrorMut.mutate()}
            disabled={!newName.trim() || !newUrl.trim()}
            className="px-2.5 py-1.5 rounded text-xs bg-blue-50 text-blue-600 border border-blue-200 hover:bg-blue-100 disabled:opacity-40 shrink-0">
            添加
          </button>
        </div>
      )}

      {/* 镜像列表 */}
      <div className="space-y-1">
        {mirrors.map((m) => {
          const result = testResults[m.id];
          const isActive = m.id === currentMirrorId;
          return (
            <div key={m.id}
              className={`flex items-center gap-2 px-3 py-2 rounded-lg border transition-colors ${
                isActive ? "border-green-200 bg-green-50/50" : "border-gray-100 bg-white"
              }`}
            >
              <div className="flex-1 min-w-0">
                <div className="flex items-center gap-1.5">
                  <span className="text-xs font-medium text-gray-700">{m.name}</span>
                  {result && (
                    <span className={`text-[10px] px-1 py-0.5 rounded ${
                      result.reachable ? "bg-green-50 text-green-600" : "bg-red-50 text-red-500"
                    }`}>
                      {result.reachable ? `✓ ${result.latency_ms}ms` : "✗ 不可达"}
                    </span>
                  )}
                </div>
                <span className="text-[10px] text-gray-400 font-mono">{m.url_prefix}</span>
              </div>

              <div className="flex gap-1 shrink-0 items-center">
                {/* 服务器端测试命令 */}
                <CopyBtn value={serverTestCmd(m)} />
                <span className="text-[10px] text-gray-300">服务器测试</span>

                <div className="w-px h-3 bg-gray-200 mx-0.5" />

                <button onClick={() => handleTest(m)} disabled={testing === m.id}
                  className="px-1.5 py-0.5 rounded text-[11px] border border-gray-200 text-gray-400 hover:text-blue-500 hover:border-blue-300 transition-colors disabled:opacity-40">
                  {testing === m.id ? "…" : "本地测试"}
                </button>
                {!isActive && (
                  <button onClick={() => setMirrorMut.mutate(m.id)}
                    className="px-1.5 py-0.5 rounded text-[11px] border border-gray-200 text-gray-400 hover:text-green-500 hover:border-green-300 transition-colors">
                    使用
                  </button>
                )}
                {!m.is_builtin && (
                  <button onClick={() => deleteMirrorMut.mutate(m.id)}
                    className="text-[11px] text-gray-300 hover:text-red-400 transition-colors">
                    x
                  </button>
                )}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ── 软件列表 + 命令生成 ──────────────────────────────────────────────────────

function SoftwareList({ serverId }: { serverId: string }) {
  const qc = useQueryClient();
  const [showAdd, setShowAdd] = useState(false);
  const [expandedCmd, setExpandedCmd] = useState<string | null>(null);
  const [commands, setCommands] = useState<Record<string, string[]>>({});

  const { data: softwareList = [] } = useQuery({
    queryKey: ["server-software"],
    queryFn: getServerSoftwareList,
  });

  const { data: categories = [] } = useQuery({
    queryKey: ["software-categories"],
    queryFn: getSoftwareCategories,
    staleTime: 30000,
  });

  const catMap = new Map(categories.map((c) => [c.id, c]));

  // 添加表单
  const [newName, setNewName] = useState("");
  const [newType, setNewType] = useState<"package" | "github">("package");
  const [newCmd, setNewCmd] = useState("");
  const [newGithubUrl, setNewGithubUrl] = useState("");
  const [newRemotePath, setNewRemotePath] = useState("");
  const [newPostCmd, setNewPostCmd] = useState("");
  const [newCategory, setNewCategory] = useState("custom");

  const addMut = useMutation({
    mutationFn: (input: SaveSoftwareInput) => addServerSoftware(input),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["server-software"] });
      setShowAdd(false);
      setNewName(""); setNewCmd(""); setNewGithubUrl(""); setNewRemotePath(""); setNewPostCmd("");
    },
  });

  const deleteMut = useMutation({
    mutationFn: (id: string) => deleteServerSoftware(id),
    onSuccess: () => qc.invalidateQueries({ queryKey: ["server-software"] }),
  });

  const handleGenerate = async (swId: string) => {
    if (expandedCmd === swId) {
      setExpandedCmd(null);
      return;
    }
    try {
      const cmds = await generateInstallCommand(swId, serverId);
      setCommands((prev) => ({ ...prev, [swId]: cmds }));
      setExpandedCmd(swId);
    } catch { /* ignore */ }
  };

  const handleAdd = () => {
    addMut.mutate({
      name: newName.trim(),
      category: newCategory,
      install_type: newType,
      install_cmd: newType === "package" ? newCmd || undefined : undefined,
      github_url: newType === "github" ? newGithubUrl || undefined : undefined,
      remote_path: newType === "github" ? newRemotePath || undefined : undefined,
      post_cmd: newPostCmd || undefined,
    });
  };

  // 按分类分组
  const grouped = softwareList.reduce<Record<string, ServerSoftware[]>>((acc, sw) => {
    const cat = sw.category || "custom";
    (acc[cat] ??= []).push(sw);
    return acc;
  }, {});

  return (
    <div className="space-y-2">
      <div className="flex items-center justify-between">
        <h5 className="text-[11px] font-semibold text-gray-500">软件仓库</h5>
        <button onClick={() => setShowAdd((v) => !v)}
          className="text-[11px] text-gray-400 hover:text-blue-500 transition-colors">
          {showAdd ? "取消" : "+ 添加软件"}
        </button>
      </div>

      {/* 添加表单 */}
      {showAdd && (
        <div className="bg-white border border-gray-200 rounded-lg p-3 space-y-2">
          <div className="grid grid-cols-[1fr_auto_auto] gap-2 items-end">
            <div className="flex flex-col gap-0.5">
              <label className="text-[10px] text-gray-400">名称</label>
              <input value={newName} onChange={(e) => setNewName(e.target.value)} placeholder="如: Redis"
                className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300" />
            </div>
            <div className="flex flex-col gap-0.5">
              <label className="text-[10px] text-gray-400">分类</label>
              <select value={newCategory} onChange={(e) => setNewCategory(e.target.value)}
                className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300">
                <option value="web">Web 服务</option>
                <option value="runtime">运行时</option>
                <option value="tool">工具</option>
                <option value="custom">自定义</option>
              </select>
            </div>
            <div className="flex flex-col gap-0.5">
              <label className="text-[10px] text-gray-400">类型</label>
              <div className="flex rounded border border-gray-200 overflow-hidden h-[30px]">
                <button onClick={() => setNewType("package")}
                  className={`px-2 text-xs ${newType === "package" ? "bg-blue-50 text-blue-600" : "bg-white text-gray-400"}`}>
                  系统包
                </button>
                <button onClick={() => setNewType("github")}
                  className={`px-2 text-xs border-l border-gray-200 ${newType === "github" ? "bg-blue-50 text-blue-600" : "bg-white text-gray-400"}`}>
                  GitHub
                </button>
              </div>
            </div>
          </div>
          {newType === "package" ? (
            <div className="flex flex-col gap-0.5">
              <label className="text-[10px] text-gray-400">安装命令</label>
              <input value={newCmd} onChange={(e) => setNewCmd(e.target.value)}
                placeholder="apt install -y redis-server"
                className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-gray-50 font-mono focus:outline-none focus:border-blue-300" />
            </div>
          ) : (
            <>
              <div className="flex flex-col gap-0.5">
                <label className="text-[10px] text-gray-400">GitHub Release URL</label>
                <input value={newGithubUrl} onChange={(e) => setNewGithubUrl(e.target.value)}
                  placeholder="https://github.com/user/repo/releases/download/v1.0/file.tar.gz"
                  className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-gray-50 font-mono focus:outline-none focus:border-blue-300" />
              </div>
              <div className="flex flex-col gap-0.5">
                <label className="text-[10px] text-gray-400">下载目标路径</label>
                <input value={newRemotePath} onChange={(e) => setNewRemotePath(e.target.value)}
                  placeholder="/opt/download/file.tar.gz"
                  className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-gray-50 font-mono focus:outline-none focus:border-blue-300" />
              </div>
            </>
          )}
          <div className="flex flex-col gap-0.5">
            <label className="text-[10px] text-gray-400">安装后命令（选填）</label>
            <input value={newPostCmd} onChange={(e) => setNewPostCmd(e.target.value)}
              placeholder="systemctl enable redis-server"
              className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-gray-50 font-mono focus:outline-none focus:border-blue-300" />
          </div>
          <div className="flex justify-end">
            <button onClick={handleAdd} disabled={!newName.trim() || addMut.isPending}
              className="px-3 py-1.5 rounded text-xs bg-blue-50 text-blue-600 border border-blue-200 hover:bg-blue-100 disabled:opacity-40">
              添加
            </button>
          </div>
        </div>
      )}

      {/* 按分类展示 */}
      {Object.entries(grouped).map(([cat, items]) => (
        <div key={cat}>
          <p className="text-[10px] text-gray-400 font-medium mb-1">{catMap.get(cat)?.name ?? cat}</p>
          <div className="space-y-1">
            {items.map((sw) => (
              <div key={sw.id} className="bg-white border border-gray-100 rounded-lg px-3 py-2">
                <div className="flex items-center gap-2">
                  <span className="text-xs font-medium text-gray-700">{sw.name}</span>
                  <span className={`text-[10px] px-1 py-0.5 rounded ${
                    sw.install_type === "github" ? "bg-gray-800 text-white" : "bg-blue-50 text-blue-600"
                  }`}>
                    {sw.install_type === "github" ? "GitHub" : "package"}
                  </span>
                  {sw.description && (
                    <span className="text-[10px] text-gray-400">{sw.description}</span>
                  )}
                  <div className="ml-auto flex gap-1 shrink-0">
                    <button onClick={() => handleGenerate(sw.id)}
                      className={`px-1.5 py-0.5 rounded text-[11px] border transition-colors ${
                        expandedCmd === sw.id
                          ? "border-blue-300 bg-blue-50 text-blue-600"
                          : "border-gray-200 text-gray-400 hover:text-blue-500"
                      }`}>
                      {expandedCmd === sw.id ? "收起" : "生成命令"}
                    </button>
                    {!sw.is_builtin && (
                      <button onClick={() => deleteMut.mutate(sw.id)}
                        className="text-[11px] text-gray-300 hover:text-red-400 transition-colors">
                        x
                      </button>
                    )}
                  </div>
                </div>
                {/* 生成的命令 */}
                {expandedCmd === sw.id && commands[sw.id] && (
                  <div className="mt-2 space-y-1">
                    {commands[sw.id].map((cmd, i) => (
                      <div key={i} className="flex items-start gap-2 px-2.5 py-1.5 bg-gray-900 rounded-md">
                        <span className="text-[10px] text-green-400 font-mono flex-1 break-all leading-relaxed">
                          $ {cmd}
                        </span>
                        <CopyBtn value={cmd} />
                      </div>
                    ))}
                    <p className="text-[10px] text-gray-400">复制命令到服务器终端（VS Code Remote / SSH）执行</p>
                  </div>
                )}
              </div>
            ))}
          </div>
        </div>
      ))}
    </div>
  );
}

// ── 全局面板（与 SSH 密钥配置同级）──────────────────────────────────────────

export function ServerSoftwarePanel() {
  return (
    <div className="space-y-4">
      <div className="bg-white border border-gray-200 rounded-xl overflow-hidden">
        <div className="flex items-center gap-3 px-5 py-3 border-b border-gray-100 bg-gray-50/60">
          <span className="text-sm font-bold text-gray-800">GitHub 镜像管理</span>
        </div>
        <div className="px-5 py-4">
          <MirrorManagerGlobal />
        </div>
      </div>

      <div className="bg-white border border-gray-200 rounded-xl overflow-hidden">
        <div className="flex items-center gap-3 px-5 py-3 border-b border-gray-100 bg-gray-50/60">
          <span className="text-sm font-bold text-gray-800">软件仓库</span>
        </div>
        <div className="px-5 py-4">
          <SoftwareListGlobal />
        </div>
      </div>
    </div>
  );
}

/// 全局镜像管理（不关联具体服务器）
function MirrorManagerGlobal() {
  const qc = useQueryClient();
  const [testResults, setTestResults] = useState<Record<string, MirrorTestResult>>({});
  const [testing, setTesting] = useState<string | null>(null);
  const [showAddMirror, setShowAddMirror] = useState(false);
  const [newName, setNewName] = useState("");
  const [newUrl, setNewUrl] = useState("");

  const { data: mirrors = [] } = useQuery({
    queryKey: ["github-mirrors"],
    queryFn: getGithubMirrors,
  });

  const addMirrorMut = useMutation({
    mutationFn: () => addGithubMirror({ name: newName.trim(), url_prefix: newUrl.trim() }),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["github-mirrors"] });
      setShowAddMirror(false);
      setNewName("");
      setNewUrl("");
    },
  });

  const deleteMirrorMut = useMutation({
    mutationFn: (id: string) => deleteGithubMirror(id),
    onSuccess: () => qc.invalidateQueries({ queryKey: ["github-mirrors"] }),
  });

  const handleTest = async (mirror: GithubMirror) => {
    setTesting(mirror.id);
    try {
      const result = await testMirrorLocal(mirror.id);
      setTestResults((prev) => ({ ...prev, [mirror.id]: result }));
    } catch {
      setTestResults((prev) => ({
        ...prev,
        [mirror.id]: { mirror_id: mirror.id, reachable: false, error: "测试失败" },
      }));
    }
    setTesting(null);
  };

  const serverTestCmd = (m: GithubMirror) => {
    const url = m.url_prefix.includes("github.com")
      ? "https://github.com/robots.txt"
      : `${m.url_prefix.replace(/\/$/, "")}/https://github.com/robots.txt`;
    return `curl -sI --max-time 8 "${url}" | head -1`;
  };

  return (
    <div className="space-y-2">
      <div className="flex items-center justify-between">
        <h5 className="text-xs font-semibold text-gray-600">GitHub 镜像源</h5>
        <button onClick={() => setShowAddMirror((v) => !v)}
          className="text-[11px] text-gray-400 hover:text-blue-500 transition-colors">
          {showAddMirror ? "取消" : "+ 自定义镜像"}
        </button>
      </div>

      <p className="text-[10px] text-gray-400">本地测试验证镜像地址是否有效；服务器测试命令需复制到服务器终端执行（不同服务器网络环境不同）</p>

      {showAddMirror && (
        <div className="flex gap-2 items-end">
          <input value={newName} onChange={(e) => setNewName(e.target.value)} placeholder="名称"
            className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-gray-50 w-28 focus:outline-none focus:border-blue-300" />
          <input value={newUrl} onChange={(e) => setNewUrl(e.target.value)} placeholder="https://mirror.example.com"
            className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-gray-50 flex-1 focus:outline-none focus:border-blue-300" />
          <button onClick={() => addMirrorMut.mutate()}
            disabled={!newName.trim() || !newUrl.trim()}
            className="px-2.5 py-1.5 rounded text-xs bg-blue-50 text-blue-600 border border-blue-200 hover:bg-blue-100 disabled:opacity-40 shrink-0">
            添加
          </button>
        </div>
      )}

      <div className="space-y-1">
        {mirrors.map((m) => {
          const result = testResults[m.id];
          return (
            <div key={m.id} className="flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-100 bg-gray-50/50">
              <div className="flex-1 min-w-0">
                <div className="flex items-center gap-1.5">
                  <span className="text-xs font-medium text-gray-700">{m.name}</span>
                  {m.is_builtin && <span className="text-[10px] text-gray-300">内置</span>}
                  {result && (
                    <span className={`text-[10px] px-1 py-0.5 rounded ${
                      result.reachable ? "bg-green-50 text-green-600" : "bg-red-50 text-red-500"
                    }`}>
                      {result.reachable ? `本地 ✓ ${result.latency_ms}ms` : "本地 ✗"}
                    </span>
                  )}
                </div>
                <span className="text-[10px] text-gray-400 font-mono">{m.url_prefix}</span>
              </div>
              <div className="flex gap-1 shrink-0 items-center">
                <CopyBtn value={serverTestCmd(m)} />
                <span className="text-[10px] text-gray-300">服务器测试</span>
                <div className="w-px h-3 bg-gray-200 mx-0.5" />
                <button onClick={() => handleTest(m)} disabled={testing === m.id}
                  className="px-1.5 py-0.5 rounded text-[11px] border border-gray-200 text-gray-400 hover:text-blue-500 hover:border-blue-300 transition-colors disabled:opacity-40">
                  {testing === m.id ? "…" : "本地测试"}
                </button>
                {!m.is_builtin && (
                  <button onClick={() => deleteMirrorMut.mutate(m.id)}
                    className="text-[11px] text-gray-300 hover:text-red-400 transition-colors">x</button>
                )}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

/// 全局软件仓库（不关联具体服务器，不生成命令）
// ── 软件表单（新增 / 编辑 复用）──────────────────────────────────────────────

function SoftwareForm({ initial, onSave, onCancel, saving }: {
  initial?: ServerSoftware;
  onSave: (input: SaveSoftwareInput) => void;
  onCancel: () => void;
  saving: boolean;
}) {
  const { data: categories = [] } = useQuery({
    queryKey: ["software-categories"],
    queryFn: getSoftwareCategories,
    staleTime: 30000,
  });

  const [name, setName] = useState(initial?.name ?? "");
  const [desc, setDesc] = useState(initial?.description ?? "");
  const [category, setCategory] = useState(initial?.category ?? "cat-custom");
  const [installType, setInstallType] = useState<"package" | "github">(
    (initial?.install_type as "package" | "github") ?? "package"
  );
  const [installCmd, setInstallCmd] = useState(initial?.install_cmd ?? "");
  const [githubUrl, setGithubUrl] = useState(initial?.github_url ?? "");
  const [remotePath, setRemotePath] = useState(initial?.remote_path ?? "");
  const [postCmd, setPostCmd] = useState(initial?.post_cmd ?? "");

  const inputCls = "text-xs px-2 py-1.5 rounded border border-gray-200 bg-white font-mono focus:outline-none focus:border-blue-300";

  const handleSubmit = () => {
    if (!name.trim()) return;
    onSave({
      name: name.trim(),
      description: desc || undefined,
      category,
      install_type: installType,
      install_cmd: installType === "package" ? installCmd || undefined : undefined,
      github_url: installType === "github" ? githubUrl || undefined : undefined,
      remote_path: remotePath || undefined,
      post_cmd: postCmd || undefined,
    });
  };

  return (
    <div className="bg-gray-50 border border-gray-200 rounded-lg p-3 space-y-2">
      <div className="grid grid-cols-[1fr_1fr_auto_auto] gap-2 items-end">
        <div className="flex flex-col gap-0.5">
          <label className="text-[10px] text-gray-400">名称 *</label>
          <input value={name} onChange={(e) => setName(e.target.value)} placeholder="如: Redis"
            className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-white focus:outline-none focus:border-blue-300" />
        </div>
        <div className="flex flex-col gap-0.5">
          <label className="text-[10px] text-gray-400">描述</label>
          <input value={desc} onChange={(e) => setDesc(e.target.value)} placeholder="简短说明"
            className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-white focus:outline-none focus:border-blue-300" />
        </div>
        <div className="flex flex-col gap-0.5">
          <label className="text-[10px] text-gray-400">分类</label>
          <select value={category} onChange={(e) => setCategory(e.target.value)}
            className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-white focus:outline-none focus:border-blue-300">
            {categories.map((c) => (
              <option key={c.id} value={c.id}>{c.name}</option>
            ))}
          </select>
        </div>
        <div className="flex flex-col gap-0.5">
          <label className="text-[10px] text-gray-400">类型</label>
          <div className="flex rounded border border-gray-200 overflow-hidden h-[30px]">
            <button onClick={() => setInstallType("package")}
              className={`px-2 text-xs ${installType === "package" ? "bg-blue-50 text-blue-600" : "bg-white text-gray-400"}`}>
              系统包
            </button>
            <button onClick={() => setInstallType("github")}
              className={`px-2 text-xs border-l border-gray-200 ${installType === "github" ? "bg-blue-50 text-blue-600" : "bg-white text-gray-400"}`}>
              GitHub
            </button>
          </div>
        </div>
      </div>

      {installType === "package" ? (
        <div className="flex flex-col gap-0.5">
          <label className="text-[10px] text-gray-400">安装命令</label>
          <input value={installCmd} onChange={(e) => setInstallCmd(e.target.value)}
            placeholder="apt install -y redis-server && systemctl enable redis-server" className={inputCls} />
        </div>
      ) : (
        <>
          <div className="flex flex-col gap-0.5">
            <label className="text-[10px] text-gray-400">GitHub Release URL</label>
            <input value={githubUrl} onChange={(e) => setGithubUrl(e.target.value)}
              placeholder="https://github.com/user/repo/releases/download/v1.0/file.tar.gz" className={inputCls} />
          </div>
          <div className="flex flex-col gap-0.5">
            <label className="text-[10px] text-gray-400">下载目标路径</label>
            <input value={remotePath} onChange={(e) => setRemotePath(e.target.value)}
              placeholder="/opt/download/file.tar.gz" className={inputCls} />
          </div>
        </>
      )}
      <div className="flex flex-col gap-0.5">
        <label className="text-[10px] text-gray-400">安装后命令（选填）</label>
        <input value={postCmd} onChange={(e) => setPostCmd(e.target.value)}
          placeholder="systemctl enable xxx" className={inputCls} />
      </div>
      <div className="flex justify-end gap-2">
        <button onClick={onCancel}
          className="px-3 py-1.5 rounded text-xs border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors">
          取消
        </button>
        <button onClick={handleSubmit} disabled={!name.trim() || saving}
          className="px-3 py-1.5 rounded text-xs bg-blue-50 text-blue-600 border border-blue-200 hover:bg-blue-100 disabled:opacity-40">
          {saving ? "保存中…" : "保存"}
        </button>
      </div>
    </div>
  );
}

// ── 全局软件仓库 ────────────────────────────────────────────────────────────

// ── 分类标签管理栏 ──────────────────────────────────────────────────────────

function CategoryTagBar() {
  const qc = useQueryClient();
  const [adding, setAdding] = useState(false);
  const [newName, setNewName] = useState("");
  const [newColor, setNewColor] = useState("#6b7280");
  const [editingCat, setEditingCat] = useState<SoftwareCategory | null>(null);
  const [confirmDeleteId, setConfirmDeleteId] = useState<string | null>(null);

  const { data: categories = [] } = useQuery({
    queryKey: ["software-categories"],
    queryFn: getSoftwareCategories,
  });

  const addMut = useMutation({
    mutationFn: () => addSoftwareCategory({ name: newName.trim(), color: newColor }),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["software-categories"] });
      setAdding(false);
      setNewName("");
    },
  });

  const updateMut = useMutation({
    mutationFn: (cat: SoftwareCategory) => updateSoftwareCategory(cat.id, { name: cat.name, color: cat.color }),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["software-categories"] });
      setEditingCat(null);
    },
  });

  const deleteMut = useMutation({
    mutationFn: (id: string) => deleteSoftwareCategory(id),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["software-categories", "server-software"] });
      setConfirmDeleteId(null);
    },
  });

  return (
    <div className="space-y-2">
      <div className="flex items-center justify-between">
        <h5 className="text-xs font-semibold text-gray-600">分类标签</h5>
        <button onClick={() => setAdding((v) => !v)}
          className="text-[11px] text-gray-400 hover:text-blue-500 transition-colors">
          {adding ? "取消" : "+ 添加标签"}
        </button>
      </div>

      {/* 添加 */}
      {adding && (
        <div className="flex items-center gap-2">
          <input value={newName} onChange={(e) => setNewName(e.target.value)} placeholder="标签名称"
            className="text-xs px-2 py-1.5 rounded border border-gray-200 bg-white flex-1 focus:outline-none focus:border-blue-300" />
          <div className="flex gap-1">
            {PRESET_COLORS.map((c) => (
              <button key={c} onClick={() => setNewColor(c)}
                className={`w-5 h-5 rounded-full border-2 transition-colors ${newColor === c ? "border-gray-800 scale-110" : "border-transparent"}`}
                style={{ backgroundColor: c }} />
            ))}
          </div>
          <button onClick={() => addMut.mutate()} disabled={!newName.trim()}
            className="px-2.5 py-1.5 rounded text-xs bg-blue-50 text-blue-600 border border-blue-200 hover:bg-blue-100 disabled:opacity-40 shrink-0">
            添加
          </button>
        </div>
      )}

      {/* 标签列表 */}
      <div className="flex flex-wrap gap-1.5">
        {categories.map((cat) => (
          <div key={cat.id} className="group relative">
            {editingCat?.id === cat.id ? (
              <div className="flex items-center gap-1 bg-white border border-gray-200 rounded-full px-2 py-0.5">
                <input value={editingCat.name}
                  onChange={(e) => setEditingCat({ ...editingCat, name: e.target.value })}
                  className="text-[11px] w-16 bg-transparent outline-none" autoFocus />
                <div className="flex gap-0.5">
                  {PRESET_COLORS.map((c) => (
                    <button key={c} onClick={() => setEditingCat({ ...editingCat, color: c })}
                      className={`w-3 h-3 rounded-full border ${editingCat.color === c ? "border-gray-800" : "border-transparent"}`}
                      style={{ backgroundColor: c }} />
                  ))}
                </div>
                <button onClick={() => updateMut.mutate(editingCat)}
                  className="text-[10px] text-blue-500 hover:text-blue-700">保存</button>
                <button onClick={() => setEditingCat(null)}
                  className="text-[10px] text-gray-400">取消</button>
              </div>
            ) : (
              <span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-[11px] font-medium border cursor-default"
                style={{ backgroundColor: cat.color + "15", color: cat.color, borderColor: cat.color + "40" }}>
                {cat.name}
                <button onClick={() => setEditingCat({ ...cat })}
                  className="opacity-0 group-hover:opacity-100 text-[10px] hover:underline transition-opacity"
                  style={{ color: cat.color }}>
                  编辑
                </button>
                {confirmDeleteId === cat.id ? (
                  <>
                    <button onClick={() => deleteMut.mutate(cat.id)}
                      className="text-[10px] text-red-500 hover:underline">确认删除</button>
                    <button onClick={() => setConfirmDeleteId(null)}
                      className="text-[10px] text-gray-400">取消</button>
                  </>
                ) : (
                  <button onClick={() => setConfirmDeleteId(cat.id)}
                    className="opacity-0 group-hover:opacity-100 text-[10px] text-gray-400 hover:text-red-400 transition-opacity">
                    x
                  </button>
                )}
              </span>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}

// ── 全局软件仓库 ────────────────────────────────────────────────────────────

function SoftwareListGlobal() {
  const qc = useQueryClient();
  const [showAdd, setShowAdd] = useState(false);
  const [editingId, setEditingId] = useState<string | null>(null);
  const [confirmDeleteId, setConfirmDeleteId] = useState<string | null>(null);

  const { data: softwareList = [] } = useQuery({
    queryKey: ["server-software"],
    queryFn: getServerSoftwareList,
  });

  const { data: categories = [] } = useQuery({
    queryKey: ["software-categories"],
    queryFn: getSoftwareCategories,
    staleTime: 30000,
  });

  const catMap = new Map(categories.map((c) => [c.id, c]));

  const addMut = useMutation({
    mutationFn: (input: SaveSoftwareInput) => addServerSoftware(input),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["server-software"] });
      setShowAdd(false);
    },
  });

  const updateMut = useMutation({
    mutationFn: ({ id, input }: { id: string; input: SaveSoftwareInput }) => updateServerSoftware(id, input),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["server-software"] });
      setEditingId(null);
    },
  });

  const deleteMut = useMutation({
    mutationFn: (id: string) => deleteServerSoftware(id),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["server-software"] });
      setConfirmDeleteId(null);
    },
  });

  // 按分类分组，保持分类的排序
  const grouped: { cat: SoftwareCategory; items: ServerSoftware[] }[] = [];
  for (const cat of categories) {
    const items = softwareList.filter((sw) => sw.category === cat.id);
    if (items.length > 0) {
      grouped.push({ cat, items });
    }
  }
  // 没有分类的软件归入"未分类"
  const uncategorized = softwareList.filter((sw) => !catMap.has(sw.category ?? ""));
  if (uncategorized.length > 0) {
    grouped.push({ cat: { id: "", name: "未分类", color: "#6b7280", sort_order: 999 }, items: uncategorized });
  }

  return (
    <div className="space-y-3">
      {/* 分类标签管理 */}
      <CategoryTagBar />

      <div className="border-t border-gray-100 pt-3">
        <div className="flex items-center justify-between mb-2">
          <h5 className="text-xs font-semibold text-gray-600">软件列表</h5>
          <button onClick={() => { setShowAdd((v) => !v); setEditingId(null); }}
            className="text-[11px] text-gray-400 hover:text-blue-500 transition-colors">
            {showAdd ? "取消" : "+ 添加软件"}
          </button>
        </div>

        {showAdd && (
          <SoftwareForm
            onSave={(input) => addMut.mutate(input)}
            onCancel={() => setShowAdd(false)}
            saving={addMut.isPending}
          />
        )}

        {/* 按分类展示 */}
        <div className="space-y-3">
          {grouped.map(({ cat, items }) => (
            <div key={cat.id}>
              <div className="flex items-center gap-1.5 mb-1">
                <span className="text-[11px] font-medium px-2 py-0.5 rounded-full border"
                  style={{ backgroundColor: cat.color + "15", color: cat.color, borderColor: cat.color + "40" }}>
                  {cat.name}
                </span>
                <span className="text-[10px] text-gray-300">{items.length}</span>
              </div>
              <div className="space-y-1">
                {items.map((sw) =>
                  editingId === sw.id ? (
                    <SoftwareForm
                      key={sw.id}
                      initial={sw}
                      onSave={(input) => updateMut.mutate({ id: sw.id, input })}
                      onCancel={() => setEditingId(null)}
                      saving={updateMut.isPending}
                    />
                  ) : (
                    <div key={sw.id} className="flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-100 bg-gray-50/50 group">
                      <span className="text-xs font-medium text-gray-700">{sw.name}</span>
                      <span className={`text-[10px] px-1 py-0.5 rounded ${
                        sw.install_type === "github" ? "bg-gray-800 text-white" : "bg-blue-50 text-blue-600"
                      }`}>
                        {sw.install_type === "github" ? "GitHub" : "package"}
                      </span>
                      {sw.description && (
                        <span className="text-[10px] text-gray-400">{sw.description}</span>
                      )}
                      {sw.install_cmd && (
                        <div className="flex items-center gap-1 min-w-0 flex-1">
                          <span className="text-[10px] text-gray-400 font-mono truncate" title={sw.install_cmd}>{sw.install_cmd}</span>
                          <CopyBtn value={sw.install_cmd} />
                        </div>
                      )}
                      {sw.github_url && (
                        <div className="flex items-center gap-1 min-w-0 flex-1">
                          <span className="text-[10px] text-gray-400 font-mono truncate" title={sw.github_url}>{sw.github_url}</span>
                          <CopyBtn value={sw.github_url} />
                        </div>
                      )}
                      <div className="ml-auto flex gap-1 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
                        <button onClick={() => { setEditingId(sw.id); setShowAdd(false); }}
                          className="px-1.5 py-0.5 rounded text-[11px] border border-gray-200 text-gray-400 hover:text-blue-500 hover:border-blue-300 transition-colors">
                          编辑
                        </button>
                        {confirmDeleteId === sw.id ? (
                          <div className="flex gap-0.5">
                            <button onClick={() => deleteMut.mutate(sw.id)}
                              className="px-1.5 py-0.5 rounded text-[11px] border border-red-300 bg-red-50 text-red-600 hover:bg-red-100 transition-colors">
                              确认
                            </button>
                            <button onClick={() => setConfirmDeleteId(null)}
                              className="px-1.5 py-0.5 rounded text-[11px] border border-gray-200 text-gray-400 transition-colors">
                              取消
                            </button>
                          </div>
                        ) : (
                          <button onClick={() => setConfirmDeleteId(sw.id)}
                            className="px-1.5 py-0.5 rounded text-[11px] border border-gray-200 text-gray-300 hover:text-red-400 hover:border-red-200 transition-colors">
                            删除
                          </button>
                        )}
                      </div>
                    </div>
                  )
                )}
              </div>
            </div>
          ))}
        </div>

        {softwareList.length === 0 && !showAdd && (
          <p className="text-[11px] text-gray-300 text-center py-4">暂无软件，点击「+ 添加软件」开始录入</p>
        )}
      </div>
    </div>
  );
}

// ── 服务器级：镜像选择 + 命令生成（嵌入 ServerCard）────────────────────────

export { MirrorManager as ServerMirrorSelector };
export { SoftwareList as ServerSoftwareCommands };
