// Admin: Sightings / Deer / Photos sections

// ============ SIGHTINGS ============
function AdminSightings({ who }) {
  const s = window.useStore();
  const [q, setQ] = React.useState("");
  const [status, setStatus] = React.useState("all");
  const [selected, setSelected] = React.useState(new Set());
  const [openSighting, setOpenSighting] = React.useState(null);
  const [activeMedia, setActiveMedia] = React.useState(0);

  const deerOpts = [["", "— 未关联 —"], ...s.deer.map(d => [d.id, `${d.name} · ${d.id}`])];

  const filtered = s.sightings.filter(o => {
    if (status !== "all" && o.status !== status) return false;
    if (q) {
      const hay = `${o.id} ${o.deerName} ${o.loc} ${o.contributor} ${o.note}`.toLowerCase();
      if (!hay.includes(q.toLowerCase())) return false;
    }
    return true;
  });

  const toggle = (id) => {
    const n = new Set(selected);
    if (n.has(id)) n.delete(id); else n.add(id);
    setSelected(n);
  };
  const toggleAll = () => {
    if (selected.size === filtered.length) setSelected(new Set());
    else setSelected(new Set(filtered.map(x => x.id)));
  };

  const bulkSet = (status) => {
    filtered.filter(o => selected.has(o.id)).forEach(o => window.dzStore.setSightingStatus(o.id, status, who));
    setSelected(new Set());
  };
  const bulkDelete = () => {
    if (!confirm(`确认删除 ${selected.size} 条观察记录？`)) return;
    window.dzStore.removeMany("sightings", [...selected], who);
    setSelected(new Set());
  };

  const update = (id, patch) => window.dzStore.update("sightings", id, patch, who);
  React.useEffect(() => setActiveMedia(0), [openSighting]);

  const appendSupportMedia = (sighting, files) => {
    const accepted = [...(files || [])].filter(f => /^image\//.test(f.type) || /^video\//.test(f.type));
    if (!accepted.length) return;
    const base = Array.isArray(sighting.media)
      ? sighting.media
      : (sighting.photo ? [{
          id: sighting.id + "-MAIN",
          type: sighting.photoMime?.startsWith("video/") ? "video" : "image",
          url: sighting.photo,
          role: "识别主图",
          recognition: true,
        }] : []);
    const additions = accepted.map((f, i) => ({
      id: sighting.id + "-SUP-" + Date.now() + "-" + i,
      type: f.type.startsWith("video/") ? "video" : "image",
      mime: f.type,
      name: f.name,
      size: f.size,
      url: URL.createObjectURL(f),
      role: f.type.startsWith("video/") ? "补充视频" : "补充图片",
      recognition: false,
      note: "观察记录补充素材，不进入 AI 识别",
    }));
    update(sighting.id, {
      media: [...base, ...additions],
      supportMediaCount: (sighting.supportMediaCount || 0) + additions.length,
    });
  };

  const newSighting = () => {
    const id = "OBS-" + Math.floor(4000 + Math.random() * 1999);
    window.dzStore.add("sightings", {
      id, deerId: "", deerName: "（待归并）",
      time: new Date().toISOString().slice(0, 16).replace("T", " "),
      loc: "", contributor: "", note: "", status: "AI 初审", media: [],
    }, who);
  };

  const open = openSighting ? s.sightings.find(x => x.id === openSighting) : null;

  return (
    <>
      <div className="admin-toolbar">
        <input type="search" placeholder="搜索 ID / 个体 / 地点 / 贡献者…" value={q} onChange={e => setQ(e.target.value)} style={{ width: 280 }} />
        <select value={status} onChange={e => setStatus(e.target.value)}>
          <option value="all">全部状态</option>
          <option>AI 初审</option>
          <option>志愿者审核中</option>
          <option>已归档</option>
          <option>驳回</option>
        </select>
        <span className="count">{filtered.length} / {s.sightings.length} 条</span>
        <div className="sp"></div>
        {selected.size > 0 && (
          <>
            <span className="count" style={{ color: "var(--accent)" }}>已选 {selected.size}</span>
            <button className="btn btn-sm btn-ghost" onClick={() => bulkSet("已归档")}>批量归档</button>
            <button className="btn btn-sm btn-ghost" onClick={() => bulkSet("驳回")}>批量驳回</button>
            <button className="btn btn-sm" onClick={bulkDelete} style={{ borderColor: "var(--candidate)", color: "var(--candidate)" }}>批量删除</button>
          </>
        )}
        <button className="btn btn-sm btn-accent" onClick={newSighting}>+ 新建观察</button>
      </div>

      <div className="admin-table-wrap">
        <table className="admin-table">
          <thead>
            <tr>
              <th style={{ width: 34 }}><input type="checkbox" checked={selected.size === filtered.length && filtered.length > 0} onChange={toggleAll} /></th>
              <th style={{ width: 64 }}>图片</th>
              <th style={{ width: 94 }}>ID</th>
              <th style={{ width: 130 }}>时间</th>
              <th>地点</th>
              <th style={{ width: 160 }}>关联个体</th>
              <th style={{ width: 110 }}>贡献者</th>
              <th>备注</th>
              <th style={{ width: 130 }}>状态</th>
              <th style={{ width: 60 }}></th>
            </tr>
          </thead>
          <tbody>
            {filtered.map(o => {
              const deer = o.deerId ? s.deer.find(d => d.id === o.deerId) : null;
              const media = getSightingMedia(o, deer);
              const photo = o.photo || deer?.photo || null;
              const tone = o.photoTone || deer?.photoTone || "none";
              return (
              <tr key={o.id} className={selected.has(o.id) ? "selected" : ""}>
                <td><input type="checkbox" checked={selected.has(o.id)} onChange={() => toggle(o.id)} /></td>
                <td>
                  {media.length ? (
                    <div
                      onClick={() => setOpenSighting(o.id)}
                      title={deer ? `${deer.name} · ${deer.id}` : "观察照片"}
                      style={{ width: 44, cursor: "pointer" }}>
                      <MediaPreview media={media[0]} tone={tone} aspect="4 / 5" compact label={media.length > 1 ? `+${media.length - 1}` : ""} />
                    </div>
                  ) : (
                    <div style={{
                      width: 44, height: 52,
                      background: "repeating-linear-gradient(135deg, var(--paper-2) 0 6px, var(--paper) 6px 12px)",
                      border: "1px dashed var(--rule)",
                      display: "flex", alignItems: "center", justifyContent: "center",
                      color: "var(--muted)", fontSize: 9, fontFamily: "JetBrains Mono, monospace",
                    }}>无图</div>
                  )}
                </td>
                <td className="mono" style={{ fontWeight: 600 }}>{o.id}</td>
                <td><EditableCell value={o.time} onChange={v => update(o.id, { time: v })} /></td>
                <td><EditableCell value={o.loc} onChange={v => update(o.id, { loc: v })} placeholder="地点…" /></td>
                <td>
                  <EditableSelect value={o.deerId || ""} options={deerOpts}
                    onChange={v => {
                      const deer = s.deer.find(d => d.id === v);
                      update(o.id, { deerId: v, deerName: deer ? deer.name : "（待归并）" });
                    }} />
                </td>
                <td><EditableCell value={o.contributor} onChange={v => update(o.id, { contributor: v })} placeholder="匿名" /></td>
                <td style={{ maxWidth: 300 }}><EditableCell value={o.note} onChange={v => update(o.id, { note: v })} placeholder="补充故事…" multiline /></td>
                <td>
                  <EditableSelect value={o.status}
                    options={["AI 初审", "志愿者审核中", "已归档", "驳回"]}
                    onChange={v => window.dzStore.setSightingStatus(o.id, v, who)} />
                </td>
                <td style={{ whiteSpace: "nowrap" }}>
                  <a className="admin-link" onClick={() => setOpenSighting(o.id)}>详情</a>
                </td>
              </tr>
              );
            })}
            {filtered.length === 0 && (
              <tr><td colSpan={10} style={{ padding: "40px 20px", textAlign: "center", color: "var(--muted)" }}>没有匹配的观察记录</td></tr>
            )}
          </tbody>
        </table>
      </div>

      <Drawer open={!!open} onClose={() => setOpenSighting(null)}
        title={open ? `观察记录 · ${open.id}` : ""}
        subtitle={open ? `${open.time} · ${open.contributor || "匿名"}` : ""}
        footer={open ? <>
          <button className="btn btn-ghost" onClick={() => { window.dzStore.remove("sightings", open.id, who); setOpenSighting(null); }}>删除这条</button>
          <button className="btn btn-accent" onClick={() => setOpenSighting(null)}>完成</button>
        </> : null}>
        {open && (() => {
          const drawerDeer = open.deerId ? s.deer.find(d => d.id === open.deerId) : null;
          const drawerTone = open.photoTone || drawerDeer?.photoTone || "none";
          const drawerMedia = getSightingMedia(open, drawerDeer);
          const currentMedia = drawerMedia[Math.min(activeMedia, Math.max(0, drawerMedia.length - 1))];
          return (
          <div>
            <div style={{ display: "grid", gridTemplateColumns: "180px 1fr", gap: 18, marginBottom: 22 }}>
              <div>
                <MediaPreview media={currentMedia} tone={drawerTone} aspect="4 / 5" label={open.loc} />
                <MediaStrip media={drawerMedia} active={activeMedia} onPick={setActiveMedia} tone={drawerTone} />
              </div>
              <div style={{ display: "grid", gap: 8, alignContent: "start" }}>
                <div className="form-label">观察照片 URL</div>
                <EditableCell value={open.photo || ""} onChange={v => update(open.id, { photo: v })} placeholder="粘贴图片 URL，留空则显示关联个体主图" multiline />
                <div className="form-label" style={{ marginTop: 6 }}>CSS 色调（可选）</div>
                <EditableCell value={open.photoTone || ""} onChange={v => update(open.id, { photoTone: v })} placeholder="例如 sepia(0.2)" />
                <input id={"sighting-media-" + open.id} type="file" accept="image/*,video/*" multiple style={{ display: "none" }}
                  onChange={e => { appendSupportMedia(open, e.target.files); e.target.value = ""; }} />
                <button className="btn btn-sm btn-ghost" onClick={() => document.getElementById("sighting-media-" + open.id)?.click()}>
                  添加非识别补充图片/视频
                </button>
                <div style={{ fontSize: 11.5, color: "var(--muted)", lineHeight: 1.55 }}>
                  补充素材只进入人工审核预览，不参与 AI 候选比对。
                </div>
              </div>
            </div>
            {[
              ["id", "ID", "text", false],
              ["time", "观察时间", "text", false],
              ["loc", "地点", "text", false],
              ["contributor", "贡献者", "text", false],
              ["note", "备注 / 故事", "text", true],
            ].map(([k, l, t, ml]) => (
              <div key={k} className="admin-form-row">
                <label>{l}</label>
                <div>{k === "id" ? <span className="mono">{open[k]}</span> : <EditableCell value={open[k]} onChange={v => update(open.id, { [k]: v })} multiline={ml} />}</div>
              </div>
            ))}
            <div className="admin-form-row">
              <label>关联个体</label>
              <div>
                <EditableSelect value={open.deerId || ""} options={deerOpts}
                  onChange={v => {
                    const deer = s.deer.find(d => d.id === v);
                    if (v) window.dzStore.mergeSighting(open.id, v, who);
                    else update(open.id, { deerId: "", deerName: "（待归并）" });
                  }} />
              </div>
            </div>
            <div className="admin-form-row">
              <label>状态</label>
              <div>
                <EditableSelect value={open.status} options={["AI 初审", "志愿者审核中", "已归档", "驳回"]}
                  onChange={v => window.dzStore.setSightingStatus(open.id, v, who)} />
              </div>
            </div>
          </div>
          );
        })()}
      </Drawer>
    </>
  );
}

// ============ DEER ============
function AdminDeer({ who }) {
  const s = window.useStore();
  const [q, setQ] = React.useState("");
  const [mode, setMode] = React.useState("all"); // all / verified / candidate
  const [openId, setOpenId] = React.useState(null);
  const [selected, setSelected] = React.useState(new Set());

  const filtered = s.deer.filter(d => {
    if (mode === "verified" && !d.verified) return false;
    if (mode === "candidate" && !d.candidate) return false;
    if (q) {
    const hay = `${d.id} ${d.name} ${d.nameEn} ${d.species || ""} ${d.region} ${(d.tags || []).join(" ")}`.toLowerCase();
      if (!hay.includes(q.toLowerCase())) return false;
    }
    return true;
  });

  const update = (id, patch) => window.dzStore.update("deer", id, patch, who);
  const newDeer = () => {
    const n = s.deer.length + 1;
    const id = "AN-" + String(n).padStart(3, "0");
    window.dzStore.add("deer", {
      id, name: "未命名个体", nameEn: "Unnamed", firstSeen: new Date().toISOString().slice(0, 10), lastSeen: new Date().toISOString().slice(0, 10),
      sightings: 0, contributors: 0, sex: "待核", estAge: "待评估", region: "（待确认）",
      speciesKey: "sika_deer", species: "梅花鹿", speciesEn: "Sika deer", category: "mammal",
      tags: [], photo: window.DEER_DATA[0].photo, photoTone: "grayscale(0.3) blur(1px)", gallery: [],
      story: "", palette: ["#28241c", "#78685a", "#b0a294"], verified: false, candidate: true,
      photoLicense: "待补充授权", photoSource: "", sourceStatus: "needs-license",
    }, who);
    setOpenId(id);
  };
  const toggle = id => { const n = new Set(selected); n.has(id) ? n.delete(id) : n.add(id); setSelected(n); };
  const bulkDelete = () => {
    if (!confirm(`删除 ${selected.size} 个个体档案？`)) return;
    window.dzStore.removeMany("deer", [...selected], who);
    setSelected(new Set());
  };

  const open = openId ? s.deer.find(d => d.id === openId) : null;

  return (
    <>
      <div className="admin-toolbar">
        <input type="search" placeholder="搜索 ID / 名字 / 区域 / 特征…" value={q} onChange={e => setQ(e.target.value)} style={{ width: 300 }} />
        <select value={mode} onChange={e => setMode(e.target.value)}>
          <option value="all">全部</option>
          <option value="verified">已归档</option>
          <option value="candidate">候选个体</option>
        </select>
        <span className="count">{filtered.length} / {s.deer.length}</span>
        <div className="sp"></div>
        {selected.size > 0 && <button className="btn btn-sm" onClick={bulkDelete} style={{ borderColor: "var(--candidate)", color: "var(--candidate)" }}>删除已选 {selected.size}</button>}
        <button className="btn btn-sm btn-accent" onClick={newDeer}>+ 新建动物档案</button>
      </div>

      <div className="admin-table-wrap">
        <table className="admin-table">
          <thead>
            <tr>
              <th style={{ width: 30 }}></th>
              <th style={{ width: 70 }}>照片</th>
              <th style={{ width: 78 }}>ID</th>
              <th>名字</th>
              <th>物种</th>
              <th>区域</th>
              <th>性别</th>
              <th>年龄</th>
              <th>首次</th>
              <th>最近</th>
              <th style={{ width: 68 }}>次数</th>
              <th>特征标签</th>
              <th style={{ width: 100 }}>状态</th>
              <th style={{ width: 60 }}></th>
            </tr>
          </thead>
          <tbody>
            {filtered.map(d => (
              <tr key={d.id} className={selected.has(d.id) ? "selected" : ""}>
                <td><input type="checkbox" checked={selected.has(d.id)} onChange={() => toggle(d.id)} /></td>
                <td>
                  <div style={{ width: 40, height: 50, backgroundImage: `url(${d.photo})`, backgroundSize: "cover", backgroundPosition: "center", filter: d.photoTone, border: "1px solid var(--rule)" }}></div>
                </td>
                <td className="mono" style={{ fontWeight: 600 }}>{d.id}</td>
                <td>
                  <EditableCell value={d.name} onChange={v => update(d.id, { name: v })} />
                  <div className="mono" style={{ fontSize: 10, color: "var(--muted)" }}>
                    <EditableCell value={d.nameEn} onChange={v => update(d.id, { nameEn: v })} />
                  </div>
                </td>
                <td>
                  <EditableSelect value={d.speciesKey || "sika_deer"}
                    options={(window.SPECIES_CATALOG || []).map(sp => [sp.key, sp.label])}
                    onChange={v => {
                      const sp = (window.SPECIES_CATALOG || []).find(x => x.key === v);
                      update(d.id, { speciesKey: v, species: sp?.label || v, speciesEn: sp?.en || "" });
                    }} />
                </td>
                <td><EditableCell value={d.region} onChange={v => update(d.id, { region: v })} /></td>
                <td><EditableCell value={d.sex} onChange={v => update(d.id, { sex: v })} /></td>
                <td><EditableCell value={d.estAge} onChange={v => update(d.id, { estAge: v })} /></td>
                <td><EditableCell value={d.firstSeen} onChange={v => update(d.id, { firstSeen: v })} format="date" /></td>
                <td><EditableCell value={d.lastSeen} onChange={v => update(d.id, { lastSeen: v })} format="date" /></td>
                <td><EditableCell value={d.sightings} onChange={v => update(d.id, { sightings: Number(v) || 0 })} format="number" /></td>
                <td><EditableTags value={d.tags} onChange={v => update(d.id, { tags: v })} /></td>
                <td>
                  <EditableSelect value={d.candidate ? "candidate" : (d.verified ? "verified" : "draft")}
                    options={[["verified", "已归档"], ["candidate", "候选"], ["draft", "草稿"]]}
                    onChange={v => update(d.id, { verified: v === "verified", candidate: v === "candidate" })} />
                </td>
                <td><a className="admin-link" onClick={() => setOpenId(d.id)}>详情</a></td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      <Drawer open={!!open} onClose={() => setOpenId(null)} width={720}
        title={open ? `${open.name} · ${open.id}` : ""}
        subtitle={open ? `最早 ${open.firstSeen} · 最近 ${open.lastSeen}` : ""}
        footer={open ? <>
          <button className="btn btn-ghost" onClick={() => { if (confirm("删除这个个体档案？")) { window.dzStore.remove("deer", open.id, who); setOpenId(null); } }}>删除档案</button>
          <button className="btn btn-accent" onClick={() => setOpenId(null)}>完成</button>
        </> : null}>
        {open && <DeerEditor deer={open} who={who} onClose={() => setOpenId(null)} />}
      </Drawer>
    </>
  );
}

function DeerEditor({ deer, who }) {
  const s = window.useStore();
  const update = (patch) => window.dzStore.update("deer", deer.id, patch, who);
  const photos = s.photos.filter(p => p.deerId === deer.id);

  return (
    <div>
      <div style={{ display: "grid", gridTemplateColumns: "160px 1fr", gap: 20, marginBottom: 22 }}>
        <div style={{ aspectRatio: "4/5", backgroundImage: `url(${deer.photo})`, backgroundSize: "cover", backgroundPosition: "center", filter: deer.photoTone, border: "1px solid var(--rule)" }}></div>
        <div style={{ display: "grid", gap: 8, alignContent: "start" }}>
          <div>
            <div className="form-label">主图 URL</div>
            <EditableCell value={deer.photo} onChange={v => update({ photo: v })} multiline />
          </div>
          <div>
            <div className="form-label">CSS 色调 (photoTone)</div>
            <EditableCell value={deer.photoTone} onChange={v => update({ photoTone: v })} placeholder='例如 sepia(0.2) saturate(0.9)' />
          </div>
          <div>
            <div className="form-label">图片授权 / 来源</div>
            <EditableCell value={deer.photoLicense || ""} onChange={v => update({ photoLicense: v })} placeholder="例如 授权摄影师 / Pexels / 机构供图" />
            <div style={{ marginTop: 8 }}>
              <EditableCell value={deer.photoSource || ""} onChange={v => update({ photoSource: v })} placeholder="来源链接或授权备注" />
            </div>
          </div>
        </div>
      </div>

      <div className="admin-form-row">
        <label>物种</label>
        <div>
          <EditableSelect value={deer.speciesKey || "sika_deer"}
            options={(window.SPECIES_CATALOG || []).map(sp => [sp.key, `${sp.label} · ${sp.en}`])}
            onChange={v => {
              const sp = (window.SPECIES_CATALOG || []).find(x => x.key === v);
              update({ speciesKey: v, species: sp?.label || v, speciesEn: sp?.en || "" });
            }} />
        </div>
      </div>

      {[
        ["name", "中文名"],
        ["nameEn", "英文名"],
        ["id", "ID（不可改）", true],
        ["sex", "性别"],
        ["estAge", "估计年龄"],
        ["region", "主要区域"],
        ["firstSeen", "首次观察", false, "date"],
        ["lastSeen", "最近观察", false, "date"],
        ["sightings", "观察次数", false, "number"],
        ["contributors", "贡献者数", false, "number"],
      ].map(([k, l, ro, fmt]) => (
        <div key={k} className="admin-form-row">
          <label>{l}</label>
          <div>{ro ? <span className="mono">{deer[k]}</span> : <EditableCell value={deer[k]} onChange={v => update({ [k]: fmt === "number" ? Number(v) || 0 : v })} format={fmt} />}</div>
        </div>
      ))}

      <div className="admin-form-row">
        <label>特征标签</label>
        <div><EditableTags value={deer.tags} onChange={v => update({ tags: v })} /></div>
      </div>

      <div className="admin-form-row">
        <label>状态</label>
        <div>
          <EditableSelect value={deer.candidate ? "candidate" : (deer.verified ? "verified" : "draft")}
            options={[["verified", "已归档"], ["candidate", "候选个体"], ["draft", "草稿"]]}
            onChange={v => update({ verified: v === "verified", candidate: v === "candidate" })} />
        </div>
      </div>

      <div className="admin-form-row">
        <label>featured</label>
        <div>
          <label style={{ display: "flex", gap: 6, alignItems: "center", fontSize: 12.5 }}>
            <input type="checkbox" checked={!!deer.featured} onChange={e => update({ featured: e.target.checked })} />
            在首页推荐位展示
          </label>
        </div>
      </div>

      <div className="admin-form-row">
        <label>色卡 Palette</label>
        <div style={{ display: "flex", gap: 6 }}>
          {(deer.palette || []).map((c, i) => (
            <input key={i} type="color" value={c} onChange={e => {
              const p = [...deer.palette]; p[i] = e.target.value; update({ palette: p });
            }} style={{ width: 40, height: 28, border: "1px solid var(--rule)", background: "transparent", padding: 0 }} />
          ))}
        </div>
      </div>

      <div className="admin-form-row">
        <label>故事 / 档案说明</label>
        <div><EditableCell value={deer.story} onChange={v => update({ story: v })} multiline placeholder="这段文字会出现在动物档案详情页…" /></div>
      </div>

      <h4 className="serif" style={{ fontSize: 17, marginTop: 20, marginBottom: 10 }}>照片库 · {photos.length} 张</h4>
      <div className="admin-photo-grid" style={{ gridTemplateColumns: "repeat(4, 1fr)" }}>
        {photos.map(p => (
          <div key={p.id} className="admin-photo">
            <img src={p.url} style={{ filter: p.tone !== "none" ? p.tone : undefined }} />
            <div className="admin-photo-role">{p.role === "cover" ? "封面" : "画廊"}</div>
            <div className="admin-photo-meta">
              <span>{p.id}</span>
            </div>
          </div>
        ))}
        {photos.length === 0 && <div style={{ gridColumn: "1 / -1", padding: 16, textAlign: "center", color: "var(--muted)", fontSize: 12 }}>该个体尚无照片</div>}
      </div>
    </div>
  );
}

// ============ PHOTOS ============
function AdminPhotos({ who }) {
  const s = window.useStore();
  const [q, setQ] = React.useState("");
  const [deerFilter, setDeerFilter] = React.useState("all");
  const [roleFilter, setRoleFilter] = React.useState("all");
  const [selected, setSelected] = React.useState(new Set());
  const [openId, setOpenId] = React.useState(null);

  const filtered = s.photos.filter(p => {
    if (deerFilter !== "all" && p.deerId !== deerFilter) return false;
    if (roleFilter !== "all" && p.role !== roleFilter) return false;
    if (q) {
      const hay = `${p.id} ${p.deerName} ${p.species || ""} ${p.location} ${p.contributor} ${p.photoLicense || ""} ${p.photoSource || ""} ${(p.tags || []).join(" ")}`.toLowerCase();
      if (!hay.includes(q.toLowerCase())) return false;
    }
    return true;
  });

  const toggle = id => { const n = new Set(selected); n.has(id) ? n.delete(id) : n.add(id); setSelected(n); };
  const update = (id, patch) => window.dzStore.update("photos", id, patch, who);

  const bulkReassign = () => {
    const target = prompt("目标个体 ID（例如 DL-003）:");
    if (!target) return;
    const deer = s.deer.find(d => d.id === target);
    if (!deer) { alert("未找到该个体"); return; }
    selected.forEach(id => update(id, { deerId: target, deerName: deer.name, species: deer.species, speciesKey: deer.speciesKey }));
    setSelected(new Set());
  };
  const bulkDelete = () => {
    if (!confirm(`删除 ${selected.size} 张照片？`)) return;
    window.dzStore.removeMany("photos", [...selected], who);
    setSelected(new Set());
  };
  const bulkSetRole = (role) => {
    selected.forEach(id => update(id, { role }));
    setSelected(new Set());
  };

  const open = openId ? s.photos.find(p => p.id === openId) : null;

  return (
    <>
      <div className="admin-toolbar">
        <input type="search" placeholder="搜索 ID / 个体 / 地点 / 标签…" value={q} onChange={e => setQ(e.target.value)} style={{ width: 260 }} />
        <select value={deerFilter} onChange={e => setDeerFilter(e.target.value)}>
          <option value="all">全部个体</option>
          {s.deer.map(d => <option key={d.id} value={d.id}>{d.name} · {d.id}</option>)}
        </select>
        <select value={roleFilter} onChange={e => setRoleFilter(e.target.value)}>
          <option value="all">全部类型</option>
          <option value="cover">封面主图</option>
          <option value="gallery">画廊</option>
        </select>
        <span className="count">{filtered.length} / {s.photos.length}</span>
        <div className="sp"></div>
        {selected.size > 0 && <>
          <span className="count" style={{ color: "var(--accent)" }}>已选 {selected.size}</span>
          <button className="btn btn-sm btn-ghost" onClick={() => bulkSetRole("cover")}>设为封面</button>
          <button className="btn btn-sm btn-ghost" onClick={() => bulkSetRole("gallery")}>设为画廊</button>
          <button className="btn btn-sm btn-ghost" onClick={bulkReassign}>批量改归属</button>
          <button className="btn btn-sm" onClick={bulkDelete} style={{ borderColor: "var(--candidate)", color: "var(--candidate)" }}>删除</button>
        </>}
      </div>

      <div style={{ background: "var(--paper)", border: "1px solid var(--rule)", padding: 14 }}>
        <div className="admin-photo-grid">
          {filtered.map(p => (
            <div key={p.id} className={"admin-photo" + (selected.has(p.id) ? " selected" : "")}>
              <img src={p.url} style={{ filter: p.tone !== "none" ? p.tone : undefined }} onClick={() => setOpenId(p.id)} />
              <div className="admin-photo-check" onClick={() => toggle(p.id)}>{selected.has(p.id) ? "✓" : "＋"}</div>
              <div className="admin-photo-role">{p.role === "cover" ? "封面" : "画廊"}</div>
              <div className="admin-photo-meta">
                <span>{p.id}</span>
                <span>{p.deerId || "—"}</span>
              </div>
            </div>
          ))}
          {filtered.length === 0 && <div style={{ gridColumn: "1 / -1", padding: 40, textAlign: "center", color: "var(--muted)" }}>没有匹配的照片</div>}
        </div>
      </div>

      <Drawer open={!!open} onClose={() => setOpenId(null)} width={560}
        title={open ? `照片 · ${open.id}` : ""}
        subtitle={open ? `${open.deerName || "未归属"} · ${open.location || "未知地点"}` : ""}
        footer={open ? <>
          <button className="btn btn-ghost" onClick={() => { if (confirm("删除这张照片？")) { window.dzStore.remove("photos", open.id, who); setOpenId(null); } }}>删除</button>
          <button className="btn btn-accent" onClick={() => setOpenId(null)}>完成</button>
        </> : null}>
        {open && (
          <div>
            <div style={{ aspectRatio: "4/3", border: "1px solid var(--rule)", background: "var(--paper-2)", marginBottom: 18 }}>
              <img src={open.url} style={{ width: "100%", height: "100%", objectFit: "cover", filter: open.tone !== "none" ? open.tone : undefined }} />
            </div>
            {[
              ["url", "URL", false, null, null, true],
              ["deerId", "归属个体 ID", true, s.deer.map(d => d.id)],
              ["role", "类型", true, [["cover", "封面"], ["gallery", "画廊"]]],
              ["taken", "拍摄日期", false, null, "date"],
              ["location", "地点"],
              ["contributor", "贡献者"],
              ["tone", "CSS 色调"],
              ["photoLicense", "授权"],
              ["photoSource", "来源"],
              ["photoSourceNote", "来源备注"],
              ["sourceStatus", "授权状态"],
            ].map(([k, l, select, opts, fmt, multiline]) => (
              <div key={k} className="admin-form-row">
                <label>{l}</label>
                <div>{select ? (
                  <EditableSelect value={open[k]} options={opts || [["", "—"], ...s.deer.map(d => [d.id, `${d.name} · ${d.id}`])]}
                    onChange={v => {
                      if (k === "deerId") {
                        const deer = s.deer.find(d => d.id === v);
                        update(open.id, { deerId: v, deerName: deer ? deer.name : "", species: deer ? deer.species : "", speciesKey: deer ? deer.speciesKey : "" });
                      } else update(open.id, { [k]: v });
                    }} />
                ) : <EditableCell value={open[k]} onChange={v => update(open.id, { [k]: v })} format={fmt} multiline={multiline || k === "url"} />}</div>
              </div>
            ))}
            <div className="admin-form-row">
              <label>标签</label>
              <div><EditableTags value={open.tags} onChange={v => update(open.id, { tags: v })} /></div>
            </div>
          </div>
        )}
      </Drawer>
    </>
  );
}

Object.assign(window, { AdminSightings, AdminDeer, AdminPhotos, DeerEditor });
