/* global React */
const { useState, useMemo, useEffect, useRef } = React;

/* ===== Sample geometries (mock data; replace with /api/geometries later) ===== */
const SAMPLE_GEOMETRIES = [
  { id: "20260512-203200-a8b4f1", name: "盘式制动器装配体", fmt: "cdb",  size: 12582912,
    desc: "4 部件装配：刹车盘 + 内/外刹车片 + 背板。共 1 个 contact pair，结构钢材料。PyAnsys 官方 td-1 示例。",
    original: "disc_pad_model.cdb", source_url: "https://github.com/ansys/example-data/.../td-1/disc_pad_model.cdb",
    at: "2026-05-12 20:32", by: "admin",
    refs: [
      { case_id: "c-20260510-a1", name: "盘式制动器 · 线性模态",     atype: "modal" },
      { case_id: "c-20260510-a2", name: "盘式制动器 · 部分预应力",   atype: "static_structural" },
      { case_id: "c-20260510-a3", name: "盘式制动器 · 全非线性",     atype: "nonlinear" },
      { case_id: "c-20260510-a4", name: "盘式制动器 · 参数化扫频",   atype: "harmonic" },
    ] },
  { id: "20260511-091245-c2e891", name: "涡轮叶片 NACA-65", fmt: "step", size: 3354624,
    desc: "压气机第一级转子叶片单通道模型，含榫头与平台。来自客户 KCT-A-2024 项目。",
    original: "turbine_blade_naca65.step", at: "2026-05-11 09:12", by: "admin",
    refs: [{ case_id: "c-20260511-b1", name: "叶片 · 离心载荷静力", atype: "static_structural" }] },
  { id: "20260510-160340-9d33aa", name: "压力容器壳体", fmt: "iges", size: 4718592,
    desc: "薄壁圆筒 + 半球封头，含两个法兰开孔。ASME 第八章设计校核基础几何。",
    original: "pressure_vessel.iges", at: "2026-05-10 16:03", by: "admin",
    refs: [
      { case_id: "c-20260510-c1", name: "容器 · 内压稳态",     atype: "static_structural" },
      { case_id: "c-20260510-c2", name: "容器 · 屈曲分析",     atype: "buckling" },
      { case_id: "c-20260510-c3", name: "容器 · 热应力耦合",   atype: "thermal_structural" },
    ] },
  { id: "20260509-114220-7f6e1c", name: "齿轮箱壳体", fmt: "cdb",  size: 19294617,
    desc: "二级减速箱箱体，含 4 个轴承座孔位、8 颗螺栓位。模态 + 谐响应分析复用。",
    original: "gearbox_housing.cdb", at: "2026-05-09 11:42", by: "admin",
    refs: [{ case_id: "c-20260509-d1", name: "齿轮箱 · 整机模态", atype: "modal" }] },
  { id: "20260508-082015-31b7d2", name: "散热翅片阵列", fmt: "anf",  size: 1677721,
    desc: "20 片纵向直翅片，铝合金，用于稳态 / 瞬态热分析对比。",
    original: "heatsink_fins.anf", at: "2026-05-08 08:20", by: "admin", refs: [] },
  { id: "20260507-153842-5a90b8", name: "螺栓 M16 装配", fmt: "step", size: 629145,
    desc: "GB/T 5783 标准件，含螺母与垫片。预紧 + 接触分析基础几何。",
    original: "bolt_m16_assembly.step", at: "2026-05-07 15:38", by: "admin",
    refs: [
      { case_id: "c-20260507-e1", name: "螺栓 · 预紧力静力", atype: "static_structural" },
      { case_id: "c-20260507-e2", name: "螺栓 · 滑移屈服",   atype: "nonlinear" },
    ] },
  { id: "20260505-094711-2bf041", name: "太阳能板支架", fmt: "iges", size: 6082662,
    desc: "屋顶分布式光伏组件支架，含立柱、横梁、压块。风载与地震载荷分析。",
    original: "solar_rack.iges", at: "2026-05-05 09:47", by: "admin",
    refs: [{ case_id: "c-20260505-f1", name: "支架 · 风载静力", atype: "static_structural" }] },
  { id: "20260503-181520-e7c3d9", name: "连接器外壳", fmt: "xt", size: 419430,
    desc: "塑料注塑件，含 8 个端子孔。落跌冲击分析备用几何。",
    original: "connector_shell.x_t", at: "2026-05-03 18:15", by: "admin", refs: [] },
];

