// ---------- 任务队列 (task queue) ----------
// Per-site concurrency = 1 enforced by backend. UI polls /api/jobs every
// few seconds and shows a queue summary + job list with cancel buttons.

const JOBS_STATE = { refreshTimer: null, page: 1, pageSize: 20, jobs: [] };

PAGES.jobs = async () => {
  const ws = document.getElementById("workspace");
  ws.innerHTML = `
    ${pageBanner({
      title: "任务队列",
      subtitle: "同一站点同时只跑一个目标 — 防止触发反爬风控；手动/定时抓取都排队"
    })}
    ${card({
      body: `
        <div class="filter-bar">
          <label class="text-xs text-slate-500 flex items-center gap-2"><input id="j-autorefresh" type="checkbox" checked /> 自动刷新 (3秒)</label>
          <select id="j-status" style="width:140px">
            <option value="">状态：全部</option>
            <option value="running">运行中</option>
            <option value="queued">排队中</option>
            <option value="failed">失败</option>
            <option value="cancelled">已取消</option>
            <option value="done">已完成</option>
          </select>
          <div style="flex:1"></div>
          <button class="btn-outline" onclick="loadJobs()">立即刷新</button>
        </div>
        <div id="j-summary" class="px-5 py-3 border-b border-[#E5EBF1] text-[12px]"></div>
        <div id="j-table"></div>
        <div id="j-pager"></div>`
    })}
    ${card({
      body: `
        <div class="px-5 py-3 border-b border-[#E5EBF1] flex items-center justify-between">
          <div>
            <div class="text-sm font-semibold text-slate-700">已计划（cron 触发）</div>
            <div class="text-[11px] text-slate-500">scheduler 进程注册的 cron 任务 · 按下次触发时间排序 · 不需要刷新</div>
          </div>
          <button class="btn-outline" onclick="loadScheduledJobs()">刷新计划</button>
        </div>
        <div id="j-scheduled"></div>`
    })}
  `;
  document.getElementById("j-status").onchange = () => { JOBS_STATE.page = 1; loadJobs(); };
  document.getElementById("j-autorefresh").onchange = (e) => {
    if (JOBS_STATE.refreshTimer) clearInterval(JOBS_STATE.refreshTimer);
    JOBS_STATE.refreshTimer = null;
    if (e.target.checked) JOBS_STATE.refreshTimer = setInterval(loadJobs, 3000);
  };
  if (!JOBS_STATE.refreshTimer) JOBS_STATE.refreshTimer = setInterval(loadJobs, 3000);
  loadJobs();
  loadScheduledJobs();   // populate the cron-scheduled section once on entry
};

async function loadJobs() {
  // Only run if user is actually on the jobs tab — otherwise the timer
  // keeps firing forever even after switching pages.
  if (CURRENT !== "jobs") {
    if (JOBS_STATE.refreshTimer) { clearInterval(JOBS_STATE.refreshTimer); JOBS_STATE.refreshTimer = null; }
    return;
  }
  const status = document.getElementById("j-status")?.value || "";
  const qs = new URLSearchParams({ limit: "5000" });
  if (status) qs.set("status", status);
  try {
    const r = await api("/api/jobs?" + qs);
    JOBS_STATE.jobs = r.jobs || [];
    _renderQueueSummary(r.queue_summary || []);
    _renderJobsTable();
  } catch (e) {
    document.getElementById("j-table").innerHTML = `<div class="p-6 text-sm text-red-600">加载失败：${esc(e.message)}</div>`;
  }
}

function _renderQueueSummary(summary) {
  const el = document.getElementById("j-summary");
  if (!el) return;
  if (!summary.length) {
    el.innerHTML = `<span class="text-slate-500">目前无任何站点在队列中。</span>`;
    return;
  }
  const siteLabel = (code, name) => `
    <span class="font-medium text-slate-800">${esc(name || code || "—")}</span>
    ${name && code ? `<span class="mono text-[11px] text-slate-400">${esc(code)}</span>` : ""}
  `;
  const targetLabel = (code, name) => `
    <span class="text-slate-700">${esc(name || code || "—")}</span>
    ${name && code ? `<span class="mono text-[11px] text-slate-400">${esc(code)}</span>` : ""}
  `;
  el.innerHTML = summary.map(s => {
    const pageHint = s.running_current_page
      ? `<span class="text-slate-400">第 ${esc(s.running_current_page)} 页</span>`
      : "";
    const running = s.running_job_id
      ? `<span class="pill pill-green">运行中</span> ${targetLabel(s.running_target, s.running_target_name)} <span class="text-slate-400">(${s.running_for_sec ?? 0}s)</span> ${pageHint}`
      : `<span class="pill pill-slate">空闲</span>`;
    const queued = s.queued_count > 0
      ? `<span class="pill pill-amber">排队 ${s.queued_count}</span>`
      : "";
    return `<div class="inline-flex items-center gap-2 mr-6 mb-1">${siteLabel(s.site_code, s.site_name)} ${running} ${queued}</div>`;
  }).join("");
}

