// Media Downloader popup — multi-task manager
// All downloads run on the local Python service at http://127.0.0.1:8765.
// The service is the source of truth: tasks survive popup close/reopen.

const SERVER_URL = "http://127.0.0.1:8765";

const serverDot = document.getElementById("serverDot");
const serverText = document.getElementById("serverText");
const urlInput = document.getElementById("urlInput");
const pasteBtn = document.getElementById("pasteBtn");
const downloadBtn = document.getElementById("downloadBtn");
const messageEl = document.getElementById("message");

const tasklistEl = document.getElementById("tasklist");
const tasklistToggle = document.getElementById("tasklistToggle");
const tasklistBadge = document.getElementById("tasklistBadge");
const tasklistEmpty = document.getElementById("tasklistEmpty");
const tasklistItems = document.getElementById("tasklistItems");
const clearDoneBtn = document.getElementById("clearDoneBtn");

// Local cache of tasks shown in the UI. Key = server task id.
const tasks = new Map();
let pollTimer = null;
let serverOnline = false;

// ---------- Server checks ----------

function setServerOnline(online, reason) {
  serverOnline = online;
  serverDot.className = online ? "dot online" : "dot offline";
  if (online) {
    serverText.textContent = "本地服务已连接";
  } else {
    const detail = reason ? "：" + reason : "，请在解压目录里双击 安装开机自启.bat";
    serverText.textContent = "本地服务未启动" + detail;
  }
  updateDownloadButton();
}

function updateDownloadButton() {
  const hasUrl = urlInput.value.trim().length > 0;
  downloadBtn.disabled = !serverOnline || !hasUrl;
  downloadBtn.textContent = !serverOnline
    ? "请先启动服务"
    : hasUrl
    ? "开始下载"
    : "请先粘贴链接";
}

async function checkServer() {
  try {
    const res = await fetch(`${SERVER_URL}/api/health`, {
      signal: AbortSignal.timeout(2000),
    });
    if (res.ok) {
      setServerOnline(true);
      return true;
    }
    setServerOnline(false, `服务返回 HTTP ${res.status}`);
    return false;
  } catch (error) {
    let reason = "连接失败";
    const name = error && error.name;
    const msg = (error && error.message) || "";
    if (name === "TimeoutError" || /timeout/i.test(msg)) {
      reason = "连接超时（>2s）";
    } else if (name === "AbortError") {
      reason = "连接被中断";
    } else if (name === "TypeError" && /fetch/i.test(msg)) {
      reason = "无法连接 127.0.0.1:8765";
    } else if (msg) {
      reason = msg.slice(0, 60);
    }
    setServerOnline(false, reason);
    return false;
  }
}

// ---------- UI messages (top-level) ----------

function setMessage(text, kind) {
  messageEl.className = "msg show " + (kind || "info");
  messageEl.textContent = text || "";
  if (!text) messageEl.classList.remove("show");
}

function setMessageClickable(text, kind, onClick) {
  setMessage(text, kind);
  if (!text) return;
  messageEl.classList.add("clickable");
  messageEl.onclick = onClick;
}
messageEl.addEventListener("click", () => {
  if (messageEl.classList.contains("clickable")) {
    messageEl.classList.remove("clickable");
    messageEl.onclick = null;
  }
});

// ---------- Task rendering ----------

function statusLabel(status) {
  switch (status) {
    case "queued": return "排队中";
    case "starting": return "准备中";
    case "parsing": return "解析中";
    case "downloading": return "下载中";
    case "done": return "完成";
    case "error": return "失败";
    default: return status || "—";
  }
}

function shortTitle(t) {
  const title = (t.title || t.url || "").trim();
  if (!title) return "解析中…";
  if (title.length <= 48) return title;
  return title.slice(0, 47) + "…";
}

function fmtTimeAgo(ts) {
  if (!ts) return "";
  const d = new Date(ts * 1000);
  const pad = (n) => (n < 10 ? "0" + n : "" + n);
  return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}

