const $ = (selector) => document.querySelector(selector);
const API_BASE = "/api/cms";
const MAX_UPLOAD_SIZE = 20 * 1024 * 1024;
const DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
let categories = [];
let editingSlug = null;
let editingStatus = "new-unsaved";
let dirty = false;
let previewTimer = null;
let previewRequest = 0;
let captchaToken = "";
let captchaObjectUrl = "";
let captchaRequest = 0;
let currentUser = null;
let consultationTeachers = [];
let staffAccounts = [];
let activeConsultationUser = null;
const consultationStatusLabels = { unprocessed: "未处理", added: "已添加", not_added: "未添加" };

async function api(path, options = {}) {
  const endpoint = path.startsWith("/") ? path : `/${path}`;
  const response = await fetch(`${API_BASE}${endpoint}`, {
    headers: { "Content-Type": "application/json" },
    ...options,
    body: options.body ? JSON.stringify(options.body) : undefined,
  });
  let data = {};
  try { data = await response.json(); } catch {}
  if (!response.ok) throw new Error(data.error || `请求失败（${response.status}）`);
  return data;
}

const show = (selector) => $(selector).classList.remove("hidden");
const hide = (selector) => $(selector).classList.add("hidden");
async function refreshCaptcha() {
  const request = ++captchaRequest;
  captchaToken = "";
  $("#captcha").value = "";
  $("#captcha-image").removeAttribute("src");
  try {
    const response = await fetch(`${API_BASE}/captcha?t=${Date.now()}`, { cache: "no-store" });
    if (!response.ok) throw new Error(`验证码加载失败（${response.status}）`);
    const token = response.headers.get("X-Captcha-Token") || "";
    const blob = await response.blob();
    if (request !== captchaRequest) return;
    if (!token) throw new Error("验证码令牌缺失");
    if (captchaObjectUrl) URL.revokeObjectURL(captchaObjectUrl);
    captchaObjectUrl = URL.createObjectURL(blob);
    captchaToken = token;
    $("#captcha-image").src = captchaObjectUrl;
  } catch (error) {
    if (request === captchaRequest) $("#login-error").textContent = error.message;
  }
}

function showLogin() {
  show("#login");
  hide("#app");
  refreshCaptcha();
  $("#username").value ||= "admin";
  $("#username").focus();
}

async function boot() {
  const session = await api("/session").catch(() => ({ authed: false }));
  if (session.authed) {
    hide("#login"); show("#app");
    await enterApp(session.user);
  }
  else showLogin();
}

$("#captcha-image").addEventListener("click", refreshCaptcha);

$("#login-form").addEventListener("submit", async (event) => {
  event.preventDefault();
  $("#login-error").textContent = "";
  try {
    const result = await api("/session", { method: "POST", body: { username: $("#username").value, password: $("#password").value, captcha: $("#captcha").value, captchaToken } });
    $("#password").value = "";
    $("#captcha").value = "";
    hide("#login"); show("#app");
    await enterApp(result.user);
  } catch (error) {
    $("#login-error").textContent = error.message;
    refreshCaptcha();
  }
});

$("#logout").addEventListener("click", async () => { await api("/session", { method: "DELETE" }); location.reload(); });

async function enterApp(user) {
  currentUser = user;
  const admin = user?.role === "admin";
  const customerService = user?.role === "customer_service";
  const operator = user?.role === "operator";
  document.body.classList.toggle("teacher-session", customerService);
  document.body.classList.toggle("operator-session", operator);
  document.querySelectorAll(".admin-only").forEach((element) => element.classList.toggle("hidden", !admin));
  document.querySelectorAll(".consultation-role").forEach((element) => element.classList.toggle("hidden", operator));
  document.querySelectorAll(".content-role").forEach((element) => element.classList.toggle("hidden", customerService));
  $("#account-role").textContent = admin ? "管理员账号" : operator ? "运营账号" : "客服账号";
  $("#account-name").textContent = user?.name || "";
  await showAdminView(operator ? "articles" : "consultations");
  if (admin || operator) {
    await loadCategories();
    loadArticles();
  }
}

const adminViews = {
  consultations: ["#consultations-view", "用户管理", "查看和跟进官网用户线索"],
  teachers: ["#teachers-view", "账号管理", "管理客服和运营账号权限"],
  articles: ["#list-view", "学习资讯", "管理官网文章内容"],
  settings: ["#settings-view", "账号设置", "管理管理员账号的登录安全"],
};

async function showAdminView(name) {
  Object.values(adminViews).forEach(([selector]) => hide(selector));
  hide("#edit-view");
  const allowedName = currentUser?.role === "operator" ? "articles" : name;
  const view = adminViews[allowedName] || adminViews.consultations;
  show(view[0]);
  $("#page-title").textContent = view[1];
  $("#page-subtitle").textContent = view[2];
  document.querySelectorAll("[data-admin-nav]").forEach((button) => button.classList.toggle("active", button.dataset.adminNav === allowedName));
  if (allowedName === "consultations") await loadConsultations();
  if (allowedName === "teachers") await loadTeachers();
  if (allowedName === "articles") await loadArticles();
}

document.querySelectorAll("[data-admin-nav]").forEach((button) => button.addEventListener("click", () => { void showAdminView(button.dataset.adminNav); }));

const formatTime = (value) => {
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return "-";
  const pad = (part) => String(part).padStart(2, "0");
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
};

const notePreview = (value, maxLength = 24) => {
  const text = String(value || "").replace(/\s+/g, " ").trim();
  if (!text) return "-";
  return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
};

