import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import {
  getCloudProducts, addCloudProduct, editCloudProduct, deleteCloudProduct,
  getCloudDatabases, addCloudDatabase, editCloudDatabase, deleteCloudDatabase,
  getDockerApps, addDockerApp, editDockerApp, deleteDockerApp,
  type CloudProduct, type SaveCloudProductInput,
  type CloudDatabase, type SaveCloudDatabaseInput,
  type DockerApp, type SaveDockerAppInput,
} from "../../lib/commands";

// ── 厂商 & 产品类型配置 ────────────────────────────────────────────────────────

const VENDORS = [
  { value: "aliyun",  label: "阿里云",  color: "bg-orange-100 text-orange-700 border-orange-200", dot: "bg-orange-500" },
  { value: "tencent", label: "腾讯云",  color: "bg-blue-100 text-blue-700 border-blue-200",       dot: "bg-blue-500" },
  { value: "local",   label: "本地",    color: "bg-green-100 text-green-700 border-green-200",    dot: "bg-green-500" },
  { value: "custom",  label: "自定义",  color: "bg-gray-100 text-gray-600 border-gray-200",        dot: "bg-gray-400" },
];

const PRODUCT_TYPES: Record<string, { value: string; label: string; icon: string }[]> = {
  aliyun: [
    { value: "ecs",    label: "云服务器 ECS",   icon: "🖥" },
    { value: "rds",    label: "云数据库 RDS",   icon: "🗄" },
    { value: "oss",    label: "对象存储 OSS",   icon: "🪣" },
    { value: "cdn",    label: "CDN",           icon: "🌐" },
    { value: "domain", label: "域名",           icon: "🔗" },
    { value: "redis",  label: "云数据库 Redis", icon: "⚡" },
    { value: "clb",    label: "负载均衡 CLB",   icon: "⚖️" },
    { value: "custom", label: "其他",           icon: "📦" },
  ],
  tencent: [
    { value: "cvm",    label: "云服务器 CVM",   icon: "🖥" },
    { value: "tdsql",  label: "云数据库 TDSQL", icon: "🗄" },
    { value: "cos",    label: "对象存储 COS",   icon: "🪣" },
    { value: "cdn",    label: "CDN",           icon: "🌐" },
    { value: "domain", label: "域名",           icon: "🔗" },
    { value: "redis",  label: "云数据库 Redis", icon: "⚡" },
    { value: "clb",    label: "负载均衡 CLB",   icon: "⚖️" },
    { value: "custom", label: "其他",           icon: "📦" },
  ],
  local: [
    { value: "mysql",      label: "MySQL",       icon: "🐬" },
    { value: "postgresql", label: "PostgreSQL",  icon: "🐘" },
    { value: "redis",      label: "Redis",       icon: "⚡" },
    { value: "mongodb",    label: "MongoDB",     icon: "🍃" },
    { value: "sqlite",     label: "SQLite",      icon: "📁" },
    { value: "docker",     label: "Docker 应用", icon: "🐳" },
    { value: "custom",     label: "其他",         icon: "📦" },
  ],
  custom: [
    { value: "custom", label: "自定义产品", icon: "📦" },
    { value: "ecs",    label: "云服务器",   icon: "🖥" },
    { value: "rds",    label: "云数据库",   icon: "🗄" },
    { value: "oss",    label: "对象存储",   icon: "🪣" },
    { value: "cdn",    label: "CDN",       icon: "🌐" },
    { value: "domain", label: "域名",       icon: "🔗" },
  ],
};

const STATUS_OPTIONS = [
  { value: "active",  label: "运行中", color: "text-emerald-600 bg-emerald-50 border-emerald-200" },
  { value: "stopped", label: "已停止", color: "text-amber-600 bg-amber-50 border-amber-200" },
  { value: "expired", label: "已到期", color: "text-red-600 bg-red-50 border-red-200" },
];

// ── 连接方式字段定义 ──────────────────────────────────────────────────────────

type ConnSchema =
  | { type: "db" }       // 内网地址/端口 + 外网地址/端口
  | { type: "server" }   // 公网IP / 内网IP / SSH端口
  | { type: "clb" }      // 公网IP / 内网IP
  | { type: "oss" }      // 对象存储：Bucket / Endpoint / AccessKey / Secret / ACL
  | { type: "docker" }   // Docker 应用：镜像 / 端口映射 / 网络
  | null;                // 无连接字段

function getConnSchema(productType: string): ConnSchema {
  if (["rds", "tdsql", "redis", "mysql", "postgresql", "mongodb"].includes(productType)) return { type: "db" };
  if (["ecs", "cvm"].includes(productType))             return { type: "server" };
  if (productType === "clb")                            return { type: "clb" };
  if (["oss", "cos"].includes(productType))             return { type: "oss" };
  if (productType === "docker")                         return { type: "docker" };
  return null;
}

interface ConnData {
  internal_host?: string;
  internal_port?: string;
  external_host?: string;
  external_port?: string;
  public_ip?: string;
  private_ip?: string;
  ssh_port?: string;
  // OSS / COS
  bucket_name?: string;
  oss_endpoint?: string;
  oss_region?: string;
  access_key?: string;
  access_secret?: string;
  acl?: string;
  force_path_style?: string; // "true" | "false"
  // Docker
  docker_image?: string;
  docker_container?: string;
  docker_ports?: string;   // "8080:80,443:443"（逗号分隔）
  docker_network?: string;
  docker_compose?: string; // compose 文件路径
}

function parseConnData(configData: string): ConnData {
  try { return JSON.parse(configData) as ConnData; }
  catch { return {}; }
}

function serializeConnData(conn: ConnData): string {
  const cleaned = Object.fromEntries(
    Object.entries(conn).filter(([, v]) => v && (v as string).trim())
  );
  return Object.keys(cleaned).length > 0 ? JSON.stringify(cleaned) : "{}";
}

// ── 辅助函数 ──────────────────────────────────────────────────────────────────

function getVendor(v: string) {
  return VENDORS.find((x) => x.value === v) ?? VENDORS[VENDORS.length - 1];
}
function getProductTypes(vendor: string) {
  return PRODUCT_TYPES[vendor] ?? PRODUCT_TYPES.custom;
}
function getProductType(vendor: string, pt: string) {
  return getProductTypes(vendor).find((x) => x.value === pt) ?? { value: pt, label: pt, icon: "📦" };
}
function getStatusStyle(s: string) {
  return STATUS_OPTIONS.find((x) => x.value === s)?.color ?? STATUS_OPTIONS[0].color;
}
function getStatusLabel(s: string) {
  return STATUS_OPTIONS.find((x) => x.value === s)?.label ?? s;
}
function daysUntilExpiry(expireAt: string): number {
  return Math.ceil((new Date(expireAt).getTime() - Date.now()) / (1000 * 60 * 60 * 24));
}

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

// ── 连接方式表单区块（在模态框内复用） ─────────────────────────────────────────