function ensureTaskCard(task) {
  let entry = tasks.get(task.id);
  if (!entry) {
    entry = { id: task.id, el: null, percent: 0, status: task.status || "queued" };
    const el = document.createElement("div");
    el.className = "task";
    el.dataset.id = task.id;
    el.innerHTML = `
      <div class="task-head">
        <div class="task-title"></div>
        <div class="task-status"></div>
      </div>
      <div class="task-bar-bg"><div class="task-bar"></div></div>
      <div class="task-meta">
        <span class="task-meta-l"></span>
        <span class="task-meta-r"></span>
      </div>
      <div class="task-err" style="display:none;"></div>
    `;
    tasklistItems.prepend(el);
    entry.el = el;
    tasks.set(task.id, entry);
  }
  return entry;
}

function updateTaskCard(entry, task) {
  const status = task.status || "queued";
  const percent = Math.max(0, Math.min(100, Number(task.percent || 0)));
  entry.status = status;
  entry.percent = percent;

  const el = entry.el;
  el.classList.toggle("active", status === "queued" || status === "starting" || status === "parsing" || status === "downloading");
  el.classList.toggle("error", status === "error");
  el.classList.toggle("done", status === "done");

  el.querySelector(".task-title").textContent = shortTitle(task);
  el.querySelector(".task-status").textContent = statusLabel(status);
  el.querySelector(".task-bar").style.width = percent + "%";

  const metaL = [];
  if (task.cookie_count) metaL.push(`cookies ${task.cookie_count}`);
  const startedTs = task.started_at || task.submitted_at;
  if (startedTs) metaL.push(fmtTimeAgo(startedTs));
  el.querySelector(".task-meta-l").textContent = metaL.join(" · ");

  const metaR = [];
  metaR.push(percent + "%");
  if (task.status === "done" && task.finished_at) {
    metaR.push("✓ " + fmtTimeAgo(task.finished_at));
  }
  el.querySelector(".task-meta-r").textContent = metaR.join(" ");

  const errEl = el.querySelector(".task-err");
  if (status === "error" && task.error) {
    errEl.textContent = task.error;
    errEl.style.display = "block";
  } else {
    errEl.style.display = "none";
  }
}

function updateTasklistSummary() {
  let active = 0;
  let finished = 0;
  for (const e of tasks.values()) {
    if (e.status === "queued" || e.status === "starting" || e.status === "parsing" || e.status === "downloading") {
      active += 1;
    } else if (e.status === "done" || e.status === "error") {
      finished += 1;
    }
  }
  if (active > 0) {
    tasklistBadge.textContent = active + " 进行中";
    tasklistBadge.classList.add("has-active");
  } else if (finished > 0) {
    tasklistBadge.textContent = "完成 " + finished;
    tasklistBadge.classList.remove("has-active");
  } else {
    tasklistBadge.textContent = "0";
    tasklistBadge.classList.remove("has-active");
  }
  tasklistEmpty.style.display = tasks.size === 0 ? "block" : "none";
}

function removeTaskCard(taskId) {
  const e = tasks.get(taskId);
  if (e && e.el) e.el.remove();
  tasks.delete(taskId);
}

function clearFinished() {
  for (const [id, e] of Array.from(tasks.entries())) {
    if (e.status === "done" || e.status === "error") removeTaskCard(id);
  }
  updateTasklistSummary();
}

// ---------- Polling ----------

async function pollAll() {
  if (!serverOnline) return;
  const activeIds = [];
  for (const [id, e] of tasks.entries()) {
    if (e.status === "queued" || e.status === "starting" || e.status === "parsing" || e.status === "downloading") {
      activeIds.push(id);
    }
  }
  for (const id of activeIds) {
    try {
      const res = await fetch(`${SERVER_URL}/api/status?id=${encodeURIComponent(id)}`, { signal: AbortSignal.timeout(2000) });
      if (!res.ok) continue;
      const data = await res.json();
      const entry = tasks.get(id);
      if (!entry) continue;
      updateTaskCard(entry, data);
    } catch (e) { /* ignore per-task error */ }
  }
  updateTasklistSummary();
}

function startPolling() {
  if (pollTimer) clearInterval(pollTimer);
  pollTimer = setInterval(pollAll, 1000);
}

function stopPolling() {
  if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
}

// ---------- Load tasks from server (re-open recovery) ----------