function filterableTableHead(rowClass, columns, tableRows) {
  const head = document.createElement("div");
  head.className = `data-row data-head ${rowClass}`;
  const filters = [];
  columns.forEach((column, index) => {
    const cell = document.createElement("label");
    cell.className = "column-filter";
    const label = document.createElement("span");
    label.textContent = column;
    const select = document.createElement("select");
    select.setAttribute("aria-label", `${column}筛选`);
    select.title = `筛选${column}`;
    const all = document.createElement("option");
    all.value = "";
    all.textContent = "全部";
    select.append(all);
    [...new Set(tableRows.map((item) => item.values[index]))]
      .sort((left, right) => String(left).localeCompare(String(right), "zh-CN", { numeric: true }))
      .forEach((value) => {
        const option = document.createElement("option");
        option.value = value;
        option.textContent = value;
        select.append(option);
      });
    filters.push(select);
    cell.append(label, select);
    head.append(cell);
  });
  const operation = document.createElement("span");
  operation.textContent = "操作";
  head.append(operation);
  return { head, filters };
}

function setupTablePagination(list, tableRows, filters) {
  list.parentElement.querySelector(".table-pagination")?.remove();
  const footer = document.createElement("div");
  footer.className = "table-pagination";
  let page = 1;
  let pageSize = 20;
  const render = (resetPage = false) => {
    if (resetPage) page = 1;
    const filtered = tableRows.filter((item) => filters.every((filter, index) => !filter.value || item.values[index] === filter.value));
    const pageCount = Math.max(1, Math.ceil(filtered.length / pageSize));
    page = Math.min(page, pageCount);
    const start = (page - 1) * pageSize;
    tableRows.forEach((item) => item.element.classList.toggle("hidden", !filtered.slice(start, start + pageSize).includes(item)));
    footer.innerHTML = "";
    const summary = document.createElement("span");
    summary.textContent = `共 ${filtered.length} 条`;
    const controls = document.createElement("div");
    controls.className = "table-pagination__controls";
    const sizeLabel = document.createElement("label");
    sizeLabel.textContent = "每页";
    const sizeSelect = document.createElement("select");
    [20, 50, 100].forEach((size) => {
      const option = document.createElement("option");
      option.value = String(size);
      option.textContent = String(size);
      option.selected = size === pageSize;
      sizeSelect.append(option);
    });
    sizeSelect.addEventListener("change", () => { pageSize = Number(sizeSelect.value); render(true); });
    sizeLabel.append(sizeSelect, document.createTextNode("条"));
    const previous = listAction("上一页", "ghost small", () => { if (page > 1) { page -= 1; render(); } });
    const pageLabel = document.createElement("span");
    pageLabel.textContent = `${page} / ${pageCount}`;
    const next = listAction("下一页", "ghost small", () => { if (page < pageCount) { page += 1; render(); } });
    previous.disabled = page <= 1;
    next.disabled = page >= pageCount;
    controls.append(sizeLabel, previous, pageLabel, next);
    footer.append(summary, controls);
  };
  filters.forEach((filter) => filter.addEventListener("change", () => render(true)));
  list.parentElement.append(footer);
  render();
}

function updateConsultationMetrics(records) {
  $("#consultation-count").textContent = records.length;
  $("#metric-total").textContent = records.length;
  [["unprocessed", "metric-unprocessed"], ["added", "metric-added"], ["not_added", "metric-not-added"]].forEach(([status, id]) => {
    $(`#${id}`).textContent = records.filter((item) => item.status === status).length;
  });
}

async function loadConsultations() {
  const result = await api("/consultations").catch((error) => { alert(error.message); return { consultations: [] }; });
  const records = result.consultations || [];
  if (currentUser?.role === "admin" && !consultationTeachers.length) {
    const teacherResult = await api("/consultation-teachers").catch(() => ({ teachers: [] }));
    staffAccounts = teacherResult.teachers || [];
    consultationTeachers = staffAccounts.filter((teacher) => teacher.role === "customer_service");
  }
  updateConsultationMetrics(records);
  const list = $("#consultation-list");
  list.innerHTML = "";
  list.parentElement.querySelector(".table-pagination")?.remove();
  if (!records.length) {
    list.innerHTML = '<div class="empty">暂时没有用户线索。官网用户提交手机号后会显示在这里。</div>';
    return;
  }
  const tableRows = [];
  records.forEach((record) => {
    const row = document.createElement("article");
    row.className = "data-row consultation-row";
    row.innerHTML = '<strong class="sequence"></strong><span class="user-name"></span><span class="phone-text"></span><span class="location"></span><span class="teacher-name"></span><span class="consultation-status"></span><span class="unadded-reason"></span><span class="note-summary"></span><span class="call-count"></span><time class="created"></time><div class="row-actions"></div>';
    const values = [
      String(record.sequence || "-"), record.name || "未填写", record.phone,
      record.location || "-", record.teacherName || "未分配",
      consultationStatusLabels[record.status] || "未处理", record.unaddedReason || "-",
      notePreview(record.note), String(record.callCount ?? 0), formatTime(record.createdAt),
    ];
    row.querySelector(".sequence").textContent = values[0];
    row.querySelector(".user-name").textContent = values[1];
    row.querySelector(".phone-text").textContent = record.phone;
    row.querySelector(".location").textContent = values[3];
    row.querySelector(".teacher-name").textContent = values[4];
    const status = row.querySelector(".consultation-status");
    status.textContent = values[5];
    status.classList.add(`consultation-status--${record.status}`);
    row.querySelector(".unadded-reason").textContent = values[6];
    row.querySelector(".note-summary").textContent = values[7];
    row.querySelector(".note-summary").title = record.note || "";
    row.querySelector(".call-count").textContent = values[8];
    row.querySelector(".created").textContent = values[9];
    const actions = row.querySelector(".row-actions");
    actions.append(listAction("编辑", "text-action", async () => { if (currentUser?.role === "admin" && !consultationTeachers.length) await loadTeachers(); openConsultationUserModal(record); }));
    if (currentUser?.role === "admin") actions.append(listAction("删除", "text-action danger", async () => {
      if (!confirm(`确定删除用户“${record.phone}”吗？`)) return;
      try { await api(`/consultations/${record.id}`, { method: "DELETE" }); await loadConsultations(); } catch (error) { alert(error.message); }
    }));
    tableRows.push({ element: row, values });
  });
  const tableHead = filterableTableHead("consultation-row", ["ID", "姓名", "手机号", "归属地", "分配客服", "状态", "未添加原因", "备注", "打电话次数", "提交时间"], tableRows);
  list.append(tableHead.head);
  tableRows.forEach((item) => list.append(item.element));
  setupTablePagination(list, tableRows, tableHead.filters);
}