function ConnFormSection({ schema, conn, onChange }: {
  schema: ConnSchema;
  conn: ConnData;
  onChange: (key: keyof ConnData, val: string) => void;
}) {
  if (!schema) return null;

  const [showSecret, setShowSecret] = useState(false);

  const inputCls = "text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 font-mono w-full";
  const labelCls = "text-[11px] text-gray-400 text-right";

  return (
    <>
      {/* 分隔线标题 */}
      <div className="col-span-2 flex items-center gap-2 pt-1">
        <div className="flex-1 h-px bg-gray-100" />
        <span className="text-[11px] text-gray-400 font-medium shrink-0">连接方式</span>
        <div className="flex-1 h-px bg-gray-100" />
      </div>

      {schema.type === "db" && (
        <>
          <label className={labelCls}>内网地址</label>
          <div className="flex gap-2">
            <input value={conn.internal_host ?? ""} onChange={(e) => onChange("internal_host", e.target.value)}
              placeholder="rm-xxxxx.mysql.rds.aliyuncs.com" className={`${inputCls} flex-1`} />
            <input value={conn.internal_port ?? ""} onChange={(e) => onChange("internal_port", e.target.value)}
              placeholder="3306" className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 font-mono w-20" />
          </div>

          <label className={labelCls}>外网地址</label>
          <div className="flex gap-2">
            <input value={conn.external_host ?? ""} onChange={(e) => onChange("external_host", e.target.value)}
              placeholder="rm-xxxxx.mysql.rds.aliyuncs.com（外网）" className={`${inputCls} flex-1`} />
            <input value={conn.external_port ?? ""} onChange={(e) => onChange("external_port", e.target.value)}
              placeholder="3306" className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 font-mono w-20" />
          </div>
        </>
      )}

      {(schema.type === "server" || schema.type === "clb") && (
        <>
          <label className={labelCls}>公网 IP</label>
          <input value={conn.public_ip ?? ""} onChange={(e) => onChange("public_ip", e.target.value)}
            placeholder="1.2.3.4" className={inputCls} />

          <label className={labelCls}>内网 IP</label>
          <input value={conn.private_ip ?? ""} onChange={(e) => onChange("private_ip", e.target.value)}
            placeholder="172.16.x.x" className={inputCls} />

          {schema.type === "server" && (
            <>
              <label className={labelCls}>SSH 端口</label>
              <input value={conn.ssh_port ?? ""} onChange={(e) => onChange("ssh_port", e.target.value)}
                placeholder="22" className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 font-mono w-20" />
            </>
          )}
        </>
      )}

      {schema.type === "oss" && (
        <>
          <label className={labelCls}>Bucket *</label>
          <input value={conn.bucket_name ?? ""} onChange={(e) => onChange("bucket_name", e.target.value)}
            placeholder="例：my-bucket" className={inputCls} />

          <label className={labelCls}>Region *</label>
          <input value={conn.oss_region ?? ""} onChange={(e) => onChange("oss_region", e.target.value)}
            placeholder="例：oss-cn-hangzhou" className={inputCls} />

          <label className={labelCls}>Endpoint</label>
          <input value={conn.oss_endpoint ?? ""} onChange={(e) => onChange("oss_endpoint", e.target.value)}
            placeholder="例：https://oss-cn-hangzhou.aliyuncs.com" className={inputCls} />

          <label className={labelCls}>Access Key</label>
          <input value={conn.access_key ?? ""} onChange={(e) => onChange("access_key", e.target.value)}
            placeholder="AccessKey ID" className={inputCls} />

          <label className={labelCls}>Access Secret</label>
          <div className="flex gap-2">
            <input
              type={showSecret ? "text" : "password"}
              value={conn.access_secret ?? ""} onChange={(e) => onChange("access_secret", e.target.value)}
              placeholder="AccessKey Secret"
              className={`${inputCls} flex-1`} />
            <button type="button" onClick={() => setShowSecret((v) => !v)}
              className="px-2 py-1 rounded border border-gray-200 text-[11px] text-gray-400 hover:text-gray-600 shrink-0">
              {showSecret ? "隐藏" : "显示"}
            </button>
          </div>

          <label className={labelCls}>ACL</label>
          <input value={conn.acl ?? ""} onChange={(e) => onChange("acl", e.target.value)}
            placeholder="例：public-read / private" className={inputCls} />

          <label className={labelCls}>Force Path Style</label>
          <div className="flex items-center gap-2">
            <button
              type="button"
              onClick={() => onChange("force_path_style", conn.force_path_style === "true" ? "false" : "true")}
              className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors ${
                conn.force_path_style === "true" ? "bg-blue-500" : "bg-gray-200"
              }`}
            >
              <span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${
                conn.force_path_style === "true" ? "translate-x-4" : "translate-x-0"
              }`} />
            </button>
            <span className="text-[11px] text-gray-500">
              {conn.force_path_style === "true" ? "已启用" : "已禁用"}
            </span>
          </div>
        </>
      )}

      {schema.type === "docker" && (
        <>
          <label className={labelCls}>镜像 *</label>
          <input value={conn.docker_image ?? ""} onChange={(e) => onChange("docker_image", e.target.value)}
            placeholder="例：nginx:alpine / postgres:15"
            className={inputCls} />

          <label className={labelCls}>容器名</label>
          <input value={conn.docker_container ?? ""} onChange={(e) => onChange("docker_container", e.target.value)}
            placeholder="例：my-nginx"
            className={inputCls} />

          <label className={labelCls}>端口映射</label>
          <input value={conn.docker_ports ?? ""} onChange={(e) => onChange("docker_ports", e.target.value)}
            placeholder="例：8080:80, 443:443（逗号分隔）"
            className={inputCls} />

          <label className={labelCls}>网络</label>
          <input value={conn.docker_network ?? ""} onChange={(e) => onChange("docker_network", e.target.value)}
            placeholder="例：bridge / my-network"
            className={inputCls} />

          <label className={labelCls}>Compose 路径</label>
          <input value={conn.docker_compose ?? ""} onChange={(e) => onChange("docker_compose", e.target.value)}
            placeholder="例：~/projects/myapp/docker-compose.yml"
            className={inputCls} />
        </>
      )}
    </>
  );
}

// ── 模态表单 ──────────────────────────────────────────────────────────────────