function _jobSiteCell(j) {
  return `
    <div class="text-slate-800">${esc(j.site_name || j.site_code || "—")}</div>
    ${j.site_name && j.site_code ? `<div class="text-[11px] text-slate-400 mono">${esc(j.site_code)}</div>` : ""}
  `;
}

function _jobTargetCell(j) {
  const subtitle = j.channel_path || j.channel_name || "";
  return `
    <div class="text-slate-900">${esc(j.target_name || j.target_code || "—")}</div>
    <div class="text-[11px] text-slate-400 mono">${esc(j.target_code || "")}</div>
    ${subtitle ? `<div class="text-[11px] text-slate-500 truncate max-w-[360px]" title="${esc(subtitle)}">${esc(subtitle)}</div>` : ""}
  `;
}

function _renderJobsTable() {
  const { jobs, page, pageSize } = JOBS_STATE;
  const total = jobs.length;
  const slice = jobs.slice((page - 1) * pageSize, page * pageSize);
  const statusPill = j => {
    const map = { running: "blue", queued: "amber", done: "green", failed: "red", cancelled: "slate" };
    return pill(j.status, map[j.status] || "slate");
  };
  const actions = j => {
    const parts = [];
    if (j.status === "queued") {
      parts.push(`<button onclick="cancelJob('${j.job_id}')" class="text-xs px-2 py-1 rounded border border-red-200 text-red-600 hover:bg-red-50">取消</button>`);
    } else if (j.status === "running") {
      if (j.stop_requested) {
        parts.push(`<span class="text-xs text-amber-600">终止已请求</span>`);
      } else {
        parts.push(`<button onclick="cancelJob('${j.job_id}')" class="text-xs px-2 py-1 rounded border border-amber-200 text-amber-700 hover:bg-amber-50" title="标记为终止，当前批次完成后停止">请求终止</button>`);
      }
    }
    return parts.join(" ");
  };
  const progressCell = j => {
    if (j.current_page) {
      const done = j.last_completed_page ? `已完成第 ${esc(j.last_completed_page)} 页` : "尚无完成页";
      return `<div class="text-slate-700">当前第 ${esc(j.current_page)} 页</div><div class="text-[11px] text-slate-400">${done}</div>`;
    }
    if (j.last_completed_page) {
      return `<div class="text-slate-500">已完成第 ${esc(j.last_completed_page)} 页</div>`;
    }
    return "—";
  };
  document.getElementById("j-table").innerHTML = `
    <table class="w-full text-sm">
      <thead class="bg-slate-50 text-slate-500 text-xs">
        <tr>
          <th class="text-left px-4 py-2" style="width:90px">状态</th>
          <th class="text-left px-4 py-2" style="width:190px">站点</th>
          <th class="text-left px-4 py-2">目标</th>
          <th class="text-left px-4 py-2" style="width:80px">来源</th>
          <th class="text-left px-4 py-2" style="width:130px">页码</th>
          <th class="text-left px-4 py-2" style="width:110px">排队时长</th>
          <th class="text-left px-4 py-2" style="width:110px">运行时长</th>
          <th class="text-left px-4 py-2" style="width:200px">结果 / 错误</th>
          <th class="text-right px-4 py-2" style="width:150px">操作</th>
        </tr>
      </thead>
      <tbody>
        ${slice.map(j => `
          <tr class="border-t border-slate-100 hover:bg-slate-50/60">
            <td class="px-4 py-2">${statusPill(j)}</td>
            <td class="px-4 py-2 text-xs">${_jobSiteCell(j)}</td>
            <td class="px-4 py-2 text-xs">${_jobTargetCell(j)}</td>
            <td class="px-4 py-2 text-xs text-slate-600">${esc(j.source)}</td>
            <td class="px-4 py-2 text-xs">${progressCell(j)}</td>
            <td class="px-4 py-2 text-xs text-slate-500">${j.queued_for_sec != null ? j.queued_for_sec + "s" : "—"}</td>
            <td class="px-4 py-2 text-xs text-slate-500">${j.running_for_sec != null ? j.running_for_sec + "s" : "—"}</td>
            <td class="px-4 py-2 text-xs text-slate-600 truncate max-w-[260px]" title="${esc(j.error_msg || JSON.stringify(j.result || {}))}">${
              j.error_msg ? `<span class="text-red-600">${esc(j.error_msg)}</span>`
              : j.result ? esc(Object.entries(j.result).slice(0,3).map(([k,v])=>`${k}=${v}`).join(" "))
              : "—"
            }</td>
            <td class="px-4 py-2 text-right">${actions(j)}</td>
          </tr>`).join("") || `<tr><td colspan="9" class="p-10 text-center text-slate-400 text-sm">暂无任务</td></tr>`}
      </tbody>
    </table>`;
  renderPager({
    container: document.getElementById("j-pager"),
    total, page, pageSize,
    summary: `共 ${total} 个任务`,
    onPage: p => { JOBS_STATE.page = p; _renderJobsTable(); },
  });
}