$("#refresh-consultations").addEventListener("click", loadConsultations);

function closeConsultationUserModal() {
  hide("#consultation-user-modal");
  $("#consultation-user-form").reset();
  $("#consultation-user-error").textContent = "";
  activeConsultationUser = null;
}

function openConsultationUserModal(record = null) {
  if (!consultationTeachers.length && currentUser?.role === "admin") { alert("请先在账号管理中添加客服"); return; }
  activeConsultationUser = record;
  const teacherSelect = $("#consultation-user-teacher");
  teacherSelect.innerHTML = consultationTeachers.length
    ? consultationTeachers.map((teacher) => `<option value="${teacher.id}">${teacher.name}（${teacher.username}）</option>`).join("")
    : `<option value="${record?.teacherId || currentUser?.id || ""}">${record?.teacherName || currentUser?.name || "当前客服"}</option>`;
  $("#consultation-user-form").reset();
  $("#consultation-user-title").textContent = record ? "编辑用户" : "录入用户";
  $("#consultation-user-description").textContent = record ? `用户 ID ${record.sequence || "-"} · 提交于 ${formatTime(record.createdAt)}` : "一次完成用户信息、咨询状态和跟进备注录入";
  $("#consultation-user-id").value = record?.id || "";
  $("#consultation-user-name").value = record?.name || "";
  $("#consultation-user-phone").value = record?.phone || "";
  teacherSelect.value = record?.teacherId || consultationTeachers[0]?.id || "";
  teacherSelect.disabled = currentUser?.role !== "admin";
  $("#consultation-user-status").value = record?.status || "unprocessed";
  $("#consultation-user-reason").value = record?.unaddedReason || "";
  $("#consultation-user-calls").value = record?.callCount ?? 0;
  syncCallCountButtons();
  $("#consultation-user-note").value = record?.note || "";
  $("#consultation-user-note-count").textContent = $("#consultation-user-note").value.length;
  $("#consultation-user-error").textContent = "";
  show("#consultation-user-modal");
  $("#consultation-user-name").focus();
}

$("#new-consultation").addEventListener("click", async () => {
  if (currentUser?.role === "admin" && !consultationTeachers.length) await loadTeachers();
  openConsultationUserModal();
});
$("#close-consultation-user-modal").addEventListener("click", closeConsultationUserModal);
$("#consultation-user-note").addEventListener("input", () => { $("#consultation-user-note-count").textContent = $("#consultation-user-note").value.length; });
function syncCallCountButtons() {
  const value = Math.min(9999, Math.max(0, Number($("#consultation-user-calls").value) || 0));
  $("#decrease-consultation-calls").disabled = value <= 0;
  $("#increase-consultation-calls").disabled = value >= 9999;
}
function changeCallCount(delta) {
  const input = $("#consultation-user-calls");
  input.value = Math.min(9999, Math.max(0, (Number(input.value) || 0) + delta));
  syncCallCountButtons();
}
$("#decrease-consultation-calls").addEventListener("click", () => changeCallCount(-1));
$("#increase-consultation-calls").addEventListener("click", () => changeCallCount(1));
$("#consultation-user-calls").addEventListener("input", syncCallCountButtons);
$("#consultation-user-form").addEventListener("submit", async (event) => {
  event.preventDefault();
  const id = $("#consultation-user-id").value;
  const body = {
    name: $("#consultation-user-name").value, phone: $("#consultation-user-phone").value,
    teacherId: $("#consultation-user-teacher").value,
    status: $("#consultation-user-status").value, unaddedReason: $("#consultation-user-reason").value,
    callCount: Number($("#consultation-user-calls").value), note: $("#consultation-user-note").value,
  };
  const submit = event.submitter;
  submit.disabled = true;
  $("#consultation-user-error").textContent = "";
  try {
    await api(id ? `/consultations/${id}` : "/consultations", { method: id ? "PATCH" : "POST", body });
    closeConsultationUserModal();
    await loadConsultations();
  } catch (error) { $("#consultation-user-error").textContent = error.message; }
  finally { submit.disabled = false; }
});