function CloudProductModal({ initial, onSave, onClose, saving }: {
  initial?: CloudProduct;
  onSave: (input: SaveCloudProductInput) => void;
  onClose: () => void;
  saving: boolean;
}) {
  const initConn = initial ? parseConnData(initial.config_data) : {};

  const [vendor, setVendor]           = useState(initial?.vendor ?? "aliyun");
  const [productType, setProductType] = useState(initial?.product_type ?? "ecs");
  const [name, setName]               = useState(initial?.name ?? "");
  const [region, setRegion]           = useState(initial?.region ?? "");
  const [instanceId, setInstanceId]   = useState(initial?.instance_id ?? "");
  const [endpoint, setEndpoint]       = useState(initial?.endpoint ?? "");
  const [expireAt, setExpireAt]       = useState(initial?.expire_at?.slice(0, 10) ?? "");
  const [status, setStatus]           = useState(initial?.status ?? "active");
  const [notes, setNotes]             = useState(initial?.notes ?? "");
  const [conn, setConn]               = useState<ConnData>(initConn);

  const handleVendorChange = (v: string) => {
    setVendor(v);
    setProductType(getProductTypes(v)[0].value);
    setConn({});
  };
  const handleProductTypeChange = (pt: string) => {
    setProductType(pt);
    setConn({});
  };
  const handleConnChange = (key: keyof ConnData, val: string) => {
    setConn((prev) => ({ ...prev, [key]: val }));
  };

  const handleSubmit = () => {
    if (!name.trim()) return;
    onSave({
      vendor,
      product_type: productType,
      name: name.trim(),
      region: region.trim() || undefined,
      instance_id: instanceId.trim() || undefined,
      endpoint: endpoint.trim() || undefined,
      expire_at: expireAt || undefined,
      status,
      config_data: serializeConnData(conn),
      notes: notes.trim() || undefined,
    });
  };

  const schema = getConnSchema(productType);
  const types = getProductTypes(vendor);

  return (
    <div className="fixed inset-0 bg-black/30 z-50 flex items-center justify-center" onClick={onClose}>
      <div className="bg-white rounded-xl border border-gray-200 shadow-xl w-[560px] flex flex-col max-h-[90vh]"
        onClick={(e) => e.stopPropagation()}>
        <div className="flex items-center justify-between px-6 py-4 border-b border-gray-100 shrink-0">
          <h3 className="text-sm font-bold text-gray-800">
            {initial ? "编辑云产品" : "添加云产品"}
          </h3>
          <button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-lg leading-none">×</button>
        </div>

        <div className="px-6 py-4 overflow-y-auto">
          <div className="grid grid-cols-[80px_1fr] gap-x-3 gap-y-3 items-center">

            <label className="text-[11px] text-gray-400 text-right">厂商 *</label>
            <div className="flex gap-2">
              {VENDORS.map((v) => (
                <button key={v.value} onClick={() => handleVendorChange(v.value)}
                  className={`px-3 py-1.5 rounded-lg text-xs font-medium border-2 transition-colors ${
                    vendor === v.value ? `border-transparent ${v.color}` : "border-gray-200 text-gray-500 hover:border-gray-300"
                  }`}>
                  {v.label}
                </button>
              ))}
            </div>

            <label className="text-[11px] text-gray-400 text-right">产品类型 *</label>
            <select value={productType} onChange={(e) => handleProductTypeChange(e.target.value)}
              className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300">
              {types.map((t) => <option key={t.value} value={t.value}>{t.icon} {t.label}</option>)}
            </select>

            <label className="text-[11px] text-gray-400 text-right">名称 *</label>
            <input value={name} onChange={(e) => setName(e.target.value)}
              placeholder="例：生产环境数据库"
              className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300" />

            <label className="text-[11px] text-gray-400 text-right">地域</label>
            <input value={region} onChange={(e) => setRegion(e.target.value)}
              placeholder="例：cn-hangzhou / ap-guangzhou"
              className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 font-mono" />

            <label className="text-[11px] text-gray-400 text-right">实例 ID</label>
            <input value={instanceId} onChange={(e) => setInstanceId(e.target.value)}
              placeholder="例：i-xxxxx / rm-xxxxx"
              className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 font-mono" />

            {/* 无连接字段时才显示通用端点 */}
            {!schema && (
              <>
                <label className="text-[11px] text-gray-400 text-right">访问端点</label>
                <input value={endpoint} onChange={(e) => setEndpoint(e.target.value)}
                  placeholder="IP 地址、域名或连接字符串"
                  className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 font-mono" />
              </>
            )}

            {/* 连接方式字段（按类型自适应） */}
            <ConnFormSection schema={schema} conn={conn} onChange={handleConnChange} />

            <label className="text-[11px] text-gray-400 text-right">到期时间</label>
            <input type="date" value={expireAt} onChange={(e) => setExpireAt(e.target.value)}
              className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 w-40" />

            <label className="text-[11px] text-gray-400 text-right">状态</label>
            <select value={status} onChange={(e) => setStatus(e.target.value)}
              className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 w-32">
              {STATUS_OPTIONS.map((s) => <option key={s.value} value={s.value}>{s.label}</option>)}
            </select>

            <label className="text-[11px] text-gray-400 text-right self-start mt-2">备注</label>
            <textarea value={notes} onChange={(e) => setNotes(e.target.value)}
              placeholder="规格配置、密码、到期提醒等…" rows={3}
              className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 resize-y min-h-[60px]" />
          </div>
        </div>

        <div className="flex items-center justify-end gap-2 px-6 py-3 border-t border-gray-100 bg-gray-50/50 shrink-0">
          <button onClick={onClose}
            className="px-3 py-1.5 rounded-md text-xs border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors">
            取消
          </button>
          <button onClick={handleSubmit} disabled={saving || !name.trim()}
            className="px-4 py-1.5 rounded-md text-xs font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors disabled:opacity-40">
            {saving ? "保存中…" : "保存"}
          </button>
        </div>
      </div>
    </div>
  );
}

// ── OSS Secret 展示行（带显示/隐藏） ─────────────────────────────────────────

function OssSecretRow({ secret }: { secret: string }) {
  const [show, setShow] = useState(false);
  return (
    <div className="flex items-center gap-2 text-[11px]">
      <span className="text-gray-400 shrink-0 w-14 text-right">Secret</span>
      <span className="font-mono text-gray-700 flex-1 min-w-0 truncate">
        {show ? secret : "••••••••••••••••"}
      </span>
      <button onClick={() => setShow((v) => !v)}
        className="text-[10px] text-gray-400 hover:text-gray-600 shrink-0">
        {show ? "隐藏" : "显示"}
      </button>
      {show && <CopyBtn value={secret} />}
    </div>
  );
}

// ── 连接信息展示区块（卡片内） ────────────────────────────────────────────────

