// ———— Collect Page ————
function StatusBadge({ status, workerDead }) {
  if (workerDead && ["queued","pending","collecting","analyzing"].includes(status)) {
    return <span className="badge warning">Worker 未运行</span>;
  }
  const map = {
    "pending":        { label: "待处理",   cls: "neutral",  dot: false },
    "queued":         { label: "队列中",   cls: "neutral",  dot: true  },
    "collecting":     { label: "采集中",   cls: "info",     dot: true  },
    "analyzing":      { label: "分析中",   cls: "warning",  dot: true  },
    "done":           { label: "完成",     cls: "success",  dot: false, check: true },
    "skipped_dup":    { label: "已存在",   cls: "neutral",  dot: false },
    "skipped_repost": { label: "搬运视频", cls: "neutral",  dot: false },
    "failed":         { label: "失败",     cls: "danger",   dot: false },
  };
  const m = map[status] || { label: status, cls: "neutral", dot: false };
  return (
    <span className={`badge ${m.cls}`}>
      {m.dot   && <Icon.Spinner size={10} />}
      {m.check && <Icon.Check   size={10} />}
      {m.label}
    </span>
  );
}

// ── 格式化工具函数 ──────────────────────────────────────────────────────────

function fmtViews(n) {
  if (n === null || n === undefined) return "—";
  if (n >= 100_000_000) return (n / 100_000_000).toFixed(1) + "亿";
  if (n >= 10_000)      return (n / 10_000).toFixed(1) + "万";
  return n.toLocaleString();
}

function fmtDuration(sec) {
  if (!sec && sec !== 0) return "—";
  const m = Math.floor(sec / 60);
  const s = sec % 60;
  return `${m}:${String(s).padStart(2, "0")}`;
}

function daysSince(dateStr) {
  if (!dateStr) return null;
  return Math.floor((Date.now() - new Date(dateStr).getTime()) / 86400000);
}

// ── 关键词搜索子组件 ────────────────────────────────────────────────────────