async function loadTeachers() {
  const result = await api("/consultation-teachers").catch((error) => { alert(error.message); return { teachers: [] }; });
  staffAccounts = result.teachers || [];
  consultationTeachers = staffAccounts.filter((teacher) => teacher.role === "customer_service");
  const list = $("#teacher-list");
  list.innerHTML = "";
  list.parentElement.querySelector(".table-pagination")?.remove();
  if (!staffAccounts.length) {
    list.innerHTML = '<div class="empty">还没有后台账号。添加客服或运营账号后会显示在这里。</div>';
    return;
  }
  const tableRows = [];
  staffAccounts.forEach((teacher) => {
    const row = document.createElement("article");
    row.className = "data-row teacher-row";
    row.innerHTML = '<strong class="staff-name"></strong><span class="username"></span><span class="staff-role"></span><span class="staff-wechat"></span><span class="sort"></span><span class="assigned"></span><span class="teacher-state"></span><div class="row-actions"></div>';
    const values = [
      teacher.name, teacher.username, teacher.role === "operator" ? "运营" : "客服",
      teacher.role === "operator" ? "-" : teacher.wechat || "-",
      teacher.role === "operator" ? "-" : String(teacher.sort),
      teacher.role === "operator" ? "-" : `${teacher.assignedCount} 次`,
      teacher.enabled ? "启用" : "停用",
    ];
    row.querySelector(".staff-name").textContent = values[0];
    row.querySelector(".username").textContent = values[1];
    row.querySelector(".staff-role").textContent = values[2];
    row.querySelector(".staff-wechat").textContent = values[3];
    row.querySelector(".sort").textContent = values[4];
    row.querySelector(".assigned").textContent = values[5];
    const state = row.querySelector(".teacher-state");
    state.textContent = values[6];
    state.className = `teacher-state ${teacher.enabled ? "enabled" : "disabled"}`;
    row.querySelector(".row-actions").append(
      listAction("编辑", "text-action", () => openTeacherModal(teacher)),
      listAction("删除", "text-action danger", async () => {
        if (!confirm(`确定删除账号“${teacher.name}”吗？历史数据仍会保留。`)) return;
        try { await api(`/consultation-teachers/${teacher.id}`, { method: "DELETE" }); await loadTeachers(); }
        catch (error) { alert(error.message); }
      }),
    );
    tableRows.push({ element: row, values });
  });
  const tableHead = filterableTableHead("teacher-row", ["姓名", "登录账号", "角色", "微信号", "轮询顺序", "累计分配", "状态"], tableRows);
  list.append(tableHead.head);
  tableRows.forEach((item) => list.append(item.element));
  setupTablePagination(list, tableRows, tableHead.filters);
}

function closeTeacherModal() { hide("#teacher-modal"); $("#teacher-form").reset(); $("#teacher-error").textContent = ""; }

function openTeacherModal(teacher = null) {
  $("#teacher-form").reset();
  $("#teacher-error").textContent = "";
  $("#teacher-id").value = teacher?.id || "";
  $("#teacher-modal-title").textContent = teacher ? "编辑后台账号" : "添加后台账号";
  $("#teacher-role").value = teacher?.role || "customer_service";
  $("#teacher-name").value = teacher?.name || "";
  $("#teacher-username").value = teacher?.username || "";
  $("#teacher-wechat").value = teacher?.wechat || "";
  $("#teacher-sort").value = teacher?.sort || Math.max(1, ...consultationTeachers.map((item) => item.sort + 1));
  $("#teacher-enabled").checked = teacher?.enabled ?? true;
  $("#teacher-password").required = !teacher;
  $("#teacher-password-hint").textContent = teacher ? "留空则不修改密码" : "至少 6 个字符";
  $("#teacher-qr-url").value = teacher?.qrCodeUrl || "";
  $("#teacher-qr-preview").src = teacher?.qrCodeUrl || "";
  $("#teacher-qr-preview").classList.toggle("hidden", !teacher?.qrCodeUrl);
  $("#teacher-qr-label").textContent = teacher ? "点击更换，或直接粘贴" : "点击选择图片，或直接粘贴";
  syncStaffRoleFields();
  show("#teacher-modal");
  $("#teacher-name").focus();
}

$("#new-teacher").addEventListener("click", () => openTeacherModal());
function syncStaffRoleFields() {
  const customerService = $("#teacher-role").value === "customer_service";
  document.querySelectorAll(".customer-service-field").forEach((field) => field.classList.toggle("hidden", !customerService));
  $("#teacher-sort").required = customerService;
  $("#teacher-enabled-label").textContent = customerService ? "启用并参与轮询分配" : "启用该运营账号";
  $("#teacher-modal-description").textContent = customerService ? "客服负责查看并处理分配给自己的用户咨询" : "运营负责学习资讯的创建、编辑与发布";
}
$("#teacher-role").addEventListener("change", syncStaffRoleFields);
$("#cancel-teacher").addEventListener("click", closeTeacherModal);
$("#teacher-qr-button").addEventListener("click", () => $("#teacher-qr-file").click());
async function uploadTeacherQr(file) {
  if (!file?.type?.startsWith("image/")) return $("#teacher-error").textContent = "请选择或粘贴图片文件";
  if (file.size > MAX_UPLOAD_SIZE) return $("#teacher-error").textContent = "二维码图片不能超过 20MB";
  $("#teacher-error").textContent = "";
  $("#teacher-qr-label").textContent = "上传中…";
  try {
    const dataUrl = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(file); });
    const result = await api("/uploads", { method: "POST", body: { dataUrl } });
    $("#teacher-qr-url").value = result.url;
    $("#teacher-qr-preview").src = result.url;
    show("#teacher-qr-preview");
    $("#teacher-qr-label").textContent = "点击更换，或直接粘贴";
  } catch (error) { $("#teacher-error").textContent = error.message; $("#teacher-qr-label").textContent = "点击选择图片，或直接粘贴"; }
}
$("#teacher-qr-file").addEventListener("change", async (event) => {
  const file = event.target.files?.[0];
  event.target.value = "";
  if (file) await uploadTeacherQr(file);
});
document.addEventListener("paste", async (event) => {
  if ($("#teacher-modal").classList.contains("hidden") || $("#teacher-role").value !== "customer_service") return;
  const imageItem = [...(event.clipboardData?.items || [])].find((item) => item.kind === "file" && item.type.startsWith("image/"));
  const file = imageItem?.getAsFile();
  if (!file) return;
  event.preventDefault();
  await uploadTeacherQr(file);
});