function ConnInfoBlock({ productType, configData }: { productType: string; configData: string }) {
  const schema = getConnSchema(productType);
  if (!schema) return null;

  const conn = parseConnData(configData);
  const hasAny = Object.values(conn).some((v) => v && (v as string).trim());
  if (!hasAny) return null;

  const rowCls = "flex items-center gap-2 text-[11px]";
  const labelCls = "text-gray-400 shrink-0 w-14 text-right";

  const buildOssText = () => {
    const lines: string[] = [];
    if (conn.bucket_name)   lines.push(`Bucket Name: ${conn.bucket_name}`);
    if (conn.oss_region)    lines.push(`Region: ${conn.oss_region}`);
    if (conn.oss_endpoint)  lines.push(`Endpoint: ${conn.oss_endpoint}`);
    if (conn.access_key)    lines.push(`Access Key: ${conn.access_key}`);
    if (conn.access_secret) lines.push(`Access Secret: ${conn.access_secret}`);
    if (conn.acl)           lines.push(`ACL: ${conn.acl}`);
    if (conn.force_path_style) lines.push(`Force Path Style: ${conn.force_path_style}`);
    return lines.join("\n");
  };

  return (
    <div className="mt-2 bg-blue-50/50 border border-blue-100 rounded-lg px-3 py-2.5 space-y-1.5">
      <div className="flex items-center justify-between mb-1.5">
        <p className="text-[10px] font-semibold text-blue-500 uppercase tracking-wider">连接方式</p>
        {schema.type === "oss" && (
          <CopyBtn value={buildOssText()} label="复制配置" />
        )}
      </div>

      {schema.type === "db" && (
        <>
          {(conn.internal_host || conn.internal_port) && (
            <div className={rowCls}>
              <span className={labelCls}>内网</span>
              <span className="font-mono text-gray-700 flex-1 min-w-0 truncate">
                {[conn.internal_host, conn.internal_port].filter(Boolean).join(":")}
              </span>
              {conn.internal_host && <CopyBtn value={[conn.internal_host, conn.internal_port].filter(Boolean).join(":")} />}
            </div>
          )}
          {(conn.external_host || conn.external_port) && (
            <div className={rowCls}>
              <span className={labelCls}>外网</span>
              <span className="font-mono text-gray-700 flex-1 min-w-0 truncate">
                {[conn.external_host, conn.external_port].filter(Boolean).join(":")}
              </span>
              {conn.external_host && <CopyBtn value={[conn.external_host, conn.external_port].filter(Boolean).join(":")} />}
            </div>
          )}
        </>
      )}

      {(schema.type === "server" || schema.type === "clb") && (
        <>
          {conn.public_ip && (
            <div className={rowCls}>
              <span className={labelCls}>公网 IP</span>
              <span className="font-mono text-gray-700">{conn.public_ip}</span>
              <CopyBtn value={conn.public_ip} />
            </div>
          )}
          {conn.private_ip && (
            <div className={rowCls}>
              <span className={labelCls}>内网 IP</span>
              <span className="font-mono text-gray-700">{conn.private_ip}</span>
              <CopyBtn value={conn.private_ip} />
            </div>
          )}
          {schema.type === "server" && conn.ssh_port && (
            <div className={rowCls}>
              <span className={labelCls}>SSH 端口</span>
              <span className="font-mono text-gray-700">{conn.ssh_port}</span>
              <CopyBtn value={conn.ssh_port} />
            </div>
          )}
        </>
      )}

      {schema.type === "oss" && (
        <>
          {conn.bucket_name && (
            <div className={rowCls}>
              <span className={labelCls}>Bucket</span>
              <span className="font-mono text-gray-700 flex-1 min-w-0 truncate">{conn.bucket_name}</span>
              <CopyBtn value={conn.bucket_name} />
            </div>
          )}
          {conn.oss_region && (
            <div className={rowCls}>
              <span className={labelCls}>Region</span>
              <span className="font-mono text-gray-700 flex-1 min-w-0 truncate">{conn.oss_region}</span>
              <CopyBtn value={conn.oss_region} />
            </div>
          )}
          {conn.oss_endpoint && (
            <div className={rowCls}>
              <span className={labelCls}>Endpoint</span>
              <span className="font-mono text-gray-700 flex-1 min-w-0 truncate">{conn.oss_endpoint}</span>
              <CopyBtn value={conn.oss_endpoint} />
            </div>
          )}
          {conn.access_key && (
            <div className={rowCls}>
              <span className={labelCls}>AK</span>
              <span className="font-mono text-gray-700 flex-1 min-w-0 truncate">{conn.access_key}</span>
              <CopyBtn value={conn.access_key} />
            </div>
          )}
          {conn.access_secret && (
            <OssSecretRow secret={conn.access_secret} />
          )}
          {conn.acl && (
            <div className={rowCls}>
              <span className={labelCls}>ACL</span>
              <span className="font-mono text-gray-700">{conn.acl}</span>
            </div>
          )}
          {conn.force_path_style === "true" && (
            <div className={rowCls}>
              <span className={labelCls}>Path Style</span>
              <span className="text-gray-500">Force Path Style 已启用</span>
            </div>
          )}
        </>
      )}

      {schema.type === "docker" && (
        <>
          {conn.docker_image && (
            <div className={rowCls}>
              <span className={labelCls}>镜像</span>
              <span className="font-mono text-gray-700 flex-1 min-w-0 truncate">{conn.docker_image}</span>
              <CopyBtn value={conn.docker_image} />
            </div>
          )}
          {conn.docker_container && (
            <div className={rowCls}>
              <span className={labelCls}>容器名</span>
              <span className="font-mono text-gray-700 flex-1 min-w-0 truncate">{conn.docker_container}</span>
              <CopyBtn value={conn.docker_container} />
            </div>
          )}
          {conn.docker_ports && (
            <div className="space-y-1">
              <p className="text-[10px] font-semibold text-blue-400 uppercase tracking-wider mt-0.5">端口映射</p>
              {conn.docker_ports.split(",").map((p) => p.trim()).filter(Boolean).map((mapping) => {
                const [hostPort, containerPort] = mapping.split(":");
                return (
                  <div key={mapping} className={rowCls}>
                    <span className={labelCls}>宿主 → 容器</span>
                    <span className="font-mono text-gray-700">
                      <span className="text-blue-600">{hostPort}</span>
                      <span className="text-gray-400"> → </span>
                      <span>{containerPort ?? hostPort}</span>
                    </span>
                    <CopyBtn value={mapping} />
                  </div>
                );
              })}
            </div>
          )}
          {conn.docker_network && (
            <div className={rowCls}>
              <span className={labelCls}>网络</span>
              <span className="font-mono text-gray-700">{conn.docker_network}</span>
              <CopyBtn value={conn.docker_network} />
            </div>
          )}
          {conn.docker_compose && (
            <div className={rowCls}>
              <span className={labelCls}>Compose</span>
              <span className="font-mono text-gray-600 flex-1 min-w-0 truncate text-[10px]">{conn.docker_compose}</span>
              <CopyBtn value={conn.docker_compose} />
            </div>
          )}
          {conn.docker_ports && (
            <div className="mt-1.5 rounded-md border border-cyan-100 bg-cyan-50/60 px-2.5 py-2">
              <div className="flex items-center gap-1.5 mb-1">
                <span className="text-[10px] font-semibold text-cyan-600 uppercase tracking-wider">启动命令</span>
              </div>
              <div className="flex items-start gap-2">
                <code className="text-[11px] font-mono text-cyan-800 break-all flex-1 leading-relaxed select-all">
                  {`docker run ${conn.docker_ports.split(",").map((p) => p.trim()).filter(Boolean).map((m) => `-p ${m}`).join(" ")}${conn.docker_network ? ` --network ${conn.docker_network}` : ""}${conn.docker_container ? ` --name ${conn.docker_container}` : ""} ${conn.docker_image ?? "<镜像>"}`}
                </code>
                <CopyBtn value={`docker run ${conn.docker_ports.split(",").map((p) => p.trim()).filter(Boolean).map((m) => `-p ${m}`).join(" ")}${conn.docker_network ? ` --network ${conn.docker_network}` : ""}${conn.docker_container ? ` --name ${conn.docker_container}` : ""} ${conn.docker_image ?? "<镜像>"}`} />
              </div>
            </div>
          )}
        </>
      )}
    </div>
  );
}

// ── DATABASE_URL 生成器 ───────────────────────────────────────────────────────

const DB_DRIVERS = [
  { value: "postgresql", label: "PostgreSQL", defaultPort: "5432" },
  { value: "mysql",      label: "MySQL",      defaultPort: "3306" },
  { value: "redis",      label: "Redis",      defaultPort: "6379" },
  { value: "mongodb",    label: "MongoDB",    defaultPort: "27017" },
  { value: "sqlserver",  label: "SQL Server", defaultPort: "1433" },
];

function buildDatabaseUrl(
  driver: string,
  host: string,
  port: string,
  username: string,
  password: string,
  dbname: string,
): string {
  const u = username || "<用户名>";
  const p = password || "<密码>";
  const h = host     || "<主机>";
  const po = port    || "<端口>";

  if (driver === "redis") {
    // Redis 通常格式：redis://:password@host:port
    return `redis://:${p}@${h}:${po}`;
  }
  if (driver === "mongodb") {
    const d = dbname || "<库名>";
    return `mongodb://${u}:${p}@${h}:${po}/${d}`;
  }
  const d = dbname || "<库名>";
  return `${driver}://${u}:${p}@${h}:${po}/${d}`;
}

// ── 数据库表单模态框 ──────────────────────────────────────────────────────────