function KeywordSearchPanel({ onJobsCreated }) {
  const [keyword,    setKeyword]    = useState("");
  const [platform,   setPlatform]   = useState("youtube");
  const [maxResults, setMaxResults] = useState(20);
  const [searching,  setSearching]  = useState(false);
  const [searchError,setSearchError]= useState("");
  const [results,    setResults]    = useState([]);   // PreviewItem[]

  // 过滤条件
  const [filterMinViews,   setFilterMinViews]   = useState("");
  // NOTE: filterMaxDays removed — YouTube flat search never returns publish dates
  //       (yt-dlp extract_flat mode omits all date fields from search entries).
  const [filterMaxMinutes, setFilterMaxMinutes]  = useState("");

  // 已选 URL 集合
  const [selected,  setSelected]   = useState(new Set());
  const [batching,  setBatching]   = useState(false);
  const [batchMsg,  setBatchMsg]   = useState("");

  // 计算过滤后的结果
  const filtered = results.filter(item => {
    if (filterMinViews) {
      const minV = Number(filterMinViews) * 10000; // 单位：万
      if (item.views === null || item.views === undefined || item.views < minV) return false;
    }
    if (filterMaxMinutes && item.duration_sec !== null && item.duration_sec !== undefined) {
      if (item.duration_sec > Number(filterMaxMinutes) * 60) return false;
    }
    return true;
  });

  const selectedFiltered = filtered.filter(r => selected.has(r.url));
  const allFilteredSelected = filtered.length > 0 && filtered.every(r => selected.has(r.url));

  const toggleSelect = (url) => {
    setSelected(prev => {
      const next = new Set(prev);
      next.has(url) ? next.delete(url) : next.add(url);
      return next;
    });
  };

  const selectAll = () => setSelected(prev => {
    const next = new Set(prev);
    filtered.forEach(r => next.add(r.url));
    return next;
  });

  const clearAll = () => setSelected(prev => {
    const next = new Set(prev);
    filtered.forEach(r => next.delete(r.url));
    return next;
  });

  const doSearch = async () => {
    if (!keyword.trim()) return;
    setSearching(true);
    setSearchError("");
    setResults([]);
    setSelected(new Set());
    setBatchMsg("");
    try {
      const data = await window.API.searchPreview(keyword.trim(), platform, maxResults);
      setResults(data);
    } catch (e) {
      setSearchError(e.message);
    } finally {
      setSearching(false);
    }
  };

  const doBatch = async () => {
    const urls = selectedFiltered.map(r => r.url);
    if (!urls.length) return;
    setBatching(true);
    setBatchMsg("");
    try {
      const res = await window.API.batchCollect(urls);
      setBatchMsg(`已提交 ${res.count} 个任务到队列`);
      setSelected(new Set());
      if (onJobsCreated) onJobsCreated();
    } catch (e) {
      setBatchMsg(`提交失败: ${e.message}`);
    } finally {
      setBatching(false);
    }
  };

  const platforms = [
    { value: "youtube", label: "YouTube" },
  ];

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
      {/* 搜索表单 */}
      <div className="field">
        <label className="field-label">关键词</label>
        <input
          className="input"
          value={keyword}
          onChange={e => setKeyword(e.target.value)}
          onKeyDown={e => e.key === "Enter" && doSearch()}
          placeholder="输入搜索关键词，回车或点击搜索"
        />
      </div>

      <div style={{ display: "flex", gap: 8, alignItems: "flex-end" }}>
        <div className="field" style={{ width: 90, marginBottom: 0 }}>
          <label className="field-label">结果数</label>
          <input
            className="input"
            type="number"
            min={1}
            value={maxResults}
            onChange={e => setMaxResults(Number(e.target.value))}
          />
        </div>
        <button
          className="btn primary"
          style={{ height: 36, whiteSpace: "nowrap" }}
          onClick={doSearch}
          disabled={searching || !keyword.trim()}
        >
          {searching ? <><Icon.Spinner /> 搜索中...</> : "搜索"}
        </button>
      </div>

      {searchError && (
        <div style={{ padding: "8px 10px", background: "#fff0f0", border: "1px solid var(--danger)", borderRadius: 6, fontSize: 12, color: "var(--danger)" }}>
          {searchError}
        </div>
      )}

      {/* 搜索结果区 */}
      {results.length > 0 && (
        <>
          {/* 过滤条件行 */}
          <div style={{
            display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center",
            padding: "8px 10px", background: "var(--bg-muted)", borderRadius: 6, fontSize: 12,
          }}>
            <span style={{ color: "var(--ink-3)", whiteSpace: "nowrap" }}>过滤：</span>
            <label style={{ display: "flex", alignItems: "center", gap: 4, color: "var(--ink-2)" }}>
              播放量 ≥
              <input
                className="input" type="number" min={0}
                value={filterMinViews}
                onChange={e => setFilterMinViews(e.target.value)}
                placeholder="万"
                style={{ width: 64, height: 26, fontSize: 12, padding: "0 6px" }}
              />
              万
            </label>
            <label style={{ display: "flex", alignItems: "center", gap: 4, color: "var(--ink-2)" }}>
              时长 ≤
              <input
                className="input" type="number" min={0}
                value={filterMaxMinutes}
                onChange={e => setFilterMaxMinutes(e.target.value)}
                placeholder="分钟"
                style={{ width: 60, height: 26, fontSize: 12, padding: "0 6px" }}
              />
              分钟
            </label>
            <span style={{ marginLeft: "auto", color: "var(--ink-3)" }}>
              {filtered.length}/{results.length} 条
            </span>
          </div>

          {/* 全选/清空行 */}
          <div style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 12 }}>
            <button className="btn ghost sm" onClick={allFilteredSelected ? clearAll : selectAll}>
              {allFilteredSelected ? "取消全选" : "全选过滤结果"}
            </button>
            {selected.size > 0 && (
              <button className="btn ghost sm" onClick={() => setSelected(new Set())}>
                清空所有选择
              </button>
            )}
            <span style={{ marginLeft: "auto", color: "var(--ink-3)" }}>
              已选 <strong>{selected.size}</strong> 个
            </span>
          </div>

          {/* 结果表格 */}
          <div style={{ overflowY: "auto", maxHeight: 340, border: "1px solid var(--line)", borderRadius: 6 }}>
            <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
              <thead>
                <tr style={{ background: "var(--bg-muted)", position: "sticky", top: 0, zIndex: 1 }}>
                  <th style={{ width: 32, padding: "6px 8px", textAlign: "center", borderBottom: "1px solid var(--line)" }}>
                    <input
                      type="checkbox"
                      checked={allFilteredSelected}
                      onChange={allFilteredSelected ? clearAll : selectAll}
                    />
                  </th>
                  <th style={{ padding: "6px 8px", textAlign: "left", borderBottom: "1px solid var(--line)", fontWeight: 500, color: "var(--ink-2)" }}>标题 / 频道</th>
                  <th style={{ padding: "6px 8px", textAlign: "right", borderBottom: "1px solid var(--line)", fontWeight: 500, color: "var(--ink-2)", whiteSpace: "nowrap" }}>播放量</th>
                  <th style={{ padding: "6px 8px", textAlign: "right", borderBottom: "1px solid var(--line)", fontWeight: 500, color: "var(--ink-2)", whiteSpace: "nowrap" }}>时长</th>
                </tr>
              </thead>
              <tbody>
                {filtered.map((item, idx) => {
                  const isSelected = selected.has(item.url);
                  return (
                    <tr
                      key={item.url || idx}
                      onClick={() => toggleSelect(item.url)}
                      style={{
                        cursor: "pointer",
                        background: isSelected ? "oklch(0.97 0.02 258)" : idx % 2 === 0 ? "transparent" : "var(--bg-muted)",
                        transition: "background 0.1s",
                      }}
                    >
                      <td style={{ padding: "6px 8px", textAlign: "center", borderBottom: "1px solid var(--line)" }}>
                        <input
                          type="checkbox"
                          checked={isSelected}
                          onChange={() => toggleSelect(item.url)}
                          onClick={e => e.stopPropagation()}
                        />
                      </td>
                      <td style={{ padding: "6px 8px", borderBottom: "1px solid var(--line)", maxWidth: 220 }}>
                        <div style={{ fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "var(--ink-1)" }}>
                          {item.title || "—"}
                        </div>
                        <div style={{ color: "var(--ink-3)", fontSize: 11, marginTop: 2 }}>
                          {item.channel || "—"}
                        </div>
                      </td>
                      <td style={{ padding: "6px 8px", textAlign: "right", borderBottom: "1px solid var(--line)", color: "var(--ink-2)", whiteSpace: "nowrap" }}>
                        {fmtViews(item.views)}
                      </td>
                      <td style={{ padding: "6px 8px", textAlign: "right", borderBottom: "1px solid var(--line)", color: "var(--ink-2)", fontFamily: "var(--font-mono)", fontSize: 11, whiteSpace: "nowrap" }}>
                        {fmtDuration(item.duration_sec)}
                      </td>
                    </tr>
                  );
                })}
                {filtered.length === 0 && (
                  <tr>
                    <td colSpan={4} style={{ padding: "24px", textAlign: "center", color: "var(--ink-3)" }}>
                      没有符合过滤条件的结果，请调整过滤条件
                    </td>
                  </tr>
                )}
              </tbody>
            </table>
          </div>

          {/* 批量采集按钮 */}
          {batchMsg && (
            <div style={{
              padding: "8px 10px", borderRadius: 6, fontSize: 12,
              background: batchMsg.startsWith("提交失败") ? "#fff0f0" : "#f0fff4",
              border: `1px solid ${batchMsg.startsWith("提交失败") ? "var(--danger)" : "var(--success)"}`,
              color: batchMsg.startsWith("提交失败") ? "var(--danger)" : "var(--success)",
            }}>
              {batchMsg}
            </div>
          )}
          <button
            className="btn primary lg"
            style={{ width: "100%" }}
            onClick={doBatch}
            disabled={batching || selectedFiltered.length === 0}
          >
            {batching
              ? <><Icon.Spinner /> 提交中...</>
              : <><Icon.Collect size={14} /> 批量采集选中的 {selectedFiltered.length} 个视频</>
            }
          </button>
        </>
      )}

      {!searching && results.length === 0 && !searchError && (
        <div style={{ padding: "28px 0", textAlign: "center", color: "var(--ink-3)", fontSize: 13 }}>
          输入关键词并点击搜索，预览结果后再批量采集
        </div>
      )}
    </div>
  );
}