async function cancelJob(jobId) {
  try {
    const r = await api(`/api/jobs/${jobId}/cancel`, { method: "POST" });
    toast(`已${r.cancelled === "queued" ? "取消排队任务" : "请求终止运行中任务"}`);
    loadJobs();
  } catch (e) { toast(e.message, "err"); }
}

// Cron-scheduled jobs — recomputed by api side from crawl_target +
// yaml column.schedule. Renders below the runtime queue so the operator
// can see "晚上凌晨自己会跑这些" without poking docker logs.
async function loadScheduledJobs() {
  const box = document.getElementById("j-scheduled");
  if (!box) return;
  box.innerHTML = `<div class="p-4 text-slate-400 text-sm">加载中…</div>`;
  try {
    const r = await api("/api/jobs/scheduled");
    const items = r.items || [];
    if (!items.length) {
      box.innerHTML = `<div class="p-4 text-slate-500 text-sm">没有已启用的 cron 任务</div>`;
      return;
    }
    const fmtNext = iso => {
      if (!iso) return "—";
      const d = new Date(iso);
      const now = new Date();
      const mins = Math.round((d - now) / 60000);
      const ago = mins < 60 ? `${mins}分钟后` : mins < 1440 ? `${Math.round(mins/60)}小时后` : `${Math.round(mins/1440)}天后`;
      return `<span class="text-slate-700">${iso.replace("T", " ").slice(0, 16)}</span> <span class="text-slate-400 text-[11px]">(${ago})</span>`;
    };
    box.innerHTML = `
      <table class="w-full text-sm">
        <thead class="bg-slate-50 text-slate-500 text-xs">
          <tr>
            <th class="text-left px-4 py-2">target_code</th>
            <th class="text-left px-4 py-2">站点</th>
            <th class="text-left px-4 py-2 mono" style="width:160px">cron</th>
            <th class="text-left px-4 py-2" style="width:80px">来源</th>
            <th class="text-left px-4 py-2" style="width:240px">下次触发</th>
            <th class="text-left px-4 py-2" style="width:160px">最后抓取</th>
          </tr>
        </thead>
        <tbody>
          ${items.map(it => `
            <tr class="border-t border-slate-100 hover:bg-slate-50/60">
              <td class="px-4 py-2"><div class="text-slate-900">${esc(it.target_name || it.target_code)}</div><div class="text-[11px] text-slate-400 mono">${esc(it.target_code)}</div></td>
              <td class="px-4 py-2 text-xs"><div class="text-slate-700">${esc(it.site_code)}</div><div class="text-slate-500">${esc(it.site_name || "—")}</div></td>
              <td class="px-4 py-2 mono text-xs text-slate-700" title="raw=${esc(it.cron_expr_raw)}">${esc(it.cron_expr_effective || it.cron_expr_raw || "")}</td>
              <td class="px-4 py-2"><span class="pill pill-${it.cron_source === "target_override" ? "blue" : it.cron_source === "yaml_column" ? "green" : "slate"}">${esc(it.cron_source || "—")}</span></td>
              <td class="px-4 py-2 text-xs">${fmtNext(it.next_fire_at)}</td>
              <td class="px-4 py-2 text-xs mono text-slate-500">${it.last_crawled_at ? esc(it.last_crawled_at.replace("T"," ").slice(0,16)) : "—"}</td>
            </tr>`).join("")}
        </tbody>
      </table>
      <div class="px-4 py-2 text-[11px] text-slate-500">共 ${items.length} 个 cron 任务 · 按下次触发时间排序</div>`;
  } catch (e) {
    box.innerHTML = `<div class="p-4 text-sm text-red-600">加载失败：${esc(e.message)}</div>`;
  }
}