function DatabaseModal({ productId, initial, onSave, onClose, saving }: {
  productId: string;
  initial?: CloudDatabase;
  onSave: (input: SaveCloudDatabaseInput) => void;
  onClose: () => void;
  saving: boolean;
}) {
  const [name, setName]         = useState(initial?.name ?? "");
  const [driver, setDriver]     = useState(initial?.db_driver ?? "mysql");
  const [username, setUsername] = useState(initial?.username ?? "");
  const [password, setPassword] = useState(initial?.password ?? "");
  const [charset, setCharset]   = useState(initial?.charset ?? "utf8mb4");
  const [notes, setNotes]       = useState(initial?.notes ?? "");
  const [showPwd, setShowPwd]   = useState(false);

  const handleSubmit = () => {
    if (!name.trim()) return;
    onSave({
      product_id: productId,
      name: name.trim(),
      db_driver: driver,
      username: username.trim() || undefined,
      password: password.trim() || undefined,
      charset,
      notes: notes.trim() || undefined,
    });
  };

  return (
    <div className="fixed inset-0 bg-black/30 z-50 flex items-center justify-center" onClick={onClose}>
      <div className="bg-white rounded-xl border border-gray-200 shadow-xl w-[440px] flex flex-col"
        onClick={(e) => e.stopPropagation()}>
        <div className="flex items-center justify-between px-5 py-3.5 border-b border-gray-100 shrink-0">
          <h3 className="text-sm font-bold text-gray-800">
            {initial ? "编辑数据库" : "添加数据库"}
          </h3>
          <button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-lg leading-none">×</button>
        </div>

        <div className="px-5 py-4">
          <div className="grid grid-cols-[72px_1fr] gap-x-3 gap-y-2.5 items-center">
            <label className="text-[11px] text-gray-400 text-right">驱动 *</label>
            <select value={driver} onChange={(e) => setDriver(e.target.value)}
              className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 w-40">
              {DB_DRIVERS.map((d) => <option key={d.value} value={d.value}>{d.label}</option>)}
            </select>

            <label className="text-[11px] text-gray-400 text-right">库名 *</label>
            <input value={name} onChange={(e) => setName(e.target.value)}
              placeholder="例：production_db"
              className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 font-mono" />

            <label className="text-[11px] text-gray-400 text-right">用户名</label>
            <input value={username} onChange={(e) => setUsername(e.target.value)}
              placeholder="例：app_user"
              className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 font-mono" />

            <label className="text-[11px] text-gray-400 text-right">密码</label>
            <div className="flex gap-2">
              <input
                type={showPwd ? "text" : "password"}
                value={password} onChange={(e) => setPassword(e.target.value)}
                placeholder="数据库密码"
                className="flex-1 text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 font-mono" />
              <button onClick={() => setShowPwd((v) => !v)}
                className="px-2 py-1 rounded border border-gray-200 text-[11px] text-gray-400 hover:text-gray-600 shrink-0">
                {showPwd ? "隐藏" : "显示"}
              </button>
            </div>

            {driver !== "redis" && (
              <>
                <label className="text-[11px] text-gray-400 text-right">字符集</label>
                <select value={charset} onChange={(e) => setCharset(e.target.value)}
                  className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 w-36">
                  <option value="utf8mb4">utf8mb4</option>
                  <option value="utf8">utf8</option>
                  <option value="latin1">latin1</option>
                  <option value="gbk">gbk</option>
                </select>
              </>
            )}

            <label className="text-[11px] text-gray-400 text-right self-start mt-2">备注</label>
            <textarea value={notes} onChange={(e) => setNotes(e.target.value)}
              placeholder="用途描述…" rows={2}
              className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 resize-none" />
          </div>
        </div>

        <div className="flex items-center justify-end gap-2 px-5 py-3 border-t border-gray-100 bg-gray-50/50 shrink-0">
          <button onClick={onClose}
            className="px-3 py-1.5 rounded-md text-xs border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors">
            取消
          </button>
          <button onClick={handleSubmit} disabled={saving || !name.trim()}
            className="px-4 py-1.5 rounded-md text-xs font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors disabled:opacity-40">
            {saving ? "保存中…" : "保存"}
          </button>
        </div>
      </div>
    </div>
  );
}

// ── DATABASE_URL 展示行 ───────────────────────────────────────────────────────

function DatabaseUrlRow({ label, url }: { label: string; url: string }) {
  const hasPlaceholder = url.includes("<");
  return (
    <div className="mt-1.5 rounded-md border border-indigo-100 bg-indigo-50/60 px-2.5 py-2">
      <div className="flex items-center gap-1.5 mb-1">
        <span className="text-[10px] font-semibold text-indigo-500 uppercase tracking-wider">{label}</span>
        {hasPlaceholder && (
          <span className="text-[10px] text-indigo-400">（含占位符，填写后自动更新）</span>
        )}
      </div>
      <div className="flex items-start gap-2">
        <code className="text-[11px] font-mono text-indigo-700 break-all flex-1 leading-relaxed select-all">
          DATABASE_URL=<span className={hasPlaceholder ? "text-amber-600" : ""}>{url}</span>
        </code>
        <CopyBtn value={`DATABASE_URL=${url}`} />
      </div>
    </div>
  );
}

// ── 数据库子列表（仅数据库类实例显示） ────────────────────────────────────────