// ── 主页面 ──────────────────────────────────────────────────────────────────

function CollectPage() {
  const [mode, setMode]             = useState("url");   // "url" | "keyword"
  const [url,  setUrl]              = useState("");
  const [jobs, setJobs]             = useState([]);
  const [submitting, setSubmitting] = useState(false);
  const [submitError, setSubmitError] = useState("");
  const [expanded, setExpanded]     = useState(null);

  // Worker 健康状态
  const [workerAlive,   setWorkerAlive]   = useState(true);
  const [workerCount,   setWorkerCount]   = useState(null);
  const [healthChecked, setHealthChecked] = useState(false);

  const loadJobs = async () => {
    try {
      const data = await window.API.listJobs();
      setJobs(data);
    } catch (_) {}
  };

  const checkHealth = async () => {
    try {
      const h = await window.API.health();
      setWorkerCount(h.worker_count);
      setWorkerAlive(h.redis_connected && h.worker_count > 0);
    } catch (_) {
      setWorkerCount(0);
      setWorkerAlive(false);
    } finally {
      setHealthChecked(true);
    }
  };

  useEffect(() => {
    loadJobs();
    checkHealth();
    const jobTimer    = setInterval(loadJobs,    3000);
    const healthTimer = setInterval(checkHealth, 5000);
    return () => { clearInterval(jobTimer); clearInterval(healthTimer); };
  }, []);

  const submitUrl = async () => {
    if (!url.trim()) return;
    setSubmitting(true);
    setSubmitError("");
    const urls = url.split("\n").map(s => s.trim()).filter(Boolean);
    try {
      for (const u of urls) await window.API.submitCollect(u);
      setUrl("");
      loadJobs();
    } catch (e) {
      setSubmitError(e.message);
    } finally {
      setSubmitting(false);
    }
  };

  const workerDead = healthChecked && !workerAlive;
  const activeJobs = jobs.filter(j => ["collecting","analyzing","queued","pending"].includes(j.status));
  const doneCount  = jobs.filter(j => ["done","skipped_dup","skipped_repost"].includes(j.status)).length;
  const failCount  = jobs.filter(j => j.status === "failed").length;
  const stuckCount = workerDead ? activeJobs.length : 0;

  // Tab 样式
  const tabStyle = (active) => ({
    padding: "6px 14px",
    fontSize: 13,
    fontWeight: active ? 600 : 400,
    color: active ? "var(--primary)" : "var(--ink-3)",
    borderBottom: active ? "2px solid var(--primary)" : "2px solid transparent",
    cursor: "pointer",
    background: "none",
    border: "none",
    borderBottom: active ? "2px solid var(--primary)" : "2px solid transparent",
    transition: "all 0.15s",
  });

  return (
    <>
      <div className="page-header">
        <div>
          <h1 className="page-title">采集任务</h1>
          <div className="page-subtitle">提交链接或搜索关键词，后台自动采集、分析并写入飞书。任务每 3 秒更新，Worker 状态每 5 秒检测。</div>
        </div>
        <div className="btn-row">
          {!healthChecked && (
            <span className="badge neutral"><Icon.Spinner size={10}/> 检测中</span>
          )}
          {healthChecked && workerAlive && (
            <span className="badge success"><Icon.Check size={10}/> Worker 运行中 ×{workerCount}</span>
          )}
          {healthChecked && !workerAlive && (
            <span className="badge danger">⚠ Worker 已停止</span>
          )}
        </div>
      </div>

      {/* Worker 崩溃警告横幅 */}
      {workerDead && activeJobs.length > 0 && (
        <div style={{
          marginBottom: 16, padding: "10px 16px",
          background: "oklch(0.97 0.015 55)", border: "1px solid oklch(0.75 0.08 55)",
          borderRadius: 8, fontSize: 13, color: "oklch(0.45 0.08 55)",
          display: "flex", alignItems: "center", gap: 10,
        }}>
          <span style={{ fontSize: 16 }}>⚠</span>
          <span>
            <strong>Worker 未运行</strong>，{stuckCount} 个任务卡在队列中无法执行。
            请在终端运行：<code style={{ fontFamily: "var(--font-mono)", background: "rgba(0,0,0,0.06)", padding: "1px 6px", borderRadius: 4 }}>
              python scripts/run_worker.py
            </code>
          </span>
        </div>
      )}

      <div className="collect-grid">
        {/* Left: 输入区 */}
        <div className="collect-input-card">
          <div className="card">
            <div className="card-header" style={{ paddingBottom: 0 }}>
              {/* Tab 切换 */}
              <div style={{ display: "flex", gap: 0, borderBottom: "1px solid var(--line)", width: "100%", marginBottom: 0 }}>
                <button style={tabStyle(mode === "url")}     onClick={() => setMode("url")}>
                  链接采集
                </button>
                <button style={tabStyle(mode === "keyword")} onClick={() => setMode("keyword")}>
                  关键词搜索
                </button>
              </div>
            </div>

            <div className="card-body">
              {/* ── 链接采集 Tab ── */}
              {mode === "url" && (
                <>
                  {submitError && (
                    <div style={{ marginBottom: 12, padding: "8px 10px", background: "#fff0f0", border: "1px solid var(--danger)", borderRadius: 6, fontSize: 12, color: "var(--danger)" }}>
                      {submitError}
                    </div>
                  )}
                  <div className="field">
                    <label className="field-label">视频链接</label>
                    <textarea
                      className="textarea mono"
                      value={url}
                      onChange={e => setUrl(e.target.value)}
                      placeholder={"粘贴 YouTube 链接\n支持批量，每行一条"}
                      style={{ minHeight: 100 }}
                    />
                    <div className="field-help">目前仅支持 YouTube · 最多 20 条 / 次</div>
                  </div>
                  <button
                    className="btn primary lg"
                    style={{ width: "100%" }}
                    onClick={submitUrl}
                    disabled={submitting || !url.trim()}
                  >
                    {submitting
                      ? <><Icon.Spinner /> 提交中...</>
                      : <><Icon.Collect size={14} /> 开始采集</>}
                  </button>
                </>
              )}

              {/* ── 关键词搜索 Tab ── */}
              {mode === "keyword" && (
                <KeywordSearchPanel onJobsCreated={loadJobs} />
              )}
            </div>
          </div>

          <div style={{ marginTop: 14, padding: "10px 12px", border: "1px dashed var(--line)", borderRadius: 6, fontSize: 11.5, color: "var(--ink-3)", lineHeight: 1.6 }}>
            <Icon.Info size={12}/> 采集后通过 RPA 服务拆解结构，结构化数据写入飞书 Base &amp; ChromaDB。
          </div>
        </div>

        {/* Right: 任务列表 */}
        <div className="card">
          <div className="card-header">
            <div>
              <div className="card-title">任务列表</div>
              <div className="card-subtitle" style={{ display:"flex", alignItems:"center", gap:6 }}>
                GET /api/jobs
                <span style={{ width:6, height:6, borderRadius:"50%", background: workerAlive ? "var(--success)" : workerDead ? "var(--danger)" : "var(--ink-3)", display:"inline-block" }} />
                {workerAlive ? "轮询中" : workerDead ? "Worker 已停止" : "检测中"}
              </div>
            </div>
            <div className="btn-row">
              {activeJobs.length > 0 && !workerDead && (
                <span className="badge info"><Icon.Spinner size={10}/> 运行中 {activeJobs.length}</span>
              )}
              {stuckCount > 0 && (
                <span className="badge warning">卡住 {stuckCount}</span>
              )}
              {doneCount > 0 && <span className="badge success">完成 {doneCount}</span>}
              {failCount > 0 && <span className="badge danger">失败 {failCount}</span>}
              {jobs.length === 0 && <span className="badge neutral">暂无任务</span>}
            </div>
          </div>

          <div>
            {jobs.length === 0 && (
              <div style={{ padding: "32px 24px", textAlign: "center", color: "var(--ink-3)", fontSize: 13 }}>
                提交链接后任务将显示在这里
              </div>
            )}
            {jobs.map(job => {
              const isActive = ["collecting","analyzing","queued","pending"].includes(job.status);
              const isDone   = ["done","skipped_dup","skipped_repost"].includes(job.status);
              const isFailed = job.status === "failed";
              const isStuck  = isActive && workerDead;

              return (
                <div className="job-row" key={job.job_id}>
                  <div className="job-thumb">
                    <div style={{ position:"absolute", inset:0, background:`linear-gradient(135deg, oklch(0.75 0.03 200), oklch(0.65 0.04 260))` }}/>
                    <Icon.Play size={14} style={{ position:"relative", color:"rgba(255,255,255,0.9)" }}/>
                  </div>

                  <div style={{ minWidth:0, flex:1 }}>
                    <div className="job-title" style={{ overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap" }}>
                      {job.input_url || job.job_id}
                    </div>
                    <div className="job-meta">
                      <span style={{ fontFamily:"var(--font-mono)", fontSize:10 }}>{job.job_id.slice(0,8)}</span>
                      <span>· {job.created_at ? job.created_at.slice(0,19).replace("T"," ") : ""}</span>
                    </div>
                  </div>

                  <div className="job-status-col">
                    <StatusBadge status={job.status} workerDead={workerDead} />
                  </div>

                  {isActive && !isStuck && (
                    <div className="job-progress">
                      <div className="progress">
                        <div className={`progress-bar striped ${job.status==="analyzing"?"warning":""}`}
                             style={{ width:`${job.progress || 30}%` }} />
                      </div>
                      <div className="job-progress-labels">
                        <span>
                          {job.status === "analyzing" ? "RPA 分析中..." :
                           job.status === "collecting" ? "视频下载中..." : "处理中..."}
                        </span>
                        <span>{job.progress || "…"}%</span>
                      </div>
                    </div>
                  )}

                  {isStuck && (
                    <div className="job-progress">
                      <div className="progress">
                        <div className="progress-bar striped warning" style={{ width:"100%", animationPlayState:"paused", opacity:0.4 }} />
                      </div>
                      <div className="job-progress-labels" style={{ color:"oklch(0.55 0.08 55)" }}>
                        <span>等待 Worker 重启后继续</span>
                        <span style={{ fontFamily:"var(--font-mono)", fontSize:10 }}>{job.status}</span>
                      </div>
                    </div>
                  )}

                  {isDone && (
                    <div className="job-progress">
                      <div className="progress">
                        <div className="progress-bar success" style={{ width:"100%" }} />
                      </div>
                      <div className="job-progress-labels">
                        <span>
                          {job.status === "done" ? "已写入飞书 & ChromaDB" : `跳过 (${job.status})`}
                        </span>
                        {job.video_ids && job.video_ids.length > 0 && (
                          <span style={{ fontFamily:"var(--font-mono)", fontSize:10 }}>
                            {job.video_ids[0]}
                          </span>
                        )}
                      </div>
                    </div>
                  )}

                  {isFailed && (
                    <>
                      <div className="job-progress">
                        <div className="progress">
                          <div className="progress-bar danger" style={{ width:"100%" }} />
                        </div>
                        <div className="job-progress-labels">
                          <span style={{ color:"var(--danger)" }}>任务失败</span>
                          <button
                            className="btn ghost sm"
                            style={{ padding:"0 6px" }}
                            onClick={() => setExpanded(expanded === job.job_id ? null : job.job_id)}
                          >
                            {expanded === job.job_id ? "收起" : "查看错误"}
                          </button>
                        </div>
                      </div>
                      {expanded === job.job_id && (
                        <div className="job-error">
                          {job.error_msg || "未知错误，请查看终端日志"}
                        </div>
                      )}
                    </>
                  )}
                </div>
              );
            })}
          </div>
        </div>
      </div>
    </>
  );
}
window.CollectPage = CollectPage;