async function loadTasksFromServer() {
  if (!serverOnline) return;
  try {
    const res = await fetch(`${SERVER_URL}/api/tasks`, { signal: AbortSignal.timeout(3000) });
    if (!res.ok) return;
    const data = await res.json();
    const list = [].concat(data.active || []).concat(data.recent || []);
    if (list.length === 0) {
      updateTasklistSummary();
      return;
    }
    for (const t of list) {
      if (!t.id) continue;
      const entry = ensureTaskCard(t);
      updateTaskCard(entry, t);
    }
    // If any active tasks, auto-expand the panel so the user sees them.
    if ((data.active || []).length > 0) {
      tasklistEl.classList.remove("collapsed");
    }
    updateTasklistSummary();
  } catch (e) { /* ignore */ }
}

// ---------- Download flow ----------

function extractUrl(text) {
  if (!text) return "";
  const m = text.match(/https?:\/\/[^\s\u4e00-\u9fa5]+/i);
  return m ? m[0] : text.trim();
}

function autoDownloadFromClipboard() {
  if (typeof navigator === "undefined" || !navigator.clipboard) return;
  if (urlInput.value.trim()) return;
  navigator.clipboard.readText()
    .then((txt) => {
      const u = extractUrl(txt);
      if (u) { urlInput.value = u; updateDownloadButton(); }
    })
    .catch(() => {});
}

async function startDownload() {
  const url = urlInput.value.trim();
  if (!url) { setMessage("请先粘贴链接。", "error"); return; }
  if (!serverOnline) {
    setMessage("本地服务没有启动，请重新加载扩展或运行 安装开机自启.bat。", "error");
    return;
  }
  downloadBtn.disabled = true;
  setMessage("");

  const tempId = "pending-" + Date.now();
  const entry = ensureTaskCard({ id: tempId, status: "queued", url });
  updateTaskCard(entry, { status: "queued", url, percent: 0 });
  tasklistEl.classList.remove("collapsed");
  updateTasklistSummary();

  try {
    const res = await fetch(`${SERVER_URL}/api/download`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ url, source_url: "popup" }),
      signal: AbortSignal.timeout(5000),
    });
    if (!res.ok) {
      const t = await res.text();
      throw new Error(t || `HTTP ${res.status}`);
    }
    const data = await res.json();
    const realId = data.task_id;
    if (!realId) throw new Error("服务未返回 task_id");
    // Re-key the pending card to the real server id.
    removeTaskCard(tempId);
    const realEntry = ensureTaskCard({ id: realId, status: "queued", url });
    updateTaskCard(realEntry, { id: realId, status: "queued", url, percent: 0 });
    updateTasklistSummary();
    setMessage(`已加入下载队列：${realId}`, "info");
    // Clear input for the next URL.
    urlInput.value = "";
    updateDownloadButton();
  } catch (e) {
    removeTaskCard(tempId);
    setMessage("提交失败：" + (e && e.message ? e.message : e), "error");
  } finally {
    downloadBtn.disabled = false;
    updateDownloadButton();
  }
}

// ---------- Wire up UI ----------

pasteBtn.addEventListener("click", async () => {
  try {
    const txt = await navigator.clipboard.readText();
    const u = extractUrl(txt);
    if (u) { urlInput.value = u; updateDownloadButton(); }
    else setMessage("剪贴板里没有可识别的链接。", "error");
  } catch (e) {
    setMessage("无法访问剪贴板：浏览器可能需要 HTTPS 或当前标签页。", "error");
  }
});

urlInput.addEventListener("input", updateDownloadButton);
downloadBtn.addEventListener("click", startDownload);
urlInput.addEventListener("keydown", (e) => { if (e.key === "Enter") startDownload(); });

tasklistToggle.addEventListener("click", () => {
  tasklistEl.classList.toggle("collapsed");
});
clearDoneBtn.addEventListener("click", clearFinished);

// ---------- Boot ----------

(async function boot() {
  updateDownloadButton();
  const online = await checkServer();
  if (online) {
    await loadTasksFromServer();
    startPolling();
  } else {
    setMessage("本地服务未启动，请在解压目录里双击 安装开机自启.bat 注册并启动。", "error");
  }
  // Best-effort clipboard fill, non-blocking.
  setTimeout(autoDownloadFromClipboard, 200);
})();
