// 资产 · 背景音乐素材库
// 两块组件：
//  1) 人群表（6 条房产人群预设，含年龄段 / 第一帧画面提示 / 口播第一句关键词 / 推荐风格 / 当前命中曲数）
//  2) BGM 上传管理（顶栏批量上传 + 风格自定义弹窗（绑人群） + 曲库列表 + 人群推荐预览）
//
// 风格 schema：{name, audience_slugs:[]}  —— 风格与人群多对多
// 复刻 / 合成时按 audience_slug 查 recommend 接口拿匹配曲库

const BGMLibraryPage = () => {
  const [tab, setTab] = React.useState("audiences"); // audiences | tracks

  const [audiences, setAudiences] = React.useState([]);
  const [styles, setStyles] = React.useState([]);          // [{name, audience_slugs:[]}]
  const [tracks, setTracks] = React.useState([]);
  const [recommendByAudience, setRecommendByAudience] = React.useState({}); // slug → {total, bound_styles}

  const [filterAudience, setFilterAudience] = React.useState("");
  const [filterStyle, setFilterStyle] = React.useState("");
  const [recommendAudience, setRecommendAudience] = React.useState(""); // 曲库 tab 顶部「按人群预览」
  const [recommendList, setRecommendList] = React.useState(null);

  const [loading, setLoading] = React.useState(false);
  const [err, setErr] = React.useState("");
  const [msg, setMsg] = React.useState("");

  // 上传
  const fileInputRef = React.useRef(null);
  const topUploadRef = React.useRef(null);
  const [uploadAudiences, setUploadAudiences] = React.useState([]);
  const [uploadStyles, setUploadStyles] = React.useState([]);
  const [uploadNote, setUploadNote] = React.useState("");
  const [uploading, setUploading] = React.useState(false);

  // 风格自定义弹窗
  const [styleModalOpen, setStyleModalOpen] = React.useState(false);
  const [styleModalName, setStyleModalName] = React.useState("");
  const [styleModalAudiences, setStyleModalAudiences] = React.useState([]);
  const [styleModalEditing, setStyleModalEditing] = React.useState(null); // null = 新建；string = 编辑该名
  const [styleBusy, setStyleBusy] = React.useState(false);

  const loadCore = React.useCallback(async () => {
    setLoading(true); setErr("");
    try {
      const [a, s, t] = await Promise.all([
        API.bgmLibrary.audiences(),
        API.bgmLibrary.listStyles(),
        API.bgmLibrary.listTracks(filterAudience, filterStyle),
      ]);
      setAudiences(a.audiences || []);
      setStyles(s.styles || []);
      setTracks(t.tracks || []);
    } catch (e) {
      setErr(String(e.message || e));
    } finally {
      setLoading(false);
    }
  }, [filterAudience, filterStyle]);

  // 拉每个 audience 的命中数（人群表第 6 列展示）
  const loadRecommendCounts = React.useCallback(async (audList) => {
    const out = {};
    for (const a of audList) {
      try {
        const r = await API.bgmLibrary.recommendForAudience(a.slug, 200);
        out[a.slug] = { total: r.total || 0, bound_styles: r.bound_styles || [] };
      } catch (_) {
        out[a.slug] = { total: 0, bound_styles: [] };
      }
    }
    setRecommendByAudience(out);
  }, []);

  React.useEffect(() => { loadCore(); }, [loadCore]);
  React.useEffect(() => {
    if (audiences.length > 0) loadRecommendCounts(audiences);
  }, [audiences, tracks, styles, loadRecommendCounts]);

  const toggle = (list, val, setter) => {
    setter(list.includes(val) ? list.filter(x => x !== val) : [...list, val]);
  };

  // ---- 风格弹窗 ----
  const openNewStyleModal = () => {
    setStyleModalEditing(null);
    setStyleModalName("");
    setStyleModalAudiences([]);
    setStyleModalOpen(true);
  };
  const openEditStyleModal = (s) => {
    setStyleModalEditing(s.name);
    setStyleModalName(s.name);
    setStyleModalAudiences([...(s.audience_slugs || [])]);
    setStyleModalOpen(true);
  };
  const submitStyleModal = async () => {
    const n = (styleModalName || "").trim();
    if (!n) return;
    setStyleBusy(true); setErr(""); setMsg("");
    try {
      if (styleModalEditing) {
        const r = await API.bgmLibrary.updateStyleAudiences(styleModalEditing, styleModalAudiences);
        setStyles(r.styles || []);
        setMsg(`已更新「${styleModalEditing}」的人群绑定`);
      } else {
        const r = await API.bgmLibrary.addStyle(n, styleModalAudiences);
        setStyles(r.styles || []);
        setMsg(r.added ? `已添加风格「${n}」` : `「${n}」已存在，已合并人群绑定`);
      }
      setStyleModalOpen(false);
    } catch (e) {
      setErr(String(e.message || e));
    } finally {
      setStyleBusy(false);
    }
  };

  const handleDeleteStyle = async (name) => {
    if (!confirm(`删除风格「${name}」？已绑定此风格的曲目分类会被清掉。`)) return;
    setStyleBusy(true); setErr("");
    try {
      const r = await API.bgmLibrary.deleteStyle(name);
      setStyles(r.styles || []);
      setUploadStyles(uploadStyles.filter(s => s !== name));
      if (filterStyle === name) setFilterStyle("");
      await loadCore();
    } catch (e) {
      setErr(String(e.message || e));
    } finally {
      setStyleBusy(false);
    }
  };

  // ---- 上传 ----
  const handleUpload = async (files, opts = {}) => {
    if (!files || !files.length) return;
    setUploading(true); setErr(""); setMsg("");
    const fd = new FormData();
    for (const f of files) fd.append("files", f);
    const audSlugs = opts.audiences || uploadAudiences;
    const styleNames = opts.styles || uploadStyles;
    const note = opts.note ?? uploadNote;
    fd.append("audience_slugs", audSlugs.join(","));
    fd.append("style_names", styleNames.join(","));
    fd.append("note", note);
    try {
      const r = await API.bgmLibrary.uploadTracks(fd);
      const ok = (r.uploaded || []).length;
      const skip = (r.skipped || []).length;
      const skipMsg = skip ? `，跳过 ${skip} 条（${(r.skipped || []).map(s=>s.reason).slice(0,3).join("; ")}）` : "";
      setMsg(`批量上传完成：成功 ${ok} 条${skipMsg}`);
      await loadCore();
    } catch (e) {
      setErr(String(e.message || e));
    } finally {
      setUploading(false);
      if (fileInputRef.current) fileInputRef.current.value = "";
      if (topUploadRef.current) topUploadRef.current.value = "";
    }
  };

  const handleDeleteTrack = async (track) => {
    if (!confirm(`删除「${track.original_filename || track.filename}」？`)) return;
    try {
      await API.bgmLibrary.deleteTrack(track.track_id);
      await loadCore();
    } catch (e) {
      setErr(String(e.message || e));
    }
  };

  // ---- 人群推荐预览 ----
  const loadRecommend = async (slug) => {
    setRecommendAudience(slug);
    if (!slug) { setRecommendList(null); return; }
    try {
      const r = await API.bgmLibrary.recommendForAudience(slug, 50);
      setRecommendList(r);
    } catch (e) {
      setErr(String(e.message || e));
      setRecommendList(null);
    }
  };

  const audienceName = (slug) => {
    const a = audiences.find(x => x.slug === slug);
    return a ? a.name : slug;
  };

  return (
    <>
      <Topbar
        crumbs={["资产", "背景音乐素材库"]}
        right={<>
          <span className="pill" style={{
            background: "color-mix(in oklch, var(--cyan) 14%, var(--bg-1))",
            color: "var(--cyan)",
          }}>
            <Icon name="music" size={10}/>
            {audiences.length} 人群 · {styles.length} 风格 · {tracks.length} 曲目
          </span>
          {/* 顶栏批量上传：随便在哪个 tab 都能用 */}
          <input
            ref={topUploadRef}
            type="file"
            accept=".mp3,.m4a,.wav,.aac,.flac,.ogg"
            multiple
            onChange={(e)=>handleUpload(e.target.files, { audiences: [], styles: [], note: "" })}
            style={{display:"none"}}
          />
          <button className="btn primary" onClick={()=>topUploadRef.current?.click()} disabled={uploading}>
            <Icon name="plus" size={11}/>{uploading ? "上传中…" : "批量上传 BGM"}
          </button>
          <button className="btn ghost" onClick={openNewStyleModal}>
            <Icon name="sparkle" size={11}/>新建风格
          </button>
          <button className="btn ghost" onClick={loadCore} disabled={loading}>
            <Icon name="settings" size={11}/>{loading ? "刷新…" : "刷新"}
          </button>
        </>}/>

      {/* Tab 切换 */}
      <div className="row gap-0" style={{padding:"0 16px", borderBottom:"1px solid var(--line-1)", background:"var(--bg-1)"}}>
        {[
          {k:"audiences",l:"模块一 · 人群 × 风格绑定"},
          {k:"tracks",l:"模块二 · BGM 曲库 / 推荐预览"},
        ].map(t=>(
          <div key={t.k} onClick={()=>setTab(t.k)} style={{
            padding:"10px 18px", fontSize:12, cursor:"pointer",
            color: tab===t.k?"var(--fg-0)":"var(--fg-3)", fontWeight: tab===t.k?600:400,
            borderBottom: tab===t.k?"2px solid var(--cyan)":"2px solid transparent",
          }}>{t.l}</div>
        ))}
      </div>

      {(err || msg) && (
        <div style={{padding:"8px 16px 0"}}>
          {err && <div className="tag" style={{background:"var(--pink-soft)", color:"var(--pink)"}}>{err}</div>}
          {msg && !err && <div className="tag" style={{background:"var(--cyan-soft)", color:"var(--cyan)"}}>{msg}</div>}
        </div>
      )}

      <div className="main-body">
        {tab === "audiences" ? (
          <div className="col grow scroll" style={{padding:"14px 14px 20px", gap:12}}>
            {/* 人群预设表 */}
            <div className="card">
              <div className="card-hd">
                <span className="card-title">房产 6 大人群 · 标签 / 画面 / 口播 / 推荐 BGM 风格 / 命中曲数</span>
                <div className="grow"/>
                <span className="tag cyan">{audiences.length} 条预设</span>
              </div>
              <div className="col">
                <div className="row" style={{padding:"8px 14px", borderBottom:"1px solid var(--line-1)", gap:12, fontSize:10, color:"var(--fg-3)", fontWeight:600, textTransform:"uppercase", letterSpacing:".5px"}}>
                  <div style={{flex:1.1}}>人群标签</div>
                  <div style={{width:90}}>年龄段</div>
                  <div style={{flex:2}}>第一帧画面提示</div>
                  <div style={{flex:2}}>口播第一句关键词</div>
                  <div style={{flex:1.8}}>已绑风格 / 命中曲</div>
                </div>
                {audiences.map((a,i)=> {
                  const recAt = recommendByAudience[a.slug] || {total:0, bound_styles:[]};
                  return (
                    <div key={a.slug} className="row" style={{
                      padding:"12px 14px", gap:12, alignItems:"flex-start",
                      borderBottom: i<audiences.length-1?"1px solid var(--line-1)":"none",
                    }}>
                      <div className="col gap-3" style={{flex:1.1}}>
                        <span style={{fontSize:12.5, color:"var(--fg-0)", fontWeight:600}}>{a.name}</span>
                        <span className="mono" style={{fontSize:9.5, color:"var(--fg-3)"}}>{a.slug}</span>
                      </div>
                      <div className="mono" style={{width:90, fontSize:11, color:"var(--fg-1)"}}>{a.age_range}</div>
                      <div style={{flex:2, fontSize:11.5, color:"var(--fg-1)", lineHeight:1.55}}>{a.first_frame_hint}</div>
                      <div style={{flex:2, fontSize:11.5, color:"var(--fg-1)", lineHeight:1.55}}>{a.opening_keyword}</div>
                      <div className="col gap-4" style={{flex:1.8}}>
                        <div className="row gap-4" style={{flexWrap:"wrap"}}>
                          {(recAt.bound_styles || []).length === 0
                            ? <span className="tag" style={{fontSize:9.5}}>未绑风格</span>
                            : recAt.bound_styles.map(s=>(<span key={s} className="tag cyan" style={{fontSize:10}}>{s}</span>))}
                        </div>
                        <div className="row gap-6">
                          <span className="tag" style={{
                            fontSize:10.5,
                            background: recAt.total > 0 ? "var(--cyan-soft)" : "var(--bg-2)",
                            color: recAt.total > 0 ? "var(--cyan)" : "var(--fg-3)",
                          }}>{recAt.total} 条 BGM 命中</span>
                          <button className="btn sm ghost" onClick={()=>{ setTab("tracks"); loadRecommend(a.slug); }}>
                            <Icon name="filter" size={10}/>预览
                          </button>
                        </div>
                      </div>
                    </div>
                  );
                })}
              </div>
            </div>

            {/* 风格清单：每条带 audience_slugs，可编辑可删 */}
            <div className="card">
              <div className="card-hd">
                <span className="card-title">风格清单 · 与人群多对多绑定</span>
                <div className="grow"/>
                <span className="tag">{styles.length} 条</span>
                <button className="btn sm primary" onClick={openNewStyleModal}>
                  <Icon name="plus" size={10}/>新建风格
                </button>
              </div>
              {styles.length === 0 ? (
                <div style={{padding:24, textAlign:"center", color:"var(--fg-3)", fontSize:12}}>
                  暂无风格，点右上「新建风格」开始。
                </div>
              ) : (
                <div className="col">
                  <div className="row" style={{padding:"8px 14px", borderBottom:"1px solid var(--line-1)", gap:12, fontSize:10, color:"var(--fg-3)", fontWeight:600, textTransform:"uppercase", letterSpacing:".5px"}}>
                    <div style={{flex:1}}>风格名</div>
                    <div style={{flex:3}}>绑定人群</div>
                    <div style={{width:120, textAlign:"right"}}>操作</div>
                  </div>
                  {styles.map((s,i)=>(
                    <div key={s.name} className="row" style={{
                      padding:"10px 14px", gap:12, alignItems:"center",
                      borderBottom: i<styles.length-1?"1px solid var(--line-1)":"none",
                    }}>
                      <div style={{flex:1, fontSize:12, color:"var(--fg-0)", fontWeight:500}}>{s.name}</div>
                      <div className="row gap-4" style={{flex:3, flexWrap:"wrap"}}>
                        {(s.audience_slugs || []).length === 0
                          ? <span className="tag" style={{fontSize:9.5}}>未绑</span>
                          : (s.audience_slugs || []).map(slug=>(
                              <span key={slug} className="tag cyan" style={{fontSize:10}}>{audienceName(slug)}</span>
                            ))}
                      </div>
                      <div className="row gap-4" style={{width:120, justifyContent:"flex-end"}}>
                        <button className="btn sm ghost" onClick={()=>openEditStyleModal(s)}>
                          <Icon name="settings" size={10}/>编辑
                        </button>
                        <button className="btn sm ghost" onClick={()=>handleDeleteStyle(s.name)} disabled={styleBusy}>
                          <Icon name="close" size={10}/>删
                        </button>
                      </div>
                    </div>
                  ))}
                </div>
              )}
            </div>

            <div className="card">
              <div className="card-hd"><span className="card-title">联动说明</span></div>
              <div style={{padding:"12px 14px", fontSize:11.5, color:"var(--fg-2)", lineHeight:1.7}}>
                · <b>风格 ↔ 人群多对多</b>：一条风格可同时绑给多个人群；一个人群可关联多条风格。<br/>
                · <b>曲目挂哪条</b>：曲目本身可选直接打人群标签 + 多个风格。复刻时按 audience 拉曲时，会合并「直接打 audience 的曲」与「该 audience 绑定的风格命中曲」。<br/>
                · <b>复刻自动选曲</b>：质检合成 (<span className="mono">POST /api/jobs/&#123;id&#125;/compose</span>) 现已支持 <span className="mono">audience_slug</span> 字段，传入后会优先用该人群推荐的曲池；未命中再回退 <span className="mono">music.zip</span> 随机池。
              </div>
            </div>
          </div>
        ) : (
          <>
            {/* 左侧：上传 + 自定义风格快捷入口 */}
            <div className="col scroll" style={{width:300, flexShrink:0, padding:"14px 12px", gap:12, borderRight:"1px solid var(--line-soft)"}}>
              <div className="card" style={{padding:12}}>
                <div className="kpi-l" style={{marginBottom:8}}>批量上传（可一次选多个文件）</div>
                <input
                  ref={fileInputRef}
                  type="file"
                  accept=".mp3,.m4a,.wav,.aac,.flac,.ogg"
                  multiple
                  onChange={(e)=>handleUpload(e.target.files)}
                  style={{display:"none"}}
                />
                <div className="col gap-8">
                  <div className="kpi-l">人群（多选） · 下拉</div>
                  <select
                    value=""
                    onChange={(e)=>{
                      const v = e.target.value;
                      if (v && !uploadAudiences.includes(v)) {
                        setUploadAudiences([...uploadAudiences, v]);
                      }
                      e.target.value = "";
                    }}
                    style={{padding:"6px 8px", fontSize:11.5, border:"1px solid var(--line-1)", borderRadius:4, background:"var(--bg-1)"}}>
                    <option value="">+ 选人群分类</option>
                    {audiences.filter(a=>!uploadAudiences.includes(a.slug)).map(a=>(
                      <option key={a.slug} value={a.slug}>{a.name} · {a.age_range}</option>
                    ))}
                  </select>
                  <div className="row gap-4" style={{flexWrap:"wrap"}}>
                    {uploadAudiences.map(slug=>(
                      <span key={slug} className="tag cyan" style={{fontSize:10.5}}>
                        {audienceName(slug)}
                        <span onClick={()=>setUploadAudiences(uploadAudiences.filter(x=>x!==slug))}
                              style={{marginLeft:4, cursor:"pointer", fontWeight:700}}>×</span>
                      </span>
                    ))}
                  </div>

                  <div className="kpi-l" style={{marginTop:4}}>风格（多选）</div>
                  <select
                    value=""
                    onChange={(e)=>{
                      if (e.target.value && !uploadStyles.includes(e.target.value)) {
                        setUploadStyles([...uploadStyles, e.target.value]);
                      }
                      e.target.value = "";
                    }}
                    style={{padding:"6px 8px", fontSize:11.5, border:"1px solid var(--line-1)", borderRadius:4, background:"var(--bg-1)"}}>
                    <option value="">+ 添加风格分类</option>
                    {styles.filter(s=>!uploadStyles.includes(s.name)).map(s=>(
                      <option key={s.name} value={s.name}>{s.name}</option>
                    ))}
                  </select>
                  <div className="row gap-4" style={{flexWrap:"wrap"}}>
                    {uploadStyles.map(s=>(
                      <span key={s} className="tag cyan" style={{fontSize:10.5}}>
                        {s}
                        <span onClick={()=>setUploadStyles(uploadStyles.filter(x=>x!==s))}
                              style={{marginLeft:4, cursor:"pointer", fontWeight:700}}>×</span>
                      </span>
                    ))}
                  </div>

                  <button className="btn sm ghost" onClick={openNewStyleModal} style={{marginTop:2}}>
                    <Icon name="sparkle" size={10}/>没合适风格？自定义一个
                  </button>

                  <div className="kpi-l" style={{marginTop:4}}>备注（可选）</div>
                  <input
                    type="text"
                    value={uploadNote}
                    onChange={(e)=>setUploadNote(e.target.value)}
                    placeholder="例：80s 朱明瑛风"
                    style={{padding:"6px 8px", fontSize:11.5, border:"1px solid var(--line-1)", borderRadius:4, background:"var(--bg-1)"}}/>

                  <button
                    className="btn primary"
                    onClick={()=>fileInputRef.current?.click()}
                    disabled={uploading}
                    style={{marginTop:6}}>
                    <Icon name="plus" size={11}/>
                    {uploading ? "上传中…" : "选择音频文件…"}
                  </button>
                  <span style={{fontSize:10, color:"var(--fg-3)"}}>
                    支持 mp3 / m4a / wav / aac / flac / ogg，单文件 ≤ 50MB，可一次多选
                  </span>
                </div>
              </div>
            </div>

            {/* 右侧：人群推荐预览 + 全量曲库列表 */}
            <div className="col grow scroll" style={{padding:"14px 14px 20px", gap:12, background:"var(--bg-0)"}}>
              <div className="card">
                <div className="card-hd">
                  <span className="card-title">按人群推荐预览（复刻时实际用到的曲池）</span>
                  <div className="grow"/>
                  <select value={recommendAudience} onChange={(e)=>loadRecommend(e.target.value)}
                          style={{padding:"4px 8px", fontSize:11.5, border:"1px solid var(--line-1)", borderRadius:4, background:"var(--bg-1)"}}>
                    <option value="">选人群</option>
                    {audiences.map(a=>(<option key={a.slug} value={a.slug}>{a.name}</option>))}
                  </select>
                </div>
                {!recommendAudience ? (
                  <div style={{padding:18, textAlign:"center", color:"var(--fg-3)", fontSize:12}}>
                    选一个人群即可预览该人群的可用 BGM 池。
                  </div>
                ) : !recommendList ? (
                  <div style={{padding:18, textAlign:"center", color:"var(--fg-3)", fontSize:12}}>加载中…</div>
                ) : (
                  <div className="col">
                    <div style={{padding:"10px 14px", fontSize:11.5, color:"var(--fg-2)"}}>
                      人群 <b>{audienceName(recommendList.audience)}</b> · 已绑风格 [
                      {(recommendList.bound_styles || []).join(" · ") || "无"}
                      ] · 命中曲目 <b>{recommendList.total}</b> 条
                    </div>
                    {recommendList.tracks.length === 0 ? (
                      <div style={{padding:"6px 14px 14px", color:"var(--fg-3)", fontSize:11.5}}>
                        当前该人群没有任何命中曲目。建议先给「{audienceName(recommendList.audience)}」绑定 1-2 条风格，并上传对应风格的 BGM。
                      </div>
                    ) : recommendList.tracks.map((t,i)=>(
                      <div key={t.track_id} className="row" style={{
                        padding:"8px 14px", gap:12, alignItems:"center",
                        borderTop:"1px solid var(--line-1)",
                      }}>
                        <div className="col gap-2" style={{flex:1.6, minWidth:0}}>
                          <span style={{fontSize:11.5, color:"var(--fg-0)", fontWeight:500,
                                        overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap"}}>
                            {t.original_filename || t.filename}
                          </span>
                          <span className="mono" style={{fontSize:9.5, color:"var(--fg-3)"}}>
                            {(t._match_reason || []).join(" + ")}
                            {(t._matched_styles || []).length > 0 && ` · 经风格 ${(t._matched_styles).join("/")}`}
                          </span>
                        </div>
                        <div style={{flex:1.2}}>
                          <audio controls src={API.bgmLibrary.streamUrl(t.track_id)}
                                 style={{width:"100%", height:28}}/>
                        </div>
                      </div>
                    ))}
                  </div>
                )}
              </div>

              <div className="row gap-8" style={{flexWrap:"wrap"}}>
                <div className="row gap-4" style={{alignItems:"center"}}>
                  <span className="kpi-l">人群过滤</span>
                  <select value={filterAudience} onChange={(e)=>setFilterAudience(e.target.value)}
                          style={{padding:"4px 8px", fontSize:11.5, border:"1px solid var(--line-1)", borderRadius:4, background:"var(--bg-1)"}}>
                    <option value="">全部人群</option>
                    {audiences.map(a=>(<option key={a.slug} value={a.slug}>{a.name}</option>))}
                  </select>
                </div>
                <div className="row gap-4" style={{alignItems:"center"}}>
                  <span className="kpi-l">风格过滤</span>
                  <select value={filterStyle} onChange={(e)=>setFilterStyle(e.target.value)}
                          style={{padding:"4px 8px", fontSize:11.5, border:"1px solid var(--line-1)", borderRadius:4, background:"var(--bg-1)"}}>
                    <option value="">全部风格</option>
                    {styles.map(s=>(<option key={s.name} value={s.name}>{s.name}</option>))}
                  </select>
                </div>
                {(filterAudience || filterStyle) && (
                  <button className="btn sm ghost" onClick={()=>{ setFilterAudience(""); setFilterStyle(""); }}>
                    <Icon name="close" size={10}/>清除过滤
                  </button>
                )}
                <div className="grow"/>
                <span className="tag">{tracks.length} 条</span>
              </div>

              <div className="card">
                <div className="card-hd"><span className="card-title">全量曲库</span></div>
                {tracks.length === 0 ? (
                  <div style={{padding:24, textAlign:"center", color:"var(--fg-3)", fontSize:12}}>
                    {loading ? "加载中…" : "暂无曲目。顶栏「批量上传 BGM」开始。"}
                  </div>
                ) : (
                  <div className="col">
                    <div className="row" style={{padding:"8px 14px", borderBottom:"1px solid var(--line-1)", gap:12, fontSize:10, color:"var(--fg-3)", fontWeight:600, textTransform:"uppercase", letterSpacing:".5px"}}>
                      <div style={{flex:2.4}}>名称</div>
                      <div style={{flex:1.4}}>人群</div>
                      <div style={{flex:1.4}}>风格</div>
                      <div style={{width:200}}>试听</div>
                      <div style={{width:60, textAlign:"right"}}>操作</div>
                    </div>
                    {tracks.map((t,i)=>(
                      <div key={t.track_id} className="row" style={{
                        padding:"10px 14px", gap:12, alignItems:"center",
                        borderBottom: i<tracks.length-1?"1px solid var(--line-1)":"none",
                      }}>
                        <div className="col gap-2" style={{flex:2.4, minWidth:0}}>
                          <span style={{fontSize:11.5, color:"var(--fg-0)", fontWeight:500,
                                        overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap"}}>
                            {t.original_filename || t.filename}
                          </span>
                          <span className="mono" style={{fontSize:9.5, color:"var(--fg-3)"}}>
                            {t.track_id} · {(t.size_bytes/1024).toFixed(0)} KB
                          </span>
                          {t.note && <span style={{fontSize:10.5, color:"var(--fg-3)"}}>{t.note}</span>}
                        </div>
                        <div className="row gap-3" style={{flex:1.4, flexWrap:"wrap"}}>
                          {(t.audience_slugs || []).length === 0
                            ? <span className="tag" style={{fontSize:9.5}}>未分类</span>
                            : (t.audience_slugs || []).map(s=>(
                                <span key={s} className="tag" style={{fontSize:9.5}}>{audienceName(s)}</span>
                              ))}
                        </div>
                        <div className="row gap-3" style={{flex:1.4, flexWrap:"wrap"}}>
                          {(t.style_names || []).length === 0
                            ? <span className="tag" style={{fontSize:9.5}}>—</span>
                            : (t.style_names || []).map(s=>(
                                <span key={s} className="tag cyan" style={{fontSize:9.5}}>{s}</span>
                              ))}
                        </div>
                        <div style={{width:200}}>
                          <audio controls src={API.bgmLibrary.streamUrl(t.track_id)}
                                 style={{width:"100%", height:28}}/>
                        </div>
                        <div style={{width:60, textAlign:"right"}}>
                          <button className="btn sm ghost" onClick={()=>handleDeleteTrack(t)}>
                            <Icon name="close" size={10}/>删
                          </button>
                        </div>
                      </div>
                    ))}
                  </div>
                )}
              </div>
            </div>
          </>
        )}
      </div>

      {/* 风格自定义弹窗 */}
      {styleModalOpen && (
        <div style={{
          position:"fixed", inset:0, background:"rgba(0,0,0,0.45)",
          display:"flex", alignItems:"center", justifyContent:"center", zIndex:100,
        }} onClick={()=>!styleBusy && setStyleModalOpen(false)}>
          <div onClick={(e)=>e.stopPropagation()} style={{
            background:"var(--bg-1)", border:"1px solid var(--line-1)", borderRadius:8,
            width:420, padding:20,
          }}>
            <div className="row between" style={{marginBottom:14}}>
              <span style={{fontSize:14, fontWeight:600, color:"var(--fg-0)"}}>
                {styleModalEditing ? `编辑风格：${styleModalEditing}` : "新建风格"}
              </span>
              <button className="btn sm ghost" onClick={()=>!styleBusy && setStyleModalOpen(false)}>
                <Icon name="close" size={10}/>
              </button>
            </div>
            <div className="col gap-10">
              <div className="col gap-4">
                <span className="kpi-l">风格名</span>
                <input
                  type="text"
                  value={styleModalName}
                  onChange={(e)=>setStyleModalName(e.target.value)}
                  disabled={!!styleModalEditing}
                  placeholder="例：电子 / 民谣 / 国风 / 卡通"
                  style={{padding:"6px 8px", fontSize:12, border:"1px solid var(--line-1)", borderRadius:4, background: styleModalEditing ? "var(--bg-2)" : "var(--bg-1)"}}/>
                {styleModalEditing && (
                  <span style={{fontSize:10, color:"var(--fg-3)"}}>编辑模式下风格名只读；要改名请先删了重建。</span>
                )}
              </div>
              <div className="col gap-4">
                <span className="kpi-l">绑定人群（多选） · 决定复刻时哪种人群会用到该风格</span>
                <div className="row gap-4" style={{flexWrap:"wrap"}}>
                  {audiences.map(a=>(
                    <span key={a.slug}
                          onClick={()=>toggle(styleModalAudiences, a.slug, setStyleModalAudiences)}
                          className={`tag ${styleModalAudiences.includes(a.slug)?"cyan":""}`}
                          style={{cursor:"pointer", fontSize:10.5}}>
                      {a.name} · {a.age_range}
                    </span>
                  ))}
                </div>
              </div>
            </div>
            <div className="row gap-6" style={{marginTop:16, justifyContent:"flex-end"}}>
              <button className="btn ghost" onClick={()=>!styleBusy && setStyleModalOpen(false)}>
                取消
              </button>
              <button className="btn primary" onClick={submitStyleModal}
                      disabled={styleBusy || !styleModalName.trim()}>
                <Icon name="check" size={10}/>
                {styleBusy ? "保存中…" : (styleModalEditing ? "保存绑定" : "创建")}
              </button>
            </div>
          </div>
        </div>
      )}
    </>
  );
};

Object.assign(window, { BGMLibraryPage });