$("#teacher-form").addEventListener("submit", async (event) => {
  event.preventDefault();
  const id = $("#teacher-id").value;
  const body = { role: $("#teacher-role").value, name: $("#teacher-name").value, username: $("#teacher-username").value, wechat: $("#teacher-wechat").value, sort: Number($("#teacher-sort").value || 1), password: $("#teacher-password").value, qrCodeUrl: $("#teacher-qr-url").value, enabled: $("#teacher-enabled").checked };
  const submit = event.submitter || $("#save-teacher");
  submit.disabled = true;
  $("#teacher-error").textContent = "";
  try {
    await api(id ? `/consultation-teachers/${id}` : "/consultation-teachers", { method: id ? "PUT" : "POST", body });
    closeTeacherModal();
    await loadTeachers();
  } catch (error) { $("#teacher-error").textContent = error.message; }
  finally { submit.disabled = false; }
});

function closePasswordModal() {
  hide("#password-modal");
  $("#password-form").reset();
  $("#password-error").textContent = "";
}

$("#change-password").addEventListener("click", () => {
  $("#password-form").reset();
  $("#password-error").textContent = "";
  show("#password-modal");
  $("#current-password").focus();
});

$("#close-password-modal").addEventListener("click", closePasswordModal);
$("#cancel-password").addEventListener("click", closePasswordModal);

$("#password-form").addEventListener("submit", async (event) => {
  event.preventDefault();
  const currentPassword = $("#current-password").value;
  const newPassword = $("#new-password").value;
  const confirmPassword = $("#confirm-password").value;
  const submit = event.submitter;
  $("#password-error").textContent = "";
  if (newPassword !== confirmPassword) {
    $("#password-error").textContent = "两次输入的新密码不一致";
    return;
  }
  if (submit) submit.disabled = true;
  try {
    await api("/account/password", { method: "PUT", body: { currentPassword, newPassword } });
    closePasswordModal();
    alert("密码修改成功，请重新登录后台");
    showLogin();
  } catch (error) {
    $("#password-error").textContent = error.message;
  } finally {
    if (submit) submit.disabled = false;
  }
});

const statusMap = {
  "new-unsaved": ["尚未保存", "new-draft", "填写完成后先保存草稿，线上网站不会发生变化。"],
  "new-draft": ["新建未发布", "new-draft", "这篇文章目前只有草稿版本，发布后才会出现在官网。"],
  "edited-draft": ["修改未发布", "edited-draft", "草稿已经保存，官网仍在展示上一次发布的版本。"],
  live: ["已上线", "live", "当前编辑内容与官网展示的版本一致。"],
};

function applyStatus(element, status) {
  const [label, className] = statusMap[status] || statusMap["new-draft"];
  element.className = `status ${className}`;
  element.textContent = label;
}

async function loadArticles() {
  const articles = await api("/articles").catch((error) => { alert(error.message); return []; });
  const list = $("#article-list");
  list.innerHTML = "";
  const pending = articles.filter((item) => item.publishStatus !== "live").length;
  $("#pending-badge").textContent = `${pending} 篇草稿待发布`;
  $("#pending-badge").classList.toggle("hidden", pending === 0);
  $("#sidebar-pending").textContent = pending;
  $("#sidebar-pending").classList.toggle("hidden", pending === 0);
  if (!articles.length) { list.innerHTML = '<div class="empty">还没有文章，点击“新建文章”开始。</div>'; return; }
  for (const article of articles) {
    const card = document.createElement("article");
    card.className = "article-row";
    card.innerHTML = '<div class="article-summary"><h2></h2><p><span class="category"></span><span class="updated"></span><span class="status"></span></p></div><div class="row-actions"></div>';
    card.querySelector("h2").textContent = article.title;
    card.querySelector(".category").textContent = article.category;
    card.querySelector(".updated").textContent = `更新于 ${article.updated}`;
    applyStatus(card.querySelector(".status"), article.publishStatus);
    const actions = card.querySelector(".row-actions");
    const edit = listAction("编辑", "ghost small", () => openEditor(article.slug));
    const publish = listAction("发布", "primary small", () => publishFromList(article));
    publish.disabled = article.publishStatus === "live";
    publish.title = publish.disabled ? "文章当前已上线" : "发布到官网";
    actions.append(edit, publish, listMoreActions(article));
    list.append(card);
  }
}

function listAction(label, className, action) {
  const button = document.createElement("button");
  button.type = "button";
  button.className = className;
  button.textContent = label;
  button.onclick = action;
  return button;
}

function listMenuAction(label, className, action) {
  return listAction(label, `menu-action ${className}`.trim(), async (event) => {
    event.currentTarget.closest("details").open = false;
    await action();
  });
}

function listMoreActions(article) {
  const details = document.createElement("details");
  details.className = "more-actions row-more-actions";
  const summary = document.createElement("summary");
  summary.textContent = "更多";
  summary.setAttribute("aria-label", `${article.title}的更多操作`);
  const menu = document.createElement("div");
  menu.className = "action-menu";
  if (article.publishStatus === "edited-draft") {
    menu.append(listMenuAction("放弃未发布修改", "", () => discardFromList(article)));
  }
  if (["live", "edited-draft"].includes(article.publishStatus)) {
    menu.append(listMenuAction("下架文章", "danger", () => unpublishFromList(article)));
  }
  menu.append(listMenuAction("删除文章", "danger", () => deleteFromList(article)));
  details.append(summary, menu);
  details.addEventListener("toggle", () => {
    if (!details.open) return;
    document.querySelectorAll(".row-more-actions[open]").forEach((item) => {
      if (item !== details) item.open = false;
    });
  });
  return details;
}

async function publishFromList(article) {
  try {
    await runContentAction(`/articles/${article.slug}/publish`);
    await loadArticles();
  } catch {}
}

async function discardFromList(article) {
  if (!confirm(`确定放弃《${article.title}》未发布的修改，恢复为当前线上版本吗？`)) return;
  try {
    await api(`/articles/${article.slug}/draft`, { method: "DELETE" });
    await loadArticles();
  } catch (error) { alert(error.message); }
}