function DatabaseSubList({ productId, configData }: {
  productId: string;
  configData: string;
}) {
  const qc = useQueryClient();
  const queryKey = ["cloud-databases", productId];
  const conn = parseConnData(configData);

  const [showAdd, setShowAdd]       = useState(false);
  const [editingDb, setEditingDb]   = useState<CloudDatabase | null>(null);
  const [showPwdMap, setShowPwdMap] = useState<Record<string, boolean>>({});

  const { data: databases = [] } = useQuery({
    queryKey,
    queryFn: () => getCloudDatabases(productId),
  });

  const addMut = useMutation({
    mutationFn: (input: SaveCloudDatabaseInput) => addCloudDatabase(input),
    onSuccess: () => { qc.invalidateQueries({ queryKey }); setShowAdd(false); },
  });
  const editMut = useMutation({
    mutationFn: ({ id, input }: { id: string; input: SaveCloudDatabaseInput }) => editCloudDatabase(id, input),
    onSuccess: () => { qc.invalidateQueries({ queryKey }); setEditingDb(null); },
  });
  const deleteMut = useMutation({
    mutationFn: (id: string) => deleteCloudDatabase(id),
    onSuccess: () => qc.invalidateQueries({ queryKey }),
  });

  const togglePwd = (id: string) =>
    setShowPwdMap((prev) => ({ ...prev, [id]: !prev[id] }));

  return (
    <div className="mt-2 border border-gray-100 rounded-lg overflow-hidden">
      {/* 标题行 */}
      <div className="flex items-center justify-between px-3 py-2 bg-gray-50/80 border-b border-gray-100">
        <div className="flex items-center gap-1.5">
          <span className="text-[11px] font-semibold text-gray-500">🗃 数据库</span>
          {databases.length > 0 && (
            <span className="text-[10px] text-gray-400 bg-white border border-gray-200 rounded-full px-1.5 py-0.5">
              {databases.length}
            </span>
          )}
        </div>
        <button onClick={() => setShowAdd(true)}
          className="text-[11px] text-blue-500 hover:text-blue-700 px-2 py-0.5 rounded border border-blue-200 bg-blue-50 hover:bg-blue-100 transition-colors">
          + 添加
        </button>
      </div>

      {/* 数据库行列表 */}
      {databases.length === 0 ? (
        <p className="text-[11px] text-gray-300 text-center py-3">暂无数据库，点击「+ 添加」创建</p>
      ) : (
        <div className="divide-y divide-gray-50">
          {databases.map((db) => {
            // 内网 URL
            const internalUrl = buildDatabaseUrl(
              db.db_driver,
              conn.internal_host ?? "",
              conn.internal_port ?? DB_DRIVERS.find((d) => d.value === db.db_driver)?.defaultPort ?? "",
              db.username ?? "",
              db.password ?? "",
              db.name,
            );
            // 外网 URL（仅在填了外网地址时展示）
            const hasExternal = !!(conn.external_host);
            const externalUrl = hasExternal ? buildDatabaseUrl(
              db.db_driver,
              conn.external_host ?? "",
              conn.external_port ?? DB_DRIVERS.find((d) => d.value === db.db_driver)?.defaultPort ?? "",
              db.username ?? "",
              db.password ?? "",
              db.name,
            ) : null;

            return (
              <div key={db.id} className="px-3 py-2.5 hover:bg-gray-50/40 transition-colors">
                {/* 库名 + 操作 */}
                <div className="flex items-center gap-2">
                  <span className="font-mono text-xs font-semibold text-gray-700 flex-1 min-w-0 truncate">
                    {db.name}
                  </span>
                  <span className="text-[10px] text-indigo-500 bg-indigo-50 border border-indigo-100 rounded px-1.5 shrink-0">
                    {db.db_driver}
                  </span>
                  {db.charset && db.db_driver !== "redis" && (
                    <span className="text-[10px] text-gray-400 bg-gray-100 rounded px-1.5 shrink-0">
                      {db.charset}
                    </span>
                  )}
                  <button onClick={() => setEditingDb(db)}
                    className="text-[10px] text-gray-400 hover:text-gray-600 shrink-0">编辑</button>
                  <button onClick={() => deleteMut.mutate(db.id)}
                    className="text-[10px] text-gray-400 hover:text-red-500 shrink-0">删除</button>
                </div>

                {/* 用户名 */}
                {db.username && (
                  <div className="flex items-center gap-2 mt-1">
                    <span className="text-[10px] text-gray-400 w-10 shrink-0 text-right">用户</span>
                    <span className="font-mono text-[11px] text-gray-600">{db.username}</span>
                    <CopyBtn value={db.username} />
                  </div>
                )}

                {/* 密码 */}
                {db.password && (
                  <div className="flex items-center gap-2 mt-0.5">
                    <span className="text-[10px] text-gray-400 w-10 shrink-0 text-right">密码</span>
                    <span className="font-mono text-[11px] text-gray-600">
                      {showPwdMap[db.id] ? db.password : "••••••••"}
                    </span>
                    <button onClick={() => togglePwd(db.id)}
                      className="text-[10px] text-gray-400 hover:text-gray-600 transition-colors shrink-0">
                      {showPwdMap[db.id] ? "隐藏" : "显示"}
                    </button>
                    <CopyBtn value={db.password} />
                  </div>
                )}

                {/* DATABASE_URL */}
                <DatabaseUrlRow label="内网" url={internalUrl} />
                {externalUrl && <DatabaseUrlRow label="外网" url={externalUrl} />}

                {db.notes && (
                  <p className="text-[10px] text-gray-400 mt-1.5 leading-relaxed">{db.notes}</p>
                )}
              </div>
            );
          })}
        </div>
      )}

      {showAdd && (
        <DatabaseModal productId={productId}
          onSave={(input) => addMut.mutate(input)}
          onClose={() => setShowAdd(false)} saving={addMut.isPending} />
      )}
      {editingDb && (
        <DatabaseModal productId={productId} initial={editingDb}
          onSave={(input) => editMut.mutate({ id: editingDb.id, input })}
          onClose={() => setEditingDb(null)} saving={editMut.isPending} />
      )}
    </div>
  );
}

// ── Docker 应用模态框 ─────────────────────────────────────────────────────────

function DockerAppModal({ productId, initial, onSave, onClose, saving }: {
  productId: string;
  initial?: DockerApp;
  onSave: (input: SaveDockerAppInput) => void;
  onClose: () => void;
  saving: boolean;
}) {
  const [name, setName]         = useState(initial?.name ?? "");
  const [accessUrl, setAccessUrl] = useState(initial?.access_url ?? "");
  const [username, setUsername] = useState(initial?.username ?? "");
  const [password, setPassword] = useState(initial?.password ?? "");
  const [notes, setNotes]       = useState(initial?.notes ?? "");
  const [showPwd, setShowPwd]   = useState(false);

  const handleSubmit = () => {
    if (!name.trim()) return;
    onSave({
      product_id: productId,
      name: name.trim(),
      access_url: accessUrl.trim() || undefined,
      username: username.trim() || undefined,
      password: password.trim() || undefined,
      notes: notes.trim() || undefined,
    });
  };

  const inputCls = "text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 font-mono w-full";
  const labelCls = "text-[11px] text-gray-400 text-right";

  return (
    <div className="fixed inset-0 bg-black/30 z-50 flex items-center justify-center" onClick={onClose}>
      <div className="bg-white rounded-xl border border-gray-200 shadow-xl w-[440px] flex flex-col"
        onClick={(e) => e.stopPropagation()}>
        <div className="flex items-center justify-between px-5 py-3.5 border-b border-gray-100 shrink-0">
          <h3 className="text-sm font-bold text-gray-800">
            {initial ? "编辑容器应用" : "添加容器应用"}
          </h3>
          <button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-lg leading-none">×</button>
        </div>

        <div className="px-5 py-4">
          <div className="grid grid-cols-[72px_1fr] gap-x-3 gap-y-2.5 items-center">
            <label className={labelCls}>应用名 *</label>
            <input value={name} onChange={(e) => setName(e.target.value)}
              placeholder="例：NocoDB / Portainer / Gitea"
              className={inputCls} autoFocus />

            <label className={labelCls}>访问地址</label>
            <input value={accessUrl} onChange={(e) => setAccessUrl(e.target.value)}
              placeholder="例：http://localhost:8080"
              className={inputCls} />

            <label className={labelCls}>用户名</label>
            <input value={username} onChange={(e) => setUsername(e.target.value)}
              placeholder="登录用户名"
              className={inputCls} />

            <label className={labelCls}>密码</label>
            <div className="flex gap-2">
              <input
                type={showPwd ? "text" : "password"}
                value={password} onChange={(e) => setPassword(e.target.value)}
                placeholder="登录密码"
                className="flex-1 text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 font-mono" />
              <button onClick={() => setShowPwd((v) => !v)}
                className="px-2 py-1 rounded border border-gray-200 text-[11px] text-gray-400 hover:text-gray-600 shrink-0">
                {showPwd ? "隐藏" : "显示"}
              </button>
            </div>

            <label className={`${labelCls} self-start mt-2`}>备注</label>
            <textarea value={notes} onChange={(e) => setNotes(e.target.value)}
              placeholder="端口用途、版本信息、注意事项…" rows={2}
              className="text-xs px-2.5 py-2 rounded border border-gray-200 bg-gray-50 focus:outline-none focus:border-blue-300 resize-none w-full" />
          </div>
        </div>

        <div className="flex items-center justify-end gap-2 px-5 py-3 border-t border-gray-100 bg-gray-50/50 shrink-0">
          <button onClick={onClose}
            className="px-3 py-1.5 rounded-md text-xs border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors">
            取消
          </button>
          <button onClick={handleSubmit} disabled={saving || !name.trim()}
            className="px-4 py-1.5 rounded-md text-xs font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors disabled:opacity-40">
            {saving ? "保存中…" : "保存"}
          </button>
        </div>
      </div>
    </div>
  );
}