const FMT_META = {
  cdb:  { tag: "CDB",  name: "ANSYS CDB",     cls: "cdb"  },
  step: { tag: "STEP", name: "STEP",           cls: "step" },
  stp:  { tag: "STP",  name: "STEP",           cls: "step" },
  iges: { tag: "IGES", name: "IGES",           cls: "iges" },
  igs:  { tag: "IGS",  name: "IGES",           cls: "iges" },
  anf:  { tag: "ANF",  name: "ANSYS Native",   cls: "anf"  },
  xt:   { tag: "X_T",  name: "Parasolid",      cls: "xt"   },
  xb:   { tag: "X_B",  name: "Parasolid Bin.", cls: "xt"   },
};

const FILTER_BUCKETS = [
  { key: "cdb",   label: "CDB",   match: (g) => g.fmt === "cdb" },
  { key: "step",  label: "STEP",  match: (g) => g.fmt === "step" || g.fmt === "stp" },
  { key: "iges",  label: "IGES",  match: (g) => g.fmt === "iges" || g.fmt === "igs" },
  { key: "anf",   label: "ANF",   match: (g) => g.fmt === "anf"  },
  { key: "other", label: "其他",  match: (g) => ["xt", "xb"].includes(g.fmt) },
  { key: "unref", label: "未引用", match: (g) => g.refs.length === 0 },
];

const fmtSize = (n) => {
  if (n >= 1048576) return (n/1048576).toFixed(1) + " MB";
  if (n >= 1024)    return (n/1024).toFixed(1) + " KB";
  return n + " B";
};

/* ===== Format art (colored block thumb, the only "preview" we get) ===== */
const FmtArt = ({ fmt, className = "", showLabel = true }) => {
  const meta = FMT_META[fmt] || FMT_META.xt;
  return (
    <div className={`geo-fmt-art geo-fmt-${meta.cls} ${className}`}>
      {showLabel && <span className="geo-fmt-art-label">{meta.tag}</span>}
    </div>
  );
};

const FmtBadge = ({ fmt }) => {
  const meta = FMT_META[fmt] || FMT_META.xt;
  return <span className={`geo-fmt-badge geo-fmt-${meta.cls}-badge`}>{meta.tag}</span>;
};

/* ===== Grid card — name only, footer with fmt + size + ref count ===== */
const GeoCard = ({ g, onPick }) => (
  <div className="geo-card" onClick={() => onPick(g)} role="button" tabIndex={0}
       onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && onPick(g)}>
    <FmtArt fmt={g.fmt} className="geo-card-cover">
      {g.refs.length > 0 && (
        <span className="geo-card-ref">↗ {g.refs.length}</span>
      )}
    </FmtArt>
    <div className="geo-card-name" title={g.name}>{g.name}</div>
    <div className="geo-card-footer">
      <FmtBadge fmt={g.fmt}/>
      <span className="geo-card-size mono tnum">{fmtSize(g.size)}</span>
      <span className="geo-card-at">{g.at.slice(5, 10)}</span>
    </div>
  </div>
);

/* ===== List row ===== */
const GeoRow = ({ g, onPick }) => (
  <div className="geo-row" onClick={() => onPick(g)} role="button" tabIndex={0}
       onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && onPick(g)}>
    <FmtArt fmt={g.fmt} className="geo-row-art"/>
    <div className="geo-row-main">
      <div className="geo-row-name">{g.name}</div>
      <div className="geo-row-id mono">{g.id}</div>
    </div>
    <FmtBadge fmt={g.fmt}/>
    <span className="mono tnum geo-row-size">{fmtSize(g.size)}</span>
    <span className="geo-row-ref">
      {g.refs.length === 0
        ? <span className="geo-row-ref-zero">— 未引用</span>
        : <><span className="num mono">{g.refs.length}</span> 个 case</>}
    </span>
    <span className="geo-row-at mono">{g.at.slice(0, 10)}</span>
  </div>
);