async function unpublishFromList(article) {
  const detail = article.publishStatus === "edited-draft" ? "未发布的修改会继续保留为草稿。" : "文章内容会继续保留为草稿。";
  if (!confirm(`确定将《${article.title}》从官网下架吗？${detail}`)) return;
  try {
    await runContentAction(`/articles/${article.slug}/unpublish`);
    await loadArticles();
  } catch {}
}

async function deleteFromList(article) {
  const prompt = article.publishStatus === "new-draft"
    ? `确定彻底删除草稿《${article.title}》吗？删除后无法恢复。`
    : `确定彻底删除《${article.title}》吗？线上版本和草稿都会删除，且无法恢复。`;
  if (!confirm(prompt)) return;
  try {
    await runContentAction(`/articles/${article.slug}`, { method: "DELETE" });
    await loadArticles();
  } catch {}
}

function fillCategories(selected) {
  const current = selected || $("#category").value || categories[0] || "";
  const options = $("#category-options");
  options.innerHTML = "";
  categories.forEach((name) => {
    const option = document.createElement("button");
    option.type = "button";
    option.className = "select-option";
    option.dataset.value = name;
    option.setAttribute("role", "option");
    option.setAttribute("aria-selected", String(name === current));
    option.innerHTML = '<span></span><span class="option-check" aria-hidden="true">✓</span>';
    option.querySelector("span").textContent = name;
    option.addEventListener("click", () => {
      selectCategory(name, true);
      closeCategoryMenu();
    });
    options.append(option);
  });
  selectCategory(categories.includes(current) ? current : categories[0] || "", false);
}

function selectCategory(name, markDirty) {
  $("#category").value = name;
  $("#category-value").textContent = name || "请选择分类";
  $("#category-options").querySelectorAll(".select-option").forEach((option) => {
    option.setAttribute("aria-selected", String(option.dataset.value === name));
  });
  if (markDirty) dirty = true;
}

function closeCategoryMenu() {
  $("#category-menu").classList.add("hidden");
  $("#category-trigger").setAttribute("aria-expanded", "false");
  $("#category-select").classList.remove("open");
}

function toggleCategoryMenu() {
  const opening = $("#category-menu").classList.contains("hidden");
  if (!opening) return closeCategoryMenu();
  $("#category-menu").classList.remove("hidden");
  $("#category-trigger").setAttribute("aria-expanded", "true");
  $("#category-select").classList.add("open");
  $("#category-error").textContent = "";
}

$("#category-trigger").addEventListener("click", (event) => { event.stopPropagation(); toggleCategoryMenu(); });
$("#category-menu").addEventListener("click", (event) => event.stopPropagation());
document.addEventListener("click", closeCategoryMenu);
document.addEventListener("click", (event) => {
  document.querySelectorAll(".row-more-actions[open]").forEach((details) => {
    if (!details.contains(event.target)) details.open = false;
  });
});
document.addEventListener("keydown", (event) => { if (event.key === "Escape") closeCategoryMenu(); });

async function createCategory() {
  const input = $("#new-category");
  const name = input.value.trim();
  $("#category-error").textContent = "";
  try {
    await runContentAction("/categories", { body: { name } });
    await loadCategories();
    input.value = "";
    fillCategories(name);
    dirty = true;
    closeCategoryMenu();
    if (!$("#category-modal").classList.contains("hidden")) await loadCategoryManager();
  } catch (error) { $("#category-error").textContent = error.message; }
}

$("#add-category").addEventListener("click", createCategory);
$("#new-category").addEventListener("keydown", (event) => {
  if (event.key !== "Enter") return;
  event.preventDefault();
  createCategory();
});

async function loadCategories() {
  const result = await api("/categories");
  categories = result.categories;
  fillCategories($("#category").value);
  return result;
}

async function loadCategoryManager() {
  const result = await loadCategories();
  renderCategoryManager(result.stats || []);
}

function clearCategoryPanels() {
  $("#category-manager-list").querySelectorAll(".category-inline-panel").forEach((panel) => panel.remove());
}

function categoryPanel(row, text) {
  clearCategoryPanels();
  const panel = document.createElement("div");
  panel.className = "category-inline-panel";
  const note = document.createElement("p");
  note.textContent = text;
  panel.append(note);
  row.append(panel);
  return panel;
}

async function runCategoryAction(method, body) {
  $("#manager-category-error").textContent = "";
  try {
    await runContentAction("/categories", { method, body });
    await loadCategoryManager();
    await loadArticles();
  } catch (error) {
    $("#manager-category-error").textContent = error.message;
    await loadCategoryManager().catch(() => {});
  }
}

function showRenameCategory(row, stat) {
  const panel = categoryPanel(row, `重命名后，${stat.count} 篇文章会同步更新分类。`);
  const form = document.createElement("div");
  form.className = "category-rename-form";
  const input = document.createElement("input");
  input.maxLength = 20;
  input.value = stat.name;
  input.setAttribute("aria-label", `重命名 ${stat.name}`);
  const save = document.createElement("button");
  save.type = "button";
  save.className = "primary";
  save.textContent = "保存名称";
  const cancel = document.createElement("button");
  cancel.type = "button";
  cancel.className = "ghost";
  cancel.textContent = "取消";
  save.onclick = () => {
    const name = input.value.trim();
    if (!name || name === stat.name) return clearCategoryPanels();
    runCategoryAction("PATCH", { current: stat.name, name });
  };
  cancel.onclick = clearCategoryPanels;
  input.addEventListener("keydown", (event) => {
    if (event.key === "Enter") { event.preventDefault(); save.click(); }
    if (event.key === "Escape") clearCategoryPanels();
  });
  form.append(input, save, cancel);
  panel.append(form);
  input.focus();
  input.select();
}