// ── Docker 应用子列表 ─────────────────────────────────────────────────────────

function DockerAppSubList({ productId }: { productId: string }) {
  const qc = useQueryClient();
  const queryKey = ["docker-apps", productId];

  const [showAdd, setShowAdd]       = useState(false);
  const [editingApp, setEditingApp] = useState<DockerApp | null>(null);
  const [showPwdMap, setShowPwdMap] = useState<Record<string, boolean>>({});

  const { data: apps = [] } = useQuery({
    queryKey,
    queryFn: () => getDockerApps(productId),
  });

  const addMut = useMutation({
    mutationFn: (input: SaveDockerAppInput) => addDockerApp(input),
    onSuccess: () => { qc.invalidateQueries({ queryKey }); setShowAdd(false); },
  });
  const editMut = useMutation({
    mutationFn: ({ id, input }: { id: string; input: SaveDockerAppInput }) => editDockerApp(id, input),
    onSuccess: () => { qc.invalidateQueries({ queryKey }); setEditingApp(null); },
  });
  const deleteMut = useMutation({
    mutationFn: (id: string) => deleteDockerApp(id),
    onSuccess: () => qc.invalidateQueries({ queryKey }),
  });

  const togglePwd = (id: string) =>
    setShowPwdMap((prev) => ({ ...prev, [id]: !prev[id] }));

  return (
    <div className="mt-2 border border-gray-100 rounded-lg overflow-hidden">
      {/* 标题行 */}
      <div className="flex items-center justify-between px-3 py-2 bg-gray-50/80 border-b border-gray-100">
        <div className="flex items-center gap-1.5">
          <span className="text-[11px] font-semibold text-gray-500">🧩 容器应用</span>
          {apps.length > 0 && (
            <span className="text-[10px] text-gray-400 bg-white border border-gray-200 rounded-full px-1.5 py-0.5">
              {apps.length}
            </span>
          )}
        </div>
        <button onClick={() => setShowAdd(true)}
          className="text-[11px] text-blue-500 hover:text-blue-700 px-2 py-0.5 rounded border border-blue-200 bg-blue-50 hover:bg-blue-100 transition-colors">
          + 添加
        </button>
      </div>

      {/* 应用列表 */}
      {apps.length === 0 ? (
        <p className="text-[11px] text-gray-300 text-center py-3">暂无应用，点击「+ 添加」记录容器应用</p>
      ) : (
        <div className="divide-y divide-gray-50">
          {apps.map((app) => (
            <div key={app.id} className="px-3 py-2.5 hover:bg-gray-50/40 transition-colors">
              {/* 应用名 + 操作 */}
              <div className="flex items-center gap-2">
                <span className="text-xs font-semibold text-gray-800 flex-1 min-w-0 truncate">
                  {app.name}
                </span>
                <button onClick={() => setEditingApp(app)}
                  className="text-[10px] text-gray-400 hover:text-gray-600 shrink-0">编辑</button>
                <button onClick={() => deleteMut.mutate(app.id)}
                  className="text-[10px] text-gray-400 hover:text-red-500 shrink-0">删除</button>
              </div>

              {/* 访问地址 */}
              {app.access_url && (
                <div className="flex items-center gap-2 mt-1.5">
                  <span className="text-[10px] text-gray-400 w-10 shrink-0 text-right">地址</span>
                  <a href={app.access_url} target="_blank" rel="noreferrer"
                    className="font-mono text-[11px] text-blue-600 hover:underline flex-1 min-w-0 truncate">
                    {app.access_url}
                  </a>
                  <CopyBtn value={app.access_url} />
                </div>
              )}

              {/* 用户名 */}
              {app.username && (
                <div className="flex items-center gap-2 mt-0.5">
                  <span className="text-[10px] text-gray-400 w-10 shrink-0 text-right">用户</span>
                  <span className="font-mono text-[11px] text-gray-700">{app.username}</span>
                  <CopyBtn value={app.username} />
                </div>
              )}

              {/* 密码 */}
              {app.password && (
                <div className="flex items-center gap-2 mt-0.5">
                  <span className="text-[10px] text-gray-400 w-10 shrink-0 text-right">密码</span>
                  <span className="font-mono text-[11px] text-gray-700">
                    {showPwdMap[app.id] ? app.password : "••••••••"}
                  </span>
                  <button onClick={() => togglePwd(app.id)}
                    className="text-[10px] text-gray-400 hover:text-gray-600 transition-colors shrink-0">
                    {showPwdMap[app.id] ? "隐藏" : "显示"}
                  </button>
                  <CopyBtn value={app.password} />
                </div>
              )}

              {/* 备注 */}
              {app.notes && (
                <p className="text-[10px] text-gray-400 mt-1.5 leading-relaxed pl-12">{app.notes}</p>
              )}
            </div>
          ))}
        </div>
      )}

      {showAdd && (
        <DockerAppModal productId={productId}
          onSave={(input) => addMut.mutate(input)}
          onClose={() => setShowAdd(false)} saving={addMut.isPending} />
      )}
      {editingApp && (
        <DockerAppModal productId={productId} initial={editingApp}
          onSave={(input) => editMut.mutate({ id: editingApp.id, input })}
          onClose={() => setEditingApp(null)} saving={editMut.isPending} />
      )}
    </div>
  );
}

// ── 产品卡片 ──────────────────────────────────────────────────────────────────