/* ===== Right-side drawer (detail) ===== */
const GeoDrawer = ({ g, onClose, onUse, onDelete }) => {
  // Lock scroll + ESC to close while open
  useEffect(() => {
    if (!g) return;
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [g, onClose]);

  return (
    <>
      <div className={`geo-drawer-scrim ${g ? "open" : ""}`} onClick={onClose}/>
      <div className={`geo-preview ${g ? "open" : ""}`} aria-hidden={!g} onClick={onClose}>
        {g && (
          <div className="geo-preview-inner" onClick={(e) => e.stopPropagation()}>
            {g.has_preview ? (
              <model-viewer
                key={g.id}
                src={`/api/geometry/${g.id}/preview.gltf`}
                alt={g.name}
                camera-controls
                auto-rotate
                auto-rotate-delay="0"
                rotation-per-second="20deg"
                shadow-intensity="1.4"
                shadow-softness="0.7"
                exposure="1.1"
                tone-mapping="commerce"
                environment-image="legacy"
                interaction-prompt="none"
                style={{
                  width: "100%", flex: "1 1 0", minHeight: 0,
                  background: "transparent", "--poster-color": "transparent",
                }}
              />
            ) : (
              <>
                <FmtArt fmt={g.fmt} className="geo-preview-art" showLabel={false}/>
                <div className="geo-preview-fmt-tag">{FMT_META[g.fmt]?.tag}</div>
              </>
            )}
            <div className="geo-preview-caption">
              <div className="geo-preview-name">{g.name}</div>
              <div className="geo-preview-file mono">{g.original} · {fmtSize(g.size)}</div>
            </div>
          </div>
        )}
      </div>
      <aside className={`geo-drawer ${g ? "open" : ""}`} aria-hidden={!g}>
        {g && (
          <>
            <div className="geo-drawer-header">
              <button className="btn btn-sm btn-ghost geo-drawer-close" onClick={onClose} title="关闭 (Esc)">
                <Icon name="x" size={14}/>
              </button>
              <div className="geo-drawer-title-row">
                <FmtArt fmt={g.fmt} className="geo-drawer-art"/>
                <div className="geo-drawer-titles">
                  <div className="geo-drawer-name">{g.name}</div>
                  <div className="geo-drawer-id mono">{g.id}</div>
                </div>
              </div>
              <div className="geo-drawer-actions">
                <a className="btn btn-accent" href={`/api/geometry/${g.id}/file`} download={g.original}
                   style={{ textDecoration: "none", lineHeight: "normal" }}>
                  <Icon name="download" size={13}/> 下载原文件
                </a>
                <button className="btn"><Icon name="edit" size={13}/> 编辑</button>
                <button className="btn btn-ghost geo-drawer-del" disabled={g.refs.length > 0}
                        onClick={() => onDelete && onDelete(g)}
                        title={g.refs.length > 0 ? `被 ${g.refs.length} 个 case 引用，不可删除` : "删除"}>
                  <Icon name="trash" size={13}/>
                </button>
              </div>
            </div>

            <div className="geo-drawer-body">
              <div className="geo-section">
                <div className="geo-section-title">描述</div>
                <div className="geo-desc">{g.desc || <span className="dim">未填写描述</span>}</div>
              </div>

              <div className="geo-section">
                <div className="geo-section-title">元数据</div>
                <div className="geo-kv">
                  <span className="k">格式</span>
                  <span className="v"><FmtBadge fmt={g.fmt}/> &nbsp;{FMT_META[g.fmt]?.name}</span>
                  <span className="k">大小</span>
                  <span className="v mono tnum">{fmtSize(g.size)}</span>
                  <span className="k">原文件名</span>
                  <span className="v mono">{g.original}</span>
                  <span className="k">上传时间</span>
                  <span className="v mono">{g.at}</span>
                  <span className="k">上传者</span>
                  <span className="v">{g.by}</span>
                  {g.source_url && <>
                    <span className="k">来源</span>
                    <span className="v"><a className="geo-link mono" href={g.source_url} target="_blank" rel="noreferrer">{g.source_url}</a></span>
                  </>}
                </div>
              </div>

              <div className="geo-section">
                <div className="geo-section-title">
                  被引用的 CASE
                  <span className="num mono">{g.refs.length}</span>
                </div>
                {g.refs.length === 0 ? (
                  <div className="geo-empty-refs">
                    <div className="geo-empty-icon"><Icon name="link" size={20}/></div>
                    <div className="geo-empty-title">还未被任何 case 使用</div>
                  </div>
                ) : (
                  <div className="geo-ref-list">
                    {g.refs.map((r) => (
                      <div className="geo-ref-item" key={r.case_id} onClick={() => onUse && onUse(r)}>
                        <div className="geo-ref-name">{r.name}</div>
                        <span className="geo-ref-atype">{r.atype.replace(/_/g, " ")}</span>
                        <span className="geo-ref-arrow"><Icon name="link" size={12}/></span>
                      </div>
                    ))}
                  </div>
                )}
              </div>
            </div>
          </>
        )}
      </aside>
    </>
  );
};

/* ===== Upload modal ===== */
const GeoUploadModal = ({ open, onClose, onSubmit }) => {
  const [file, setFile] = useState(null);
  const [name, setName] = useState("");
  const [desc, setDesc] = useState("");
  const [dragging, setDragging] = useState(false);
  const [busy, setBusy] = useState(false);
  const inputRef = useRef(null);

  useEffect(() => {
    if (open) { setFile(null); setName(""); setDesc(""); setBusy(false); }
  }, [open]);
  useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open, onClose]);

  if (!open) return null;

  const ALLOWED = ["cdb","iges","igs","step","stp","anf","x_t","x_b"];
  const onFile = (f) => {
    if (!f) return;
    const ext = (f.name.split(".").pop() || "").toLowerCase();
    if (!ALLOWED.includes(ext)) {
      alert("不支持的格式：." + ext + "\n支持：" + ALLOWED.map(e => "." + e).join(" · "));
      return;
    }
    if (f.size > 100 * 1024 * 1024) {
      alert("文件超过 100 MB 上限");
      return;
    }
    setFile(f);
    if (!name) setName(f.name.replace(/\.[^.]+$/, ""));
  };

  const fmt = file ? (file.name.split(".").pop() || "").toLowerCase().replace("_", "") : null;
  const fmtKey = fmt === "xt" ? "xt" : fmt === "xb" ? "xb" : fmt;

  const submit = async () => {
    if (!file || !name.trim()) return;
    setBusy(true);
    try {
      const g = await uploadGeometry({ file, name: name.trim(), description: desc.trim() });
      onSubmit(g);
      onClose();
    } catch (e) {
      alert("上传失败：" + e.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="geo-modal-overlay" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="geo-modal" role="dialog" aria-modal="true">
        <div className="geo-modal-head">
          <div className="geo-modal-title"><Icon name="plus" size={14}/> 上传几何</div>
          <button className="btn btn-sm btn-ghost" onClick={onClose}><Icon name="x" size={13}/></button>
        </div>
        <div className="geo-modal-body">
          {!file ? (
            <div
              className={`geo-drop ${dragging ? "drag" : ""}`}
              onClick={() => inputRef.current?.click()}
              onDragOver={(e) => { e.preventDefault(); setDragging(true); }}
              onDragLeave={() => setDragging(false)}
              onDrop={(e) => { e.preventDefault(); setDragging(false); onFile(e.dataTransfer.files?.[0]); }}>
              <div className="geo-drop-icon"><Icon name="cube" size={26}/></div>
              <div className="geo-drop-big">拖入 CAD 文件，或点击选择</div>
              <div className="geo-drop-sm">支持 .cdb · .step · .iges · .anf · .x_t · .x_b<br/>单文件 ≤ 100 MB</div>
              <input ref={inputRef} type="file" hidden
                     accept=".cdb,.iges,.igs,.step,.stp,.anf,.x_t,.x_b"
                     onChange={(e) => onFile(e.target.files?.[0])}/>
            </div>
          ) : (
            <div className="geo-file-card">
              <FmtArt fmt={fmtKey} className="geo-file-art"/>
              <div className="geo-file-info">
                <div className="geo-file-name mono">{file.name}</div>
                <div className="geo-file-meta">{fmtSize(file.size)} · 自动识别为 {FMT_META[fmtKey]?.tag || fmt.toUpperCase()}</div>
              </div>
              <button className="btn btn-sm btn-ghost" onClick={() => { setFile(null); if (inputRef.current) inputRef.current.value = ""; }}>
                <Icon name="x" size={12}/>
              </button>
            </div>
          )}

          <div className="geo-field">
            <div className="geo-field-label">名称 <span className="geo-required">*</span></div>
            <input className="geo-input" value={name} onChange={(e) => setName(e.target.value)} placeholder="例如：盘式制动器装配体"/>
          </div>
          <div className="geo-field">
            <div className="geo-field-label">描述（可选）</div>
            <textarea className="geo-input geo-textarea" value={desc} onChange={(e) => setDesc(e.target.value)}
                      placeholder="部件构成、材料、来源等"/>
          </div>
        </div>
        <div className="geo-modal-foot">
          <button className="btn btn-ghost" onClick={onClose}>取消</button>
          <button className="btn btn-accent" disabled={!file || !name.trim() || busy} onClick={submit}>
            {busy ? "上传中…" : <><Icon name="plus" size={12}/> 上传</>}
          </button>
        </div>
      </div>
    </div>
  );
};

/* Transform the API shape → the UI shape this page was designed against. */
const _normalizeGeo = (g) => {
  if (!g) return g;
  const at = g.created_at
    ? new Date(g.created_at * 1000).toISOString().slice(0, 16).replace("T", " ")
    : "";
  return {
    id: g.id,
    name: g.name,
    fmt: g.fmt,
    size: g.size,
    desc: g.description || "",
    original: g.original_filename || "",
    source_url: g.source_url || null,
    at,
    by: g.user || "",
    refs: g.refs || g.refs_preview || [],
    ref_count: g.ref_count ?? (g.refs || g.refs_preview || []).length,
    has_preview: !!g.has_preview,
  };
};

const fetchGeometries = async () => {
  const r = await fetch("/api/geometry", { credentials: "include" });
  if (!r.ok) throw new Error("加载失败：" + r.status);
  const d = await r.json();
  return (d.geometry || []).map(_normalizeGeo);
};

const fetchGeometryDetail = async (id) => {
  const r = await fetch(`/api/geometry/${id}`, { credentials: "include" });
  if (!r.ok) throw new Error("详情加载失败");
  return _normalizeGeo((await r.json()).geometry);
};

const uploadGeometry = async ({ file, name, description, source_url }) => {
  const fd = new FormData();
  fd.append("file", file);
  fd.append("name", name);
  if (description) fd.append("description", description);
  if (source_url) fd.append("source_url", source_url);
  const r = await fetch("/api/geometry", { method: "POST", body: fd, credentials: "include" });
  if (!r.ok) throw new Error((await r.json()).detail || "上传失败");
  return _normalizeGeo((await r.json()).geometry);
};

const deleteGeometryApi = async (id) => {
  const r = await fetch(`/api/geometry/${id}`, { method: "DELETE", credentials: "include" });
  if (!r.ok) throw new Error((await r.json()).detail || "删除失败");
};

/* ===== Main page ===== */
const GeometryPage = () => {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(true);
  const [view, setView] = useState("grid");   // grid | list
  const [filter, setFilter] = useState("all"); // all | cdb | step | iges | anf | other | unref
  const [search, setSearch] = useState("");
  const [active, setActive] = useState(null);
  const [uploadOpen, setUploadOpen] = useState(false);

  const refresh = async () => {
    setLoading(true);
    try {
      setItems(await fetchGeometries());
    } catch (e) {
      console.error(e);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => { refresh(); }, []);

  const counts = useMemo(() => {
    const c = { all: items.length };
    for (const b of FILTER_BUCKETS) c[b.key] = items.filter(b.match).length;
    return c;
  }, [items]);

  const filtered = useMemo(() => {
    let list = items;
    if (filter !== "all") {
      const b = FILTER_BUCKETS.find(x => x.key === filter);
      if (b) list = list.filter(b.match);
    }
    if (search.trim()) {
      const q = search.trim().toLowerCase();
      list = list.filter(g =>
        g.name.toLowerCase().includes(q) ||
        (g.desc || "").toLowerCase().includes(q) ||
        g.id.toLowerCase().includes(q) ||
        (g.original || "").toLowerCase().includes(q));
    }
    return list;
  }, [items, filter, search]);

  const onUploaded = (g) => {
    setItems(prev => [g, ...prev]);
    setTimeout(() => setActive(g), 80);
  };

  // When opening a card, fetch full detail (refs list) so the drawer shows
  // every case that references this geometry, not just the preview.
  const openDetail = async (g) => {
    setActive(g);
    try {
      const full = await fetchGeometryDetail(g.id);
      setActive(full);
    } catch (_) {}
  };

  const handleDelete = async (g) => {
    if (g.ref_count > 0) {
      alert(`被 ${g.ref_count} 个案例引用，无法删除`);
      return;
    }
    if (!confirm(`确认删除「${g.name}」？此操作不可撤销。`)) return;
    try {
      await deleteGeometryApi(g.id);
      setItems(prev => prev.filter(x => x.id !== g.id));
      setActive(null);
    } catch (e) {
      alert("删除失败：" + e.message);
    }
  };

  return (
    <div className="geo-page">
      <div className="geo-toolbar">
        <div className="geo-toolbar-left">
          <div className="geo-search">
            <Icon name="search" size={12}/>
            <input value={search} onChange={(e) => setSearch(e.target.value)}
                   placeholder="搜索几何名称 / 描述 / 文件名"/>
            {search && (
              <button className="geo-search-clear" onClick={() => setSearch("")}><Icon name="x" size={10}/></button>
            )}
          </div>
          <div className="geo-chips">
            <span className={`chip ${filter === "all" ? "active" : ""}`} onClick={() => setFilter("all")}>
              全部<span className="ic">{counts.all}</span>
            </span>
            {FILTER_BUCKETS.map(b => (
              <span key={b.key}
                    className={`chip ${filter === b.key ? "active" : ""} ${counts[b.key] === 0 ? "dim" : ""}`}
                    onClick={() => setFilter(filter === b.key ? "all" : b.key)}>
                {b.label}<span className="ic">{counts[b.key]}</span>
              </span>
            ))}
          </div>
        </div>
        <div className="geo-toolbar-right">
          <div className="geo-view-toggle">
            <button className={view === "grid" ? "active" : ""} onClick={() => setView("grid")} title="网格">
              <Icon name="grid" size={13}/>
            </button>
            <button className={view === "list" ? "active" : ""} onClick={() => setView("list")} title="列表">
              <Icon name="list" size={13}/>
            </button>
          </div>
          <button className="btn btn-accent" onClick={() => setUploadOpen(true)}>
            <Icon name="plus" size={13}/> 上传几何
          </button>
        </div>
      </div>

      <div className="geo-content">
        {filtered.length === 0 ? (
          items.length === 0 ? (
            <div className="geo-empty">
              <div className="geo-empty-cube"><Icon name="cube" size={36}/></div>
              <div className="geo-empty-h">还没有几何资产</div>
              <div className="geo-empty-p">上传你的第一个 CAD 文件，开始把几何沉淀为可复用资产</div>
              <button className="btn btn-accent" onClick={() => setUploadOpen(true)} style={{ marginTop: 14 }}>
                <Icon name="plus" size={13}/> 上传第一个几何
              </button>
            </div>
          ) : (
            <div className="geo-empty">
              <div className="geo-empty-cube" style={{ opacity: 0.3 }}><Icon name="search" size={28}/></div>
              <div className="geo-empty-h">没有匹配的几何</div>
              <div className="geo-empty-p">试试清除筛选或换个关键词</div>
              <button className="btn btn-sm" style={{ marginTop: 12 }}
                      onClick={() => { setFilter("all"); setSearch(""); }}>清除筛选</button>
            </div>
          )
        ) : view === "grid" ? (
          <div className="geo-grid">
            {filtered.map(g => <GeoCard key={g.id} g={g} onPick={openDetail}/>)}
          </div>
        ) : (
          <div className="geo-list">
            <div className="geo-row geo-row-head">
              <span></span>
              <span>名称 / ID</span>
              <span>格式</span>
              <span style={{ textAlign: "right" }}>大小</span>
              <span>引用</span>
              <span>上传时间</span>
            </div>
            {filtered.map(g => <GeoRow key={g.id} g={g} onPick={openDetail}/>)}
          </div>
        )}
      </div>

      <GeoDrawer g={active} onClose={() => setActive(null)} onDelete={handleDelete}/>
      <GeoUploadModal open={uploadOpen} onClose={() => setUploadOpen(false)} onSubmit={onUploaded}/>
    </div>
  );
};

window.GeometryPage = GeometryPage;