function showDeleteCategory(row, stat) {
  const panel = categoryPanel(
    row,
    stat.count ? `该分类下有 ${stat.count} 篇文章，请选择删除后要迁移到的分类。` : "该分类下没有文章，可以直接删除。",
  );
  const actions = document.createElement("div");
  actions.className = stat.count ? "replacement-options" : "category-inline-actions";
  const choices = stat.count ? categories.filter((name) => name !== stat.name) : [""];
  choices.forEach((replacement) => {
    const button = document.createElement("button");
    button.type = "button";
    button.className = stat.count ? "" : "confirm-danger";
    button.textContent = stat.count ? `迁移到“${replacement}”并删除` : "确认删除分类";
    button.onclick = () => runCategoryAction("DELETE", { name: stat.name, replacement });
    actions.append(button);
  });
  const cancel = document.createElement("button");
  cancel.type = "button";
  cancel.className = "ghost";
  cancel.textContent = "取消";
  cancel.onclick = clearCategoryPanels;
  actions.append(cancel);
  panel.append(actions);
}

function renderCategoryManager(stats) {
  const list = $("#category-manager-list");
  list.innerHTML = "";
  stats.forEach((stat) => {
    const row = document.createElement("div");
    row.className = "category-manager-row";
    const main = document.createElement("div");
    main.className = "category-row-main";
    const name = document.createElement("div");
    name.className = "category-row-name";
    const strong = document.createElement("strong");
    strong.textContent = stat.name;
    const count = document.createElement("span");
    count.textContent = stat.count ? `${stat.count} 篇文章` : "暂无文章";
    name.append(strong, count);
    const actions = document.createElement("div");
    actions.className = "category-row-actions";
    const rename = document.createElement("button");
    rename.type = "button";
    rename.className = "ghost";
    rename.textContent = "重命名";
    rename.onclick = () => showRenameCategory(row, stat);
    const remove = document.createElement("button");
    remove.type = "button";
    remove.className = "ghost danger";
    remove.textContent = "删除";
    remove.disabled = categories.length <= 1;
    remove.title = remove.disabled ? "至少需要保留一个分类" : "删除分类";
    remove.onclick = () => showDeleteCategory(row, stat);
    actions.append(rename, remove);
    main.append(name, actions);
    row.append(main);
    list.append(row);
  });
}

async function openCategoryManager() {
  $("#manager-category-error").textContent = "";
  show("#category-modal");
  try { await loadCategoryManager(); }
  catch (error) { $("#manager-category-error").textContent = error.message; }
}

$("#manage-categories").addEventListener("click", openCategoryManager);
$("#close-category-modal").addEventListener("click", () => hide("#category-modal"));
$("#manager-add-category").addEventListener("click", async () => {
  const input = $("#manager-new-category");
  const name = input.value.trim();
  $("#manager-category-error").textContent = "";
  try {
    await runContentAction("/categories", { body: { name } });
    input.value = "";
    await loadCategoryManager();
  } catch (error) { $("#manager-category-error").textContent = error.message; }
});
$("#manager-new-category").addEventListener("keydown", (event) => {
  if (event.key === "Enter") { event.preventDefault(); $("#manager-add-category").click(); }
});

function setView(editing) {
  $("#list-view").classList.toggle("hidden", editing);
  $("#edit-view").classList.toggle("hidden", !editing);
}

function setEditorStatus(status) {
  editingStatus = status;
  applyStatus($("#editor-status"), status);
  $("#version-note").textContent = statusMap[status]?.[2] || "";
  const persisted = status !== "new-unsaved";
  $("#more-actions").classList.toggle("hidden", !persisted);
  $("#more-actions").open = false;
  $("#discard").classList.toggle("hidden", status !== "edited-draft");
  $("#unpublish").classList.toggle("hidden", !["live", "edited-draft"].includes(status));
  $("#delete-article").classList.toggle("hidden", !persisted);
}

async function openEditor(slug) {
  editingSlug = slug;
  dirty = false;
  fillCategories();
  $("#preview").innerHTML = "";
  $("#message").textContent = "";
  $("#upload-status").textContent = "";
  if (slug) {
    const article = await api(`/articles/${slug}`);
    $("#edit-heading").textContent = "编辑文章";
    $("#title").value = article.title;
    fillCategories(article.category);
    $("#body").value = article.body;
    setEditorStatus(article.publishStatus);
  } else {
    $("#edit-heading").textContent = "新建文章";
    $("#title").value = "";
    $("#body").value = "";
    setEditorStatus("new-unsaved");
  }
  setView(true);
  schedulePreview(0);
  $("#title").focus();
}

$("#new-article").addEventListener("click", () => openEditor(null));
$("#import-article").addEventListener("click", () => $("#article-file").click());
$("#article-file").addEventListener("change", async (event) => {
  const file = event.target.files?.[0];
  event.target.value = "";
  if (!file) return;
  if (file.size > MAX_UPLOAD_SIZE) {
    $("#import-status").textContent = "文件不能超过 20MB";
    return;
  }

  const extension = file.name.toLowerCase().match(/\.[^.]+$/)?.[0];
  if (![".md", ".docx"].includes(extension)) {
    $("#import-status").textContent = "仅支持 Markdown（.md）或 Word（.docx）文件";
    return;
  }

  $("#import-status").textContent = `正在导入 ${file.name}…`;
  try {
    let result;
    if (extension === ".md") {
      let body = await file.text();
      const heading = body.match(/^#\s+(.+)$/m);
      const title = heading?.[1].trim() || file.name.replace(/\.md$/i, "");
      if (heading?.index !== undefined) {
        body = `${body.slice(0, heading.index)}${body.slice(heading.index + heading[0].length)}`
          .trim()
          .replace(/\n{3,}/g, "\n\n");
      }
      if (!body) throw new Error("Markdown 文件中没有可导入的正文内容");
      result = await api("/articles", { method: "POST", body: { title, category: categories[0], body } });
    } else {
      const dataUrl = await new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result);
        reader.onerror = reject;
        reader.readAsDataURL(file);
      });
      result = await api("/imports/word", {
        method: "POST",
        body: { fileName: file.name, category: categories[0], dataUrl: String(dataUrl).replace(/^data:[^;,]*;/, `data:${DOCX_MIME};`) },
      });
    }
    const successMessage = extension === ".docx"
      ? `导入成功：已创建草稿并提取 ${result.imageCount} 张图片`
      : "导入成功：已创建 Markdown 草稿";
    $("#import-status").textContent = successMessage;
    await openEditor(result.slug);
    $("#message").textContent = successMessage;
  } catch (error) {
    $("#import-status").textContent = error.message;
  }
});
$("#back").addEventListener("click", () => {
  if (dirty && !confirm("还有未保存的内容，确定返回文章列表吗？")) return;
  setView(false);
  loadArticles();
});