function CloudProductCard({ item, onEdit, onDelete }: {
  item: CloudProduct;
  onEdit: () => void;
  onDelete: () => void;
}) {
  const [confirmDelete, setConfirmDelete] = useState(false);
  const vendor = getVendor(item.vendor);
  const pt = getProductType(item.vendor, item.product_type);

  const expiryDays = item.expire_at ? daysUntilExpiry(item.expire_at) : null;
  const expirySoon = expiryDays !== null && expiryDays <= 7;
  const expired    = expiryDays !== null && expiryDays <= 0;

  return (
    <div className="bg-white border border-gray-200 rounded-xl overflow-hidden shadow-sm">
      {/* 标题栏 */}
      <div className="flex items-center gap-2.5 px-4 py-3 bg-gradient-to-r from-gray-50 to-white border-b border-gray-100">
        <span className={`text-[10px] px-2 py-0.5 rounded border font-medium shrink-0 ${vendor.color}`}>
          {vendor.label}
        </span>
        <span className="text-[11px] px-1.5 py-0.5 rounded bg-gray-100 text-gray-600 border border-gray-200 shrink-0">
          {pt.icon} {pt.label}
        </span>
        <span className="text-sm font-bold text-gray-800 flex-1 min-w-0 truncate">{item.name}</span>
        <div className="flex gap-1.5 shrink-0">
          <button onClick={onEdit}
            className="px-2.5 py-1 rounded-md text-[11px] border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors">
            编辑
          </button>
          {!confirmDelete ? (
            <button onClick={() => setConfirmDelete(true)}
              className="px-2.5 py-1 rounded-md text-[11px] border border-gray-200 text-gray-400 hover:text-red-500 hover:border-red-200 transition-colors">
              删除
            </button>
          ) : (
            <div className="flex gap-1">
              <button onClick={onDelete}
                className="px-2.5 py-1 rounded-md text-[11px] border border-red-300 bg-red-50 text-red-600 hover:bg-red-100 transition-colors">
                确认
              </button>
              <button onClick={() => setConfirmDelete(false)}
                className="px-2.5 py-1 rounded-md text-[11px] border border-gray-200 text-gray-400 transition-colors">
                取消
              </button>
            </div>
          )}
        </div>
      </div>

      {/* 详情 */}
      <div className="px-4 py-3 space-y-1.5">
        <div className="flex items-center gap-4 flex-wrap">
          <span className={`text-[10px] px-2 py-0.5 rounded-full border font-medium ${getStatusStyle(item.status)}`}>
            {getStatusLabel(item.status)}
          </span>
          {item.region && (
            <span className="text-[11px] text-gray-500 flex items-center gap-1">
              <span className="text-gray-300">📍</span>{item.region}
            </span>
          )}
          {item.expire_at && (
            <span className={`text-[11px] flex items-center gap-1 font-medium ${
              expired ? "text-red-600" : expirySoon ? "text-amber-600" : "text-gray-500"
            }`}>
              <span>{expired ? "⚠️" : expirySoon ? "⏰" : "📅"}</span>
              {item.expire_at.slice(0, 10)}
              {expired ? " (已到期)" : expirySoon ? ` (${expiryDays} 天后到期)` : ""}
            </span>
          )}
        </div>

        {item.instance_id && (
          <div className="flex items-center gap-2 text-[11px]">
            <span className="text-gray-400 shrink-0 w-14 text-right">实例 ID</span>
            <span className="font-mono text-gray-600 truncate">{item.instance_id}</span>
          </div>
        )}

        {/* 无连接字段时显示通用端点 */}
        {!getConnSchema(item.product_type) && item.endpoint && (
          <div className="flex items-center gap-2 text-[11px]">
            <span className="text-gray-400 shrink-0 w-14 text-right">端点</span>
            <span className="font-mono text-gray-600 truncate flex-1 min-w-0">{item.endpoint}</span>
            <CopyBtn value={item.endpoint} />
          </div>
        )}

        {/* 连接方式展示 */}
        <ConnInfoBlock productType={item.product_type} configData={item.config_data} />

        {item.notes && (
          <div className="mt-1 bg-gray-50 border border-gray-100 rounded-md px-2.5 py-2">
            <p className="text-[11px] text-gray-500 leading-relaxed whitespace-pre-line">{item.notes}</p>
          </div>
        )}

        {/* 数据库子列表（仅数据库类型实例显示） */}
        {["rds", "tdsql", "redis", "mysql", "postgresql", "mongodb"].includes(item.product_type) && (
          <DatabaseSubList productId={item.id} configData={item.config_data} />
        )}

        {/* Docker 容器应用子列表 */}
        {item.product_type === "docker" && (
          <DockerAppSubList productId={item.id} />
        )}
      </div>
    </div>
  );
}

// ── 厂商分组 ──────────────────────────────────────────────────────────────────

function VendorSection({ vendor, products, onEdit, onDelete }: {
  vendor: typeof VENDORS[number];
  products: CloudProduct[];
  onEdit: (item: CloudProduct) => void;
  onDelete: (id: string) => void;
}) {
  const [collapsed, setCollapsed] = useState(false);
  if (products.length === 0) return null;

  return (
    <section className="bg-white rounded-2xl border border-gray-200 shadow-sm overflow-hidden">
      <button
        className="w-full flex items-center gap-3 px-5 py-3 border-b border-gray-100 bg-gray-50/80 hover:bg-gray-100/60 transition-colors"
        onClick={() => setCollapsed((v) => !v)}
      >
        <span className={`w-2 h-2 rounded-full ${vendor.dot} shrink-0`} />
        <h3 className="text-sm font-bold text-gray-800">{vendor.label}</h3>
        <span className="text-xs text-gray-400 bg-white border border-gray-200 rounded-full px-2 py-0.5">
          {products.length} 个
        </span>
        <svg className={`ml-auto w-3.5 h-3.5 text-gray-400 transition-transform ${collapsed ? "-rotate-90" : ""}`}
          viewBox="0 0 20 20" fill="currentColor">
          <path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
        </svg>
      </button>
      {!collapsed && (
        <div className="p-4 grid grid-cols-1 xl:grid-cols-2 gap-3">
          {products.map((item) => (
            <CloudProductCard key={item.id} item={item}
              onEdit={() => onEdit(item)} onDelete={() => onDelete(item.id)} />
          ))}
        </div>
      )}
    </section>
  );
}

// ── 主面板 ────────────────────────────────────────────────────────────────────

export function CloudProductsPanel() {
  const qc = useQueryClient();
  const [showModal, setShowModal]     = useState(false);
  const [editingItem, setEditingItem] = useState<CloudProduct | null>(null);

  const { data: products = [] } = useQuery({
    queryKey: ["cloud-products"],
    queryFn: getCloudProducts,
  });

  const addMut = useMutation({
    mutationFn: (input: SaveCloudProductInput) => addCloudProduct(input),
    onSuccess: () => { qc.invalidateQueries({ queryKey: ["cloud-products"] }); setShowModal(false); },
  });
  const editMut = useMutation({
    mutationFn: ({ id, input }: { id: string; input: SaveCloudProductInput }) => editCloudProduct(id, input),
    onSuccess: () => { qc.invalidateQueries({ queryKey: ["cloud-products"] }); setEditingItem(null); },
  });
  const deleteMut = useMutation({
    mutationFn: (id: string) => deleteCloudProduct(id),
    onSuccess: () => qc.invalidateQueries({ queryKey: ["cloud-products"] }),
  });

  const grouped = VENDORS.map((v) => ({
    vendor: v,
    products: products.filter((p) => p.vendor === v.value),
  }));

  const totalExpiringSoon = products.filter((p) => {
    if (!p.expire_at) return false;
    const d = daysUntilExpiry(p.expire_at);
    return d >= 0 && d <= 7;
  }).length;

  return (
    <div className="space-y-4">
      <div className="flex items-center justify-between">
        <div className="flex items-center gap-2">
          <h2 className="text-sm font-bold text-gray-700">云产品</h2>
          <span className="text-xs text-gray-400 bg-gray-100 rounded-full px-2 py-0.5">{products.length} 个</span>
          {totalExpiringSoon > 0 && (
            <span className="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded-full px-2 py-0.5">
              ⏰ {totalExpiringSoon} 个即将到期
            </span>
          )}
        </div>
        <button onClick={() => setShowModal(true)}
          className="px-3 py-1.5 rounded-md text-xs font-medium border border-blue-300 bg-blue-50 text-blue-600 hover:bg-blue-100 transition-colors">
          + 添加云产品
        </button>
      </div>

      {products.length === 0 && (
        <div className="text-center py-24 text-gray-400">
          <p className="text-4xl mb-4">☁️</p>
          <p className="text-sm">还没有云产品记录，点击「+ 添加云产品」开始管理</p>
          <p className="text-xs text-gray-300 mt-1">支持阿里云、腾讯云的 ECS/RDS/OSS/CDN 等各类云产品</p>
        </div>
      )}

      {grouped.map(({ vendor, products: vp }) => (
        <VendorSection key={vendor.value} vendor={vendor} products={vp}
          onEdit={setEditingItem} onDelete={(id) => deleteMut.mutate(id)} />
      ))}

      {showModal && (
        <CloudProductModal onSave={(input) => addMut.mutate(input)}
          onClose={() => setShowModal(false)} saving={addMut.isPending} />
      )}
      {editingItem && (
        <CloudProductModal initial={editingItem}
          onSave={(input) => editMut.mutate({ id: editingItem.id, input })}
          onClose={() => setEditingItem(null)} saving={editMut.isPending} />
      )}
    </div>
  );
}