function payload() {
  return {
    title: $("#title").value.trim(),
    category: $("#category").value,
    body: $("#body").value,
  };
}

async function save() {
  const result = editingSlug
    ? await api(`/articles/${editingSlug}`, { method: "PUT", body: payload() })
    : await api("/articles", { method: "POST", body: payload() });
  editingSlug = result.slug;
  dirty = false;
  setEditorStatus(result.publishStatus);
  return result.slug;
}

$("#edit-form").addEventListener("submit", async (event) => {
  event.preventDefault();
  $("#message").textContent = "正在保存…";
  try {
    await save();
    $("#message").textContent = editingStatus === "live" ? "内容与线上版本一致。" : "草稿已保存，线上版本没有改变。";
  } catch (error) { $("#message").textContent = error.message; }
});

$("#publish").addEventListener("click", async () => {
  $("#message").textContent = "正在保存草稿…";
  try {
    const slug = await save();
    await runContentAction(`/articles/${slug}/publish`);
    setEditorStatus("live");
    $("#message").textContent = "发布成功，文章已上线。";
  } catch (error) { $("#message").textContent = error.message; }
});

$("#discard").addEventListener("click", async () => {
  if (!editingSlug) return;
  if (!confirm("确定放弃未发布的修改，恢复为当前线上版本吗？")) return;
  try {
    const result = await api(`/articles/${editingSlug}/draft`, { method: "DELETE" });
    dirty = false;
    if (result.removed) { setView(false); loadArticles(); }
    else await openEditor(editingSlug);
  } catch (error) { $("#message").textContent = error.message; }
});

$("#unpublish").addEventListener("click", async () => {
  if (!editingSlug) return;
  const detail = editingStatus === "edited-draft" ? "未发布的修改会继续保留为草稿。" : "文章内容会继续保留为草稿。";
  if (!confirm(`确定将这篇文章从官网下架吗？${detail}`)) return;
  $("#more-actions").open = false;
  $("#message").textContent = "正在下架文章…";
  try {
    await runContentAction(`/articles/${editingSlug}/unpublish`);
    setEditorStatus("new-draft");
    $("#message").textContent = "文章已下架，内容已保留为草稿。";
  } catch (error) { $("#message").textContent = error.message; }
});

$("#delete-article").addEventListener("click", async () => {
  if (!editingSlug) return;
  const prompt = editingStatus === "new-draft"
    ? "确定彻底删除这篇草稿吗？删除后无法恢复。"
    : "确定彻底删除这篇文章吗？线上版本和草稿都会删除，且无法恢复。";
  if (!confirm(prompt)) return;
  $("#more-actions").open = false;
  $("#message").textContent = "正在删除文章…";
  try {
    await runContentAction(`/articles/${editingSlug}`, { method: "DELETE" });
    dirty = false;
    editingSlug = null;
    setView(false);
    await loadArticles();
  } catch (error) { $("#message").textContent = error.message; }
});

async function renderPreview() {
  const body = $("#body").value.trim();
  if (!body) { $("#preview").innerHTML = ""; return; }
  const request = ++previewRequest;
  try {
    const result = await api("/preview", { method: "POST", body: { body } });
    if (request === previewRequest) $("#preview").innerHTML = result.html;
  } catch {
    if (request === previewRequest) $("#preview").textContent = "预览暂时不可用";
  }
}

function schedulePreview(delay = 350) {
  clearTimeout(previewTimer);
  previewTimer = setTimeout(renderPreview, delay);
}

$("#title").addEventListener("input", () => { dirty = true; });
$("#body").addEventListener("input", () => { dirty = true; schedulePreview(); });

$("#upload-button").addEventListener("click", () => $("#file").click());
$("#file").addEventListener("change", async (event) => {
  const file = event.target.files?.[0];
  event.target.value = "";
  if (!file) return;
  if (file.size > MAX_UPLOAD_SIZE) {
    $("#upload-status").textContent = "图片不能超过 20MB";
    return;
  }
  $("#upload-status").textContent = "上传中…";
  try {
    const dataUrl = await new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve(reader.result);
      reader.onerror = reject;
      reader.readAsDataURL(file);
    });
    const result = await api("/uploads", { method: "POST", body: { dataUrl } });
    const textarea = $("#body");
    const start = textarea.selectionStart;
    const markdown = `\n![${file.name}](${result.url})\n`;
    textarea.value = textarea.value.slice(0, start) + markdown + textarea.value.slice(textarea.selectionEnd);
    textarea.dispatchEvent(new Event("input"));
    $("#upload-status").textContent = "图片已插入";
  } catch (error) { $("#upload-status").textContent = error.message; }
});

async function runContentAction(endpoint, { method = "POST", body } = {}) {
  return api(endpoint, { method, body });
}

boot();
