<script setup lang="ts">
import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import {
  ArrowDownToLine,
  ClipboardList,
  Code2,
  Database,
  FileJson,
  LayoutDashboard,
  LogOut,
  MessageSquareText,
  Network,
  RefreshCw,
  ShieldCheck,
} from "lucide-vue-next";
import EndpointsView from "./views/EndpointsView.vue";
import FeedbacksView from "./views/FeedbacksView.vue";
import LegacyJsonView from "./views/LegacyJsonView.vue";
import ReleasesView from "./views/ReleasesView.vue";
import SourcesView from "./views/SourcesView.vue";
import SystemView from "./views/SystemView.vue";
import AuditLogView from "./views/AuditLogView.vue";
import { adminFetch, toChineseError, uploadAdminFile } from "./api/admin";
import { createAuthStore } from "./stores/auth";
import { createDashboardStore } from "./stores/dashboard";
import { createFeedbackStore } from "./stores/feedback";
import { createLegacyStore, type LegacyName } from "./stores/legacy";
import { createReleaseStore } from "./stores/releases";
import { createSourceStore } from "./stores/sources";
import { createSystemStore } from "./stores/system";

const DashboardView = defineAsyncComponent(() => import("./views/DashboardView.vue"));

type SystemTab = "database" | "migration" | "sync" | "security" | "health" | "logs" | "audit";
type ToastState = { message: string; type: "success" | "warn" | "error" };
type LoadSystemOptions = { preserveForms?: boolean };
type LoadDatabaseOptions = { previewLegacy?: boolean; preserveForm?: boolean };
type LoadMailOptions = { preserveForm?: boolean };

type Captcha = {
  captchaId: string;
  image: string;
};

type AuthBootstrap = {
  isDefaultPassword: boolean;
  defaultUsername: string;
  defaultPassword: string;
};

type RouteItem = {
  path: string;
  label: string;
  description: string;
  icon: any;
};

const route = useRoute();
const router = useRouter();
const currentPath = computed(() => normalizeAdminPath(route.path));
const loading = ref(false);
const toast = ref<ToastState | null>(null);
const autoRefreshPaused = ref(false);
const databaseFormEditing = ref(false);
const mailConfigEditing = ref(false);
let refreshTimer: number | undefined;
let systemRefreshTimer: number | undefined;
let toastTimer: number | undefined;
let events: EventSource | null = null;

const authStore = createAuthStore();
const dashboardStore = createDashboardStore();
const feedbackStore = createFeedbackStore();
const releaseStore = createReleaseStore();
const legacyStore = createLegacyStore();
const sourceStore = createSourceStore();
const systemStore = createSystemStore();

const { csrf, captcha, bootstrap: authBootstrap, loginForm, passwordForm } = authStore;
const { dashboard, sourceCheckJobs } = dashboardStore;
const { page: feedbackPage, selected: selectedFeedback, filters: feedbackFilters, update: feedbackUpdate, commentDraft, selectedCodes: feedbackSelectedCodes, detailTab: feedbackDetailTab, tagInput: feedbackTagInput } = feedbackStore;
const { releases, notices: releaseNotices, selectedNotice, noticeDraft, uploadDraft } = releaseStore;
const { sync: legacySync, documents: legacyDocuments, drafts: legacyDrafts, modal: legacyModal, activeMediaCategoryIndex } = legacyStore;
const { sources, endpoints, draft: sourceDraft } = sourceStore;
const { database, databaseConfig, databaseLastSync, databaseSyncJob, databaseSyncOutput, healthSnapshot, auditLogs, auditPage, systemLogPage, migrationStatus, branding, databaseForm, databaseConfigCollapsed, mailConfig, legacySyncMode } = systemStore;

const routes: RouteItem[] = [
  { path: "/admin/dashboard", label: "仪表盘", description: "服务状态、接口心跳与运营指标", icon: LayoutDashboard },
  { path: "/admin/feedbacks", label: "反馈工单", description: "旧客户端反馈与处理流转", icon: MessageSquareText },
  { path: "/admin/releases", label: "发布与日志", description: "发布包、版本公告和兼容日志", icon: ArrowDownToLine },
  { path: "/admin/legacy/update-info", label: "更新 JSON", description: "可视化维护 update-info.json", icon: FileJson },
  { path: "/admin/legacy/media-types", label: "媒体源 JSON", description: "维护旧客户端媒体源结构", icon: ClipboardList },
  { path: "/admin/sources", label: "来源目录", description: "媒体/数据源目录和健康检测", icon: Network },
  { path: "/admin/endpoints", label: "客户端接口", description: "新版客户端动态接口配置", icon: Code2 },
  { path: "/admin/audit", label: "审计日志", description: "操作审计、登录记录与安全事件", icon: ShieldCheck },
  { path: "/admin/system", label: "系统运维", description: "数据库、旧项目同步、安全、健康与审计", icon: Database },
];

const navGroups = [
  { label: "概览", items: routes.filter((item) => ["/admin/dashboard"].includes(item.path)) },
  { label: "反馈", items: routes.filter((item) => ["/admin/feedbacks"].includes(item.path)) },
  { label: "发布与兼容", items: routes.filter((item) => ["/admin/releases", "/admin/legacy/update-info", "/admin/legacy/media-types"].includes(item.path)) },
  { label: "客户端接口", items: routes.filter((item) => ["/admin/sources", "/admin/endpoints"].includes(item.path)) },
  { label: "系统运维", items: routes.filter((item) => ["/admin/audit", "/admin/system"].includes(item.path)) },
];

const pageMeta = computed(() => routes.find((item) => item.path === currentPath.value) || routes[0]);
const activeLegacyName = computed<LegacyName | null>(() => {
  if (currentPath.value.endsWith("/update-info")) return "update-info";
  if (currentPath.value.endsWith("/media-types")) return "media-types";
  return null;
});
const kpis = computed(() => dashboard.value?.kpis || {});
const sourceHealth = computed(() => dashboard.value?.sourceHealth || {});
const feedbackStatus = computed(() => dashboard.value?.feedbackStatus || {});
const heartbeats = computed(() => dashboard.value?.heartbeats || []);
const clientCalls = computed(() => dashboard.value?.clientCalls || []);
const releasePackages = computed(() => releases.value?.packages || []);
const sourceCategories = computed(() => sources.value?.categories || []);
const visibleEndpointCount = computed(() => endpoints.value.filter((item) => item.enabled && item.clientVisible).length);
const healthyEndpointCount = computed(() => endpoints.value.filter((item) => ["ok", "redirected"].includes(endpointStatus(item))).length);
const latestNotice = computed(() => releaseNotices.value[0] || null);
const activeLegacyLabel = computed(() => activeLegacyName.value === "media-types" ? "media-types.json" : "update-info.json");
const activeMediaCategory = computed(() => {
  const categories = legacyDrafts["media-types"].form.categories || [];
  return categories[activeMediaCategoryIndex.value] || null;
});
const systemTab = computed<SystemTab>(() => normalizeSystemTab(route.query.tab));
const heartbeatChartRows = computed(() => {
  const rows = heartbeats.value
    .slice()
    .reverse()
    .map((item: any) => ({
      time: heartbeatTimeValue(item.checkedAt),
      label: timeLabel(item.checkedAt),
      latency: Number(item.latencyMs ?? item.latency_ms ?? 0),
      name: item.name || item.sourceId || "未知接口",
      status: labelStatus(item.status),
    }))
    .filter((item: any) => Number.isFinite(item.latency));
  return rows;
});
const isHeartbeatChartEmpty = computed(() => heartbeats.value.length === 0);

const heartbeatOption = computed(() => ({
  animation: true,
  tooltip: { trigger: "axis" },
  grid: { left: 48, right: 22, top: 28, bottom: 40, containLabel: true },
  xAxis: {
    type: "category",
    boundaryGap: heartbeatChartRows.value.length <= 1,
    data: heartbeatChartRows.value.map((item: any) => item.label),
    axisLine: { lineStyle: { color: "#cbd5e1" } },
    axisLabel: { color: "#64748b" },
  },
  yAxis: {
    type: "value",
    name: "ms",
    min: 0,
    axisLine: { lineStyle: { color: "#cbd5e1" } },
    axisLabel: { color: "#64748b" },
    splitLine: { lineStyle: { color: "#e5e7eb" } },
  },
  series: [
    {
      name: "接口延迟",
      type: "line",
      smooth: true,
      showSymbol: true,
      symbolSize: 7,
      connectNulls: true,
      areaStyle: { opacity: 0.18 },
      data: heartbeatChartRows.value.map((item: any) => item.latency),
      color: "#2563eb",
      lineStyle: { width: 3 },
      emphasis: { focus: "series" },
    },
  ],
}));

const healthOption = computed(() => {
  const data = healthStatusOrder.map((item) => ({
    name: item.label,
    value: Number(sourceHealth.value?.[item.key] || 0),
    itemStyle: { color: item.color },
  })).filter((item) => item.value > 0);
  return {
    tooltip: { trigger: "item" },
    legend: { bottom: 0 },
    series: [
      {
        name: "接口健康",
        type: "pie",
        radius: ["48%", "72%"],
        data: data.length ? data : [{ name: "暂无数据", value: 1, itemStyle: { color: "#cbd5e1" } }],
      },
    ],
  };
});

const feedbackOption = computed(() => ({
  tooltip: { trigger: "axis" },
  grid: { left: 34, right: 12, top: 20, bottom: 28 },
  xAxis: { type: "category", data: objectEntries(feedbackStatus.value).map((item) => item.name) },
  yAxis: { type: "value", splitLine: { lineStyle: { color: "#e5e7eb" } } },
  series: [{ name: "工单", type: "bar", data: objectEntries(feedbackStatus.value).map((item) => item.value), color: "#0f766e" }],
}));

const availabilityOption = computed(() => {
  const total = Number(kpis.value.sourceTotal || 0);
  const ok = Number(sourceHealth.value.ok || 0) + Number(sourceHealth.value.redirected || 0);
  const value = total ? Math.round((ok / total) * 100) : 0;
  return {
    series: [
      {
        type: "gauge",
        progress: { show: true, width: 12 },
        axisLine: { lineStyle: { width: 12 } },
        axisLabel: { distance: 16 },
        pointer: { width: 4 },
        detail: { formatter: "{value}%", fontSize: 24 },
        data: [{ value, name: "可用率" }],
      },
    ],
  };
});

const healthStatusOrder = [
  { key: "ok", label: "正常", color: "#16a34a" },
  { key: "redirected", label: "重定向健康", color: "#f59e0b" },
  { key: "degraded", label: "降级", color: "#d97706" },
  { key: "error", label: "错误", color: "#dc2626" },
  { key: "unknown", label: "未知", color: "#94a3b8" },
];

const viewContext = computed(() => ({
  activeLegacyLabel: activeLegacyLabel.value,
  activeLegacyName: activeLegacyName.value,
  addFeedbackComment,
  addMediaCategory,
  addMediaSubcategory,
  addUpdateMirror,
  applyLegacyModal,
  auditPage,
  auditLogs: auditLogs.value,
  autoRefreshPaused: autoRefreshPaused.value,
  availabilityOption: availabilityOption.value,
  branding,
  changePassword,
  checkSources,
  clientCalls: clientCalls.value,
  commentDraft,
  copyEndpointToSource,
  database: database.value,
  databaseConfig: databaseConfig.value,
  databaseConfigCollapsed: databaseConfigCollapsed.value,
  databaseFormEditing: databaseFormEditing.value,
  databaseForm,
  databaseLastSync: databaseLastSync.value,
  databaseSyncJob: databaseSyncJob.value,
  databaseSyncOutput: databaseSyncOutput.value,
  databaseSyncStatusLabel,
  databaseSyncDirectionLabel,
  databaseSyncTableCount,
  databaseConfigSummary,
  deleteEndpoint,
  editDatabaseConfig,
  endpointStatus,
  endpoints: endpoints.value,
  feedbackFilters,
  feedbackOption: feedbackOption.value,
  feedbackPage: feedbackPage.value,
  feedbackUpdate,
  feedbackSelectedCodes: feedbackSelectedCodes.value,
  feedbackDetailTab: feedbackDetailTab.value,
  feedbackTagInput: feedbackTagInput.value,
  setFeedbackDetailTab: (tab: string) => { feedbackDetailTab.value = tab as any; },
  setFeedbackTagInput: (val: string) => { feedbackTagInput.value = val; },
  addFeedbackTag,
  removeFeedbackTag,
  toggleFeedbackCode,
  toggleAllFeedbackCodes,
  bulkUpdateFeedbacks,
  formatBytes,
  formatHealthOutput,
  healthOption: healthOption.value,
  healthSnapshot: healthSnapshot.value,
  healthyEndpointCount: healthyEndpointCount.value,
  heartbeatOption: heartbeatOption.value,
  heartbeats: heartbeats.value,
  isHeartbeatChartEmpty: isHeartbeatChartEmpty.value,
  importNotices,
  kpis: kpis.value,
  labelStatus,
  labelPriority,
  latestNotice: latestNotice.value,
  legacyDocuments,
  legacyDrafts,
  legacyModal,
  activeMediaCategoryIndex: activeMediaCategoryIndex.value,
  activeMediaCategory: activeMediaCategory.value,
  legacySync: legacySync.value,
  legacySyncMode: legacySyncMode.value,
  loadAudit,
  loadBranding,
  loadFeedbacks,
  loadMigrationStatus,
  loadSystemLogs,
  mailConfig,
  mailConfigEditing: mailConfigEditing.value,
  markDatabaseFormEditing,
  markMailConfigEditing,
  migrationStatus: migrationStatus.value,
  loadMailConfig,
  reloadDatabaseConfig,
  reloadMailConfig,
  saveDatabase,
  saveBranding,
  saveMailConfig,
  testMail,
  retryFeedbackMail,
  navigate,
  noticeDraft,
  onPackageSelected,
  openFeedback,
  openNotice,
  passwordForm,
  pretty,
  previewLegacySync,
  removeItem,
  releaseNotices: releaseNotices.value,
  releasePackages: releasePackages.value,
  releases: releases.value,
  restoreLegacy,
  restoreNotice,
  runLegacySync,
  saveFeedbackUpdate,
  saveLegacy,
  saveNotice,
  saveSource,
  selectedFeedback: selectedFeedback.value,
  selectedNotice: selectedNotice.value,
  sourceCategories: sourceCategories.value,
  sourceCheckJobs: sourceCheckJobs.value,
  sourceDraft,
  statusTone,
  syncDatabase,
  systemLogPage,
  systemTab: systemTab.value,
  setSystemTab,
  setAuditPage,
  setSystemLogPage,
  selectSystemLog,
  selectAuditLog,
  testDatabase,
  toggleAutoRefresh,
  openMediaCategoryModal,
  openMediaSubcategoryModal,
  openUpdateMirrorModal,
  selectMediaCategory,
  closeLegacyModal,
  updateLegacyRawFromForm,
  uploadDraft,
  uploadPackage,
  auditMessage,
  auditTypeLabel,
  validateLegacy,
  validateNotice,
  visibleEndpointCount: visibleEndpointCount.value,
}));

async function api<T>(target: string, init: RequestInit = {}): Promise<T> {
  return adminFetch<T>(target, init, { csrf: csrf.value });
}

function uploadWithProgress<T>(target: string, form: FormData, onProgress: (loaded: number, total: number) => void): Promise<T> {
  return uploadAdminFile<T>(target, form, { csrf: csrf.value }, (progress) => onProgress(progress.loaded, progress.total));
}

function normalizeAdminPath(value: string) {
  if (value === "/admin" || value === "/admin/") return "/admin/dashboard";
  if (value === "/") return "/admin/dashboard";
  if (["/admin/database", "/admin/health", "/admin/settings", "/admin/audit"].includes(value)) return "/admin/system";
  return value;
}

function normalizeSystemTab(value: unknown): SystemTab {
  const tab = Array.isArray(value) ? value[0] : value;
  if (tab === "migration" || tab === "sync" || tab === "security" || tab === "health" || tab === "logs" || tab === "audit") return tab;
  return "database";
}

function navigate(next: string) {
  if (currentPath.value === next) {
    void load();
    return;
  }
  void router.push(next);
}

function setSystemTab(tab: SystemTab) {
  void router.replace({ path: "/admin/system", query: tab === "database" ? {} : { tab } });
}

function toggleAutoRefresh() {
  autoRefreshPaused.value = !autoRefreshPaused.value;
}

function setToast(message: string, type: ToastState["type"] = "success") {
  toast.value = { message, type };
  if (toastTimer) window.clearTimeout(toastTimer);
  toastTimer = window.setTimeout(() => {
    if (toast.value?.message === message) toast.value = null;
  }, 2500);
}

async function guarded(task: () => Promise<void>) {
  loading.value = true;
  try {
    await task();
  } catch (error) {
    const rawMessage = error instanceof Error ? error.message : String(error);
    const message = toChineseError(rawMessage);
    setToast(message, "error");
    if (isAuthError(rawMessage, message)) {
      csrf.value = "";
      sessionStorage.removeItem("ymhut.csrf");
      localStorage.removeItem("ymhut.csrf");
      events?.close();
      events = null;
      navigate("/admin/login");
    }
  } finally {
    loading.value = false;
  }
}

function isAuthError(raw: string, message: string) {
  const text = `${raw} ${message}`.toLowerCase();
  return text.includes("unauthorized") || text.includes("login required") || text.includes("401") || message.includes("需要登录");
}

async function loadCaptcha() {
  captcha.value = await api<Captcha>("/api/admin/auth/captcha");
}

async function loadAuthBootstrap() {
  authBootstrap.value = await api<AuthBootstrap>("/api/admin/auth/bootstrap");
}

async function login() {
  await guarded(async () => {
    const data = await api<{ csrfToken: string }>("/api/admin/auth/login", {
      method: "POST",
      body: JSON.stringify({ ...loginForm, captchaId: captcha.value?.captchaId }),
    });
    csrf.value = data.csrfToken;
    sessionStorage.setItem("ymhut.csrf", csrf.value);
    localStorage.removeItem("ymhut.csrf");
    connectAdminEvents();
    navigate("/admin/dashboard");
  });
}

async function logout() {
  await api("/api/admin/auth/logout", { method: "POST", body: "{}" }).catch(() => undefined);
  csrf.value = "";
  sessionStorage.removeItem("ymhut.csrf");
  localStorage.removeItem("ymhut.csrf");
  events?.close();
  events = null;
  navigate("/admin/login");
}

async function load() {
  await guarded(async () => {
    if (currentPath.value === "/admin/login") {
      await Promise.all([loadAuthBootstrap(), loadCaptcha()]);
      return;
    }
    if (!csrf.value) {
      navigate("/admin/login");
      return;
    }
    if (currentPath.value === "/admin/dashboard") await loadDashboard();
    if (currentPath.value === "/admin/feedbacks") await loadFeedbacks();
    if (currentPath.value === "/admin/releases") await loadReleases();
    if (currentPath.value === "/admin/sources") await loadSources();
    if (currentPath.value === "/admin/endpoints") await loadEndpoints();
    if (currentPath.value === "/admin/audit") await loadAudit();
    if (currentPath.value === "/admin/system") await loadSystem({ preserveForms: true });
    const legacyName = activeLegacyName.value;
    if (legacyName) await loadLegacy(legacyName);
    connectAdminEvents();
  });
}

async function loadDashboard() {
  dashboard.value = await api("/api/admin/dashboard/overview?window=24h");
}

async function loadSystem(options: LoadSystemOptions = {}) {
  await Promise.all([
    loadDatabase({ preserveForm: options.preserveForms }),
    loadMailConfig({ preserveForm: options.preserveForms }),
    loadHealth(),
    loadAudit(),
    loadSystemLogs(),
    loadDatabaseSyncLatest(),
    loadMigrationStatus(),
    loadBranding(),
  ]);
}

async function loadFeedbacks(page?: number) {
  if (page !== undefined) feedbackFilters.page = page;
  const params = new URLSearchParams({ page: String(feedbackFilters.page), perPage: String(feedbackFilters.perPage) });
  if (feedbackFilters.q) params.set("q", feedbackFilters.q);
  if (feedbackFilters.status) params.set("status", feedbackFilters.status);
  if (feedbackFilters.priority) params.set("priority", feedbackFilters.priority);
  if ((feedbackFilters as any).category) params.set("category", (feedbackFilters as any).category);
  if ((feedbackFilters as any).assignee) params.set("assignee", (feedbackFilters as any).assignee);
  const data = await api<{ page: any }>(`/api/admin/feedbacks?${params}`);
  feedbackPage.value = data.page || { items: [], total: 0, page: 1, perPage: 20 };
  feedbackSelectedCodes.value = [];
}

async function openFeedback(item: any) {
  const data = await api<{ feedback: any }>(`/api/admin/feedbacks/${encodeURIComponent(item.code)}`);
  selectedFeedback.value = data.feedback;
  feedbackUpdate.status = data.feedback.status || "new";
  feedbackUpdate.priority = data.feedback.priority || "normal";
  feedbackUpdate.statusDetail = data.feedback.statusDetail || "";
  feedbackUpdate.publicReply = data.feedback.publicReply || "";
  feedbackUpdate.assignee = data.feedback.assignee || "";
  feedbackUpdate.tags = data.feedback.tags ? [...data.feedback.tags] : [];
  feedbackDetailTab.value = "info";
}

function toggleFeedbackCode(code: string) {
  const idx = feedbackSelectedCodes.value.indexOf(code);
  if (idx >= 0) feedbackSelectedCodes.value.splice(idx, 1);
  else feedbackSelectedCodes.value.push(code);
}

function toggleAllFeedbackCodes() {
  const items: any[] = feedbackPage.value?.items || [];
  if (feedbackSelectedCodes.value.length === items.length) {
    feedbackSelectedCodes.value = [];
  } else {
    feedbackSelectedCodes.value = items.map((item: any) => item.code);
  }
}

async function bulkUpdateFeedbacks(payload: { status?: string; assignee?: string; tags?: string[] }) {
  if (!feedbackSelectedCodes.value.length) return;
  await guarded(async () => {
    await api("/api/admin/feedbacks/bulk", {
      method: "PATCH",
      body: JSON.stringify({ codes: feedbackSelectedCodes.value, ...payload }),
    });
    setToast(`已批量更新 ${feedbackSelectedCodes.value.length} 条工单`);
    feedbackSelectedCodes.value = [];
    await loadFeedbacks();
  });
}

function addFeedbackTag() {
  const tag = feedbackTagInput.value.trim();
  if (!tag || feedbackUpdate.tags.includes(tag)) { feedbackTagInput.value = ""; return; }
  feedbackUpdate.tags = [...feedbackUpdate.tags, tag];
  feedbackTagInput.value = "";
}

function removeFeedbackTag(tag: string) {
  feedbackUpdate.tags = feedbackUpdate.tags.filter((t) => t !== tag);
}

async function saveFeedbackUpdate() {
  if (!selectedFeedback.value) return;
  await guarded(async () => {
    await api(`/api/admin/feedbacks/${encodeURIComponent(selectedFeedback.value.code)}`, { method: "PATCH", body: JSON.stringify(feedbackUpdate) });
    setToast("反馈工单已更新");
    await openFeedback(selectedFeedback.value);
    await loadFeedbacks();
  });
}

async function addFeedbackComment() {
  if (!selectedFeedback.value || !commentDraft.body.trim()) return;
  await guarded(async () => {
    await api(`/api/admin/feedbacks/${encodeURIComponent(selectedFeedback.value.code)}/comments`, {
      method: "POST",
      body: JSON.stringify({ author: "admin", body: commentDraft.body, internal: commentDraft.internal }),
    });
    commentDraft.body = "";
    await openFeedback(selectedFeedback.value);
  });
}

async function loadReleases(preferredVersion = noticeDraft.version) {
  const [releaseData, noticeData] = await Promise.all([
    api<{ manifest: any }>("/api/admin/releases"),
    api<{ items: any[] }>("/api/admin/releases/notices"),
  ]);
  releases.value = releaseData.manifest;
  releaseNotices.value = noticeData.items || [];
  const target = preferredVersion && releaseNotices.value.some((item: any) => item.version === preferredVersion)
    ? preferredVersion
    : releaseNotices.value[0]?.version;
  if (target && noticeDraft.version !== target) await openNotice(target);
}

async function importNotices() {
  await guarded(async () => {
    const data = await api<{ items: any[] }>("/api/admin/releases/notices/import", { method: "POST", body: "{}" });
    releaseNotices.value = data.items || [];
    setToast("版本日志已从目录重新导入");
  });
}

async function openNotice(version: string) {
  const data = await api<{ document: any }>(`/api/admin/releases/notices/${encodeURIComponent(version)}`);
  selectedNotice.value = data.document;
  noticeDraft.version = version;
  noticeDraft.raw = data.document.raw || "";
  noticeDraft.preview = data.document.parsed || null;
}

async function validateNotice() {
  if (!noticeDraft.version) return;
  const data = await api<{ document: any }>(`/api/admin/releases/notices/${encodeURIComponent(noticeDraft.version)}/validate`, {
    method: "POST",
    body: JSON.stringify({ raw: noticeDraft.raw }),
  });
  noticeDraft.raw = data.document.raw;
  noticeDraft.preview = data.document.parsed;
  setToast("版本日志 JSON 校验通过");
}

async function saveNotice() {
  if (!noticeDraft.version) return;
  await guarded(async () => {
    const data = await api<{ document: any }>(`/api/admin/releases/notices/${encodeURIComponent(noticeDraft.version)}`, {
      method: "PUT",
      body: JSON.stringify({ raw: noticeDraft.raw, note: noticeDraft.note }),
    });
    selectedNotice.value = data.document;
    noticeDraft.note = "";
    setToast("版本日志已保存并同步兼容更新信息");
    await loadReleases(data.document?.notice?.version || noticeDraft.version);
  });
}

async function restoreNotice(revisionId: number) {
  if (!noticeDraft.version) return;
  await guarded(async () => {
    const data = await api<{ document: any }>(`/api/admin/releases/notices/${encodeURIComponent(noticeDraft.version)}/restore`, {
      method: "POST",
      body: JSON.stringify({ revisionId }),
    });
    selectedNotice.value = data.document;
    noticeDraft.raw = data.document.raw || "";
    noticeDraft.preview = data.document.parsed || null;
    setToast("版本日志已恢复");
  });
}

async function loadLegacy(name: LegacyName) {
  const data = await api<{ document: any }>(`/api/admin/legacy/${name}`);
  legacyDocuments[name] = data.document;
  legacyDrafts[name].raw = data.document.raw || "";
  legacyDrafts[name].preview = data.document.parsed || null;
  legacyDrafts[name].form = makeLegacyForm(name, data.document.parsed || {});
  if (name === "media-types") clampMediaCategoryIndex();
}

function onPackageSelected(event: Event) {
  const input = event.target as HTMLInputElement;
  uploadDraft.file = input.files?.[0] || null;
  uploadDraft.progress = 0;
  uploadDraft.loadedBytes = 0;
  uploadDraft.totalBytes = uploadDraft.file?.size || 0;
  uploadDraft.status = uploadDraft.file ? "等待上传" : "";
  if (uploadDraft.file && !uploadDraft.version) {
    const version = uploadDraft.file.name.match(/\d+\.\d+\.\d+(?:\.\d+)?/)?.[0];
    if (version) uploadDraft.version = version;
  }
}

async function uploadPackage() {
  if (!uploadDraft.file) {
    setToast("请选择要上传的发布包", "warn");
    return;
  }
  await guarded(async () => {
    const form = new FormData();
    form.append("file", uploadDraft.file as File);
    form.append("version", uploadDraft.version);
    form.append("platform", uploadDraft.platform);
    form.append("arch", uploadDraft.arch);
    form.append("channel", uploadDraft.channel);
    form.append("notes", uploadDraft.notes);
    form.append("updateManifest", String(uploadDraft.updateManifest));
    uploadDraft.uploading = true;
    uploadDraft.status = "正在上传";
    uploadDraft.progress = 0;
    uploadDraft.loadedBytes = 0;
    uploadDraft.totalBytes = uploadDraft.file?.size || 0;
    await uploadWithProgress("/api/admin/releases/packages", form, (loaded, total) => {
      uploadDraft.loadedBytes = loaded;
      uploadDraft.totalBytes = total;
      uploadDraft.progress = total > 0 ? Math.min(100, Math.round((loaded / total) * 100)) : 0;
      uploadDraft.status = uploadDraft.progress >= 100 ? "服务端处理中" : "正在上传";
    });
    uploadDraft.progress = 100;
    uploadDraft.status = "上传完成";
    uploadDraft.file = null;
    uploadDraft.notes = "";
    setToast("发布包已上传并放入下载目录");
    await loadReleases();
    window.setTimeout(() => {
      if (!uploadDraft.uploading) {
        uploadDraft.progress = 0;
        uploadDraft.loadedBytes = 0;
        uploadDraft.totalBytes = 0;
        uploadDraft.status = "";
      }
    }, 1200);
  }).finally(() => {
    uploadDraft.uploading = false;
  });
}

async function validateLegacy(name: LegacyName) {
  const data = await api<{ document: any }>(`/api/admin/legacy/${name}/validate`, {
    method: "POST",
    body: JSON.stringify({ raw: legacyDrafts[name].raw }),
  });
  legacyDrafts[name].raw = data.document.raw;
  legacyDrafts[name].preview = data.document.parsed;
  setToast("兼容 JSON 校验通过");
}

async function saveLegacy(name: LegacyName) {
  await guarded(async () => {
    if (legacyDrafts[name].tab === "form") updateLegacyRawFromForm(name);
    const data = await api<{ document: any }>(`/api/admin/legacy/${name}`, {
      method: "PUT",
      body: JSON.stringify({ raw: legacyDrafts[name].raw, note: legacyDrafts[name].note }),
    });
    legacyDocuments[name] = data.document;
    legacyDrafts[name].raw = data.document.raw;
    legacyDrafts[name].preview = data.document.parsed;
    legacyDrafts[name].form = makeLegacyForm(name, data.document.parsed || {});
    legacyDrafts[name].note = "";
    if (name === "media-types") {
      clampMediaCategoryIndex();
    }
    if (name === "update-info") {
      await loadReleases(legacyDrafts[name].preview?.app_version || legacyDrafts[name].preview?.version || noticeDraft.version);
    }
    setToast("兼容 JSON 已保存并发布到旧路径");
  });
}

async function restoreLegacy(name: LegacyName, revisionId: number) {
  await guarded(async () => {
    const data = await api<{ document: any }>(`/api/admin/legacy/${name}/restore`, {
      method: "POST",
      body: JSON.stringify({ revisionId }),
    });
    legacyDocuments[name] = data.document;
    legacyDrafts[name].raw = data.document.raw;
    legacyDrafts[name].preview = data.document.parsed;
    legacyDrafts[name].form = makeLegacyForm(name, data.document.parsed || {});
    if (name === "media-types") clampMediaCategoryIndex();
    if (name === "update-info") await loadReleases(legacyDrafts[name].preview?.app_version || legacyDrafts[name].preview?.version || noticeDraft.version);
    setToast("兼容 JSON 已恢复");
  });
}

function makeLegacyForm(name: LegacyName, parsed: any) {
  if (name === "media-types") {
    return {
      layout_version: parsed.layout_version || "1.0.0",
      last_updated: parsed.last_updated || "",
      ui_config: JSON.stringify(parsed.ui_config || {}, null, 2),
      categories: clone(parsed.categories || []).map((cat: any) => ({
        id: cat.id || "",
        name: cat.name || "",
        enabled: cat.enabled !== false,
        subcategories: clone(cat.subcategories || []).map((sub: any) => ({
          id: sub.id || "",
          name: sub.name || "",
          description: sub.description || "",
          api_url: sub.api_url || "",
          thumbnail_url: sub.thumbnail_url || "",
          refresh_interval: Number(sub.refresh_interval || 300),
          supported_formats: Array.isArray(sub.supported_formats) ? sub.supported_formats.join(", ") : "",
          downloadable: sub.downloadable !== false,
        })),
      })),
    };
  }
  return {
    app_version: parsed.app_version || parsed.version || "",
    title: parsed.title || "",
    message: parsed.message || "",
    message_md: parsed.message_md || "",
    download_url: parsed.download_url || "",
    release_notes: parsed.release_notes || "",
    release_notes_md: parsed.release_notes_md || "",
    update_notes: JSON.stringify(parsed.update_notes || {}, null, 2),
    last_update_notes: JSON.stringify(parsed.last_update_notes || {}, null, 2),
    download_mirrors: clone(parsed.download_mirrors || []),
    package_sha256: parsed.package_sha256 || "",
    package_size: parsed.package_size || "",
    updated_at: parsed.updated_at || parsed.last_updated || "",
  };
}

function updateLegacyRawFromForm(name: LegacyName) {
  const current = parseJSONSafe(legacyDrafts[name].raw, legacyDrafts[name].preview || {});
  const form = legacyDrafts[name].form || {};
  if (name === "media-types") {
    current.layout_version = form.layout_version || "1.0.0";
    current.last_updated = form.last_updated || new Date().toISOString();
    current.ui_config = parseJSONSafe(form.ui_config, current.ui_config || {});
    current.categories = (form.categories || []).map((cat: any) => ({
      ...(findByID(current.categories, cat.id) || {}),
      id: cat.id,
      name: cat.name,
      enabled: cat.enabled !== false,
      subcategories: (cat.subcategories || []).map((sub: any) => ({
        ...(findByID((findByID(current.categories, cat.id) || {}).subcategories, sub.id) || {}),
        id: sub.id,
        name: sub.name,
        description: sub.description,
        api_url: sub.api_url,
        thumbnail_url: sub.thumbnail_url,
        refresh_interval: Number(sub.refresh_interval || 300),
        supported_formats: splitList(sub.supported_formats),
        downloadable: sub.downloadable !== false,
      })),
    }));
  } else {
    for (const key of ["app_version", "title", "message", "message_md", "download_url", "release_notes", "release_notes_md", "package_sha256", "updated_at"]) {
      if (form[key] !== undefined) current[key] = form[key];
    }
    if (form.package_size !== "") current.package_size = Number(form.package_size || 0);
    current.download_mirrors = clone(form.download_mirrors || current.download_mirrors || []);
    current.update_notes = parseJSONSafe(form.update_notes, current.update_notes || {});
    current.last_update_notes = parseJSONSafe(form.last_update_notes, current.last_update_notes || {});
  }
  legacyDrafts[name].raw = JSON.stringify(current, null, 2) + "\n";
  legacyDrafts[name].preview = current;
}

function addUpdateMirror() {
  const doc = parseJSONSafe(legacyDrafts["update-info"].raw, legacyDrafts["update-info"].preview || {});
  const mirrors = Array.isArray(doc.download_mirrors) ? doc.download_mirrors : [];
  mirrors.push({ id: `mirror-${mirrors.length + 1}`, name: "备用镜像", url: "", type: "direct", enabled: true });
  doc.download_mirrors = mirrors;
  legacyDrafts["update-info"].raw = JSON.stringify(doc, null, 2) + "\n";
  legacyDrafts["update-info"].preview = doc;
}

function addMediaCategory(name: LegacyName) {
  const form = legacyDrafts[name].form;
  if (!Array.isArray(form.categories)) form.categories = [];
  form.categories.push({ id: `category-${form.categories.length + 1}`, name: "新分类", enabled: true, subcategories: [] });
}

function addMediaSubcategory(category: any) {
  if (!Array.isArray(category.subcategories)) category.subcategories = [];
  category.subcategories.push({ id: `source-${category.subcategories.length + 1}`, name: "新接口", api_url: "", refresh_interval: 300, supported_formats: "json", downloadable: true });
}

function openMediaCategoryModal(index = -1) {
  const categories = legacyDrafts["media-types"].form.categories || [];
  const existing = index >= 0 ? categories[index] : null;
  Object.assign(legacyModal, {
    open: true,
    type: "media-category",
    categoryIndex: index,
    itemIndex: -1,
    draft: clone(existing || { id: `category-${categories.length + 1}`, name: "新分类", enabled: true }),
  });
}

function selectMediaCategory(index: number) {
  activeMediaCategoryIndex.value = Math.max(0, index);
  clampMediaCategoryIndex();
}

function clampMediaCategoryIndex() {
  const categories = legacyDrafts["media-types"].form.categories || [];
  if (categories.length === 0) {
    activeMediaCategoryIndex.value = 0;
    return;
  }
  activeMediaCategoryIndex.value = Math.min(Math.max(0, activeMediaCategoryIndex.value), categories.length - 1);
}

function openMediaSubcategoryModal(categoryIndex = activeMediaCategoryIndex.value, itemIndex = -1) {
  const categories = legacyDrafts["media-types"].form.categories || [];
  const category = categories[categoryIndex];
  if (!category) return;
  activeMediaCategoryIndex.value = categoryIndex;
  const subcategories = category.subcategories || [];
  const existing = itemIndex >= 0 ? subcategories[itemIndex] : null;
  Object.assign(legacyModal, {
    open: true,
    type: "media-subcategory",
    categoryIndex,
    itemIndex,
    draft: clone(existing || { id: `source-${subcategories.length + 1}`, name: "新接口", api_url: "", thumbnail_url: "", refresh_interval: 300, supported_formats: "json", downloadable: true, description: "" }),
  });
}

function openUpdateMirrorModal(index = -1) {
  const form = legacyDrafts["update-info"].form;
  if (!Array.isArray(form.download_mirrors)) form.download_mirrors = [];
  const mirrors = form.download_mirrors;
  const existing = index >= 0 ? mirrors[index] : null;
  Object.assign(legacyModal, {
    open: true,
    type: "update-mirror",
    categoryIndex: -1,
    itemIndex: index,
    draft: clone(existing || { id: `mirror-${mirrors.length + 1}`, name: "备用镜像", url: "", type: "direct", enabled: true }),
  });
}

function closeLegacyModal() {
  Object.assign(legacyModal, { open: false, type: "", categoryIndex: -1, itemIndex: -1, draft: {} });
}

function applyLegacyModal() {
  if (legacyModal.type === "media-category") {
    const form = legacyDrafts["media-types"].form;
    if (!Array.isArray(form.categories)) form.categories = [];
    const next = { ...legacyModal.draft, enabled: legacyModal.draft.enabled !== false, subcategories: legacyModal.draft.subcategories || [] };
    if (legacyModal.categoryIndex >= 0) {
      next.subcategories = form.categories[legacyModal.categoryIndex]?.subcategories || [];
      form.categories.splice(legacyModal.categoryIndex, 1, next);
      activeMediaCategoryIndex.value = legacyModal.categoryIndex;
    } else {
      form.categories.push(next);
      activeMediaCategoryIndex.value = form.categories.length - 1;
    }
    clampMediaCategoryIndex();
  }
  if (legacyModal.type === "media-subcategory") {
    const category = legacyDrafts["media-types"].form.categories?.[legacyModal.categoryIndex];
    if (!category) return;
    if (!Array.isArray(category.subcategories)) category.subcategories = [];
    const next = { ...legacyModal.draft, refresh_interval: Number(legacyModal.draft.refresh_interval || 300), downloadable: legacyModal.draft.downloadable !== false };
    if (legacyModal.itemIndex >= 0) category.subcategories.splice(legacyModal.itemIndex, 1, next);
    else category.subcategories.push(next);
  }
  if (legacyModal.type === "update-mirror") {
    const form = legacyDrafts["update-info"].form;
    if (!Array.isArray(form.download_mirrors)) form.download_mirrors = [];
    const mirrors = form.download_mirrors;
    const next = { ...legacyModal.draft, enabled: legacyModal.draft.enabled !== false };
    if (legacyModal.itemIndex >= 0) mirrors.splice(legacyModal.itemIndex, 1, next);
    else mirrors.push(next);
    updateLegacyRawFromForm("update-info");
  }
  closeLegacyModal();
}

function removeItem(list: any[], index: number) {
  list.splice(index, 1);
  clampMediaCategoryIndex();
}

async function loadSources() {
  const data = await api<{ catalog: any }>("/api/admin/sources");
  sources.value = data.catalog || { categories: [] };
}

async function saveSource() {
  await guarded(async () => {
    await api("/api/admin/sources", { method: "POST", body: JSON.stringify(sourceDraft) });
    setToast("接口源已保存");
    await Promise.all([loadSources(), loadEndpoints()]);
  });
}

async function checkSources() {
  await guarded(async () => {
    const data = await api<{ jobId: string; job: any }>("/api/admin/sources/check", { method: "POST", body: "{}" });
    if (data.job) sourceCheckJobs.value = [data.job, ...sourceCheckJobs.value.filter((item) => item.id !== data.job.id)].slice(0, 5);
    setToast(`服务端接口检测已进入队列：${data.jobId}`);
    if (currentPath.value === "/admin/dashboard") await loadDashboard();
    if (currentPath.value === "/admin/sources") await loadSources();
    if (currentPath.value === "/admin/system") await loadSystem({ preserveForms: true });
  });
}

async function loadSourceCheckJobs() {
  const data = await api<{ items: any[] }>("/api/admin/sources/check/status");
  sourceCheckJobs.value = data.items || [];
}

async function loadEndpoints() {
  const data = await api<{ items: any[] }>("/api/admin/endpoints");
  endpoints.value = data.items || [];
}

function copyEndpointToSource(item: any) {
  Object.assign(sourceDraft, {
    sourceId: item.id || item.sourceId,
    categoryId: item.category || item.categoryId || "custom",
    categoryName: item.category || item.categoryName || "自定义接口",
    name: item.name,
    method: item.method || "GET",
    apiUrl: item.urlTemplate || item.apiUrl || "",
    urlTemplate: item.urlTemplate || item.apiUrl || "",
    proxyMode: item.proxyMode || "client_direct",
    enabled: item.enabled,
    clientVisible: item.clientVisible,
    cacheSeconds: item.cacheSeconds || 300,
    checkIntervalSec: item.checkIntervalSec || item.cacheSeconds || 300,
    supportedFormats: JSON.stringify(item.supportedFormats || ["json"]),
  });
  navigate("/admin/sources");
}

async function deleteEndpoint(item: any) {
  const sourceID = item.id || item.sourceId;
  if (!sourceID) return;
  if (!window.confirm(`确认删除客户端接口「${sourceID}」？删除后会同步兼容 media-types.json 和 update-info.json。`)) return;
  await guarded(async () => {
    await api(`/api/admin/sources/${encodeURIComponent(sourceID)}`, { method: "DELETE" });
    setToast("客户端接口已删除，兼容 JSON 已同步");
    await Promise.all([loadSources().catch(() => undefined), loadEndpoints()]);
  });
}

async function loadDatabase(options: LoadDatabaseOptions = {}) {
  const data = await api<{ database: any; config?: any }>("/api/admin/database/status");
  database.value = data.database;
  databaseConfig.value = data.config || null;
  if (data.database?.currentSyncJob) {
    databaseSyncJob.value = data.database.currentSyncJob;
    databaseSyncOutput.value = data.database.currentSyncJob.output || [];
  }
  if (!options.preserveForm || !databaseFormEditing.value) {
    applyDatabaseConfig(data.config || {}, data.database || {});
    databaseFormEditing.value = false;
  }
  if (options.previewLegacy !== false) await previewLegacySync();
}

function applyDatabaseConfig(config: any, status: any = {}) {
  databaseForm.provider = config.provider || status.configProvider || "sqlite";
  databaseForm.sqlitePath = config.sqlitePath || "";
  databaseForm.mysqlHost = config.mysqlHost || "127.0.0.1";
  databaseForm.mysqlPort = Number(config.mysqlPort || 3306);
  databaseForm.mysqlDatabase = config.mysqlDatabase || "";
  databaseForm.mysqlUser = config.mysqlUser || "";
  databaseForm.mysqlPassword = "";
  databaseForm.mysqlDsn = config.mysqlDsn || "";
  databaseConfigCollapsed.value = databaseForm.provider === "mysql" && Boolean(config.mysqlHost || config.mysqlDatabase || config.mysqlDsn);
}

function databasePayload() {
  return {
    provider: databaseForm.provider,
    sqlite_path: databaseForm.sqlitePath,
    mysql_host: databaseForm.mysqlHost,
    mysql_port: Number(databaseForm.mysqlPort || 3306),
    mysql_database: databaseForm.mysqlDatabase,
    mysql_user: databaseForm.mysqlUser,
    mysql_password: databaseForm.mysqlPassword,
  };
}

async function testDatabase() {
  await guarded(async () => {
    await api("/api/admin/database/test", {
      method: "POST",
      body: JSON.stringify(databasePayload()),
    });
    setToast("数据库连接测试通过");
  });
}

async function saveDatabase() {
  await guarded(async () => {
    const data = await api<{ database: any; config: any }>("/api/admin/database/save", {
      method: "POST",
      body: JSON.stringify(databasePayload()),
    });
    database.value = data.database;
    databaseConfig.value = data.config;
    databaseFormEditing.value = false;
    applyDatabaseConfig(data.config || {}, data.database || {});
    databaseConfigCollapsed.value = true;
    setToast("数据库配置已测试、保存并热切换");
    await loadDatabase({ previewLegacy: false, preserveForm: true });
  });
}

async function syncDatabase(direction: "import" | "sync") {
  await guarded(async () => {
    const payload = { direction: direction === "import" ? "sqlite_to_remote" : "remote_to_sqlite" };
    const data = await api<{ jobId: number; job: any }>("/api/admin/database/sync/jobs", { method: "POST", body: JSON.stringify(payload) });
    databaseSyncJob.value = data.job;
    databaseLastSync.value = data.job;
    databaseSyncOutput.value = data.job?.output || [];
    setToast("数据库同步任务已启动");
    await pollDatabaseSyncJob(data.jobId || data.job?.id);
    await Promise.all([loadDatabase({ previewLegacy: false, preserveForm: true }), loadSystemLogs()]);
  });
}

async function loadDatabaseSyncLatest() {
  const data = await api<{ job: any | null }>("/api/admin/database/sync/jobs/latest");
  if (data.job) {
    databaseSyncJob.value = data.job;
    databaseLastSync.value = data.job;
    databaseSyncOutput.value = data.job.output || [];
  }
}

async function pollDatabaseSyncJob(id: number) {
  if (!id) return;
  for (let index = 0; index < 120; index += 1) {
    const data = await api<{ job: any }>(`/api/admin/database/sync/jobs/${id}`);
    databaseSyncJob.value = data.job;
    databaseLastSync.value = data.job;
    databaseSyncOutput.value = data.job?.output || [];
    if (!data.job || data.job.status !== "running") {
      if (data.job?.status === "failed") setToast(data.job.errors?.[0] || "数据库同步失败", "error");
      else if (data.job?.status === "skipped") setToast(data.job.warnings?.[0] || "数据库同步已跳过", "warn");
      else setToast("数据库同步已完成");
      return;
    }
    await delay(1000);
  }
  setToast("数据库同步仍在执行，输出会继续随状态刷新", "warn");
}

function editDatabaseConfig() {
  databaseFormEditing.value = true;
  databaseConfigCollapsed.value = false;
}

function markDatabaseFormEditing() {
  databaseFormEditing.value = true;
}

async function reloadDatabaseConfig() {
  databaseFormEditing.value = false;
  await guarded(async () => {
    await loadDatabase({ previewLegacy: false });
    setToast("数据库配置已从服务端重新读取");
  });
}

function databaseConfigSummary() {
  const config = databaseConfig.value || {};
  if (databaseForm.provider === "mysql") {
    const host = config.mysqlHost || databaseForm.mysqlHost || "127.0.0.1";
    const port = config.mysqlPort || databaseForm.mysqlPort || 3306;
    const databaseName = config.mysqlDatabase || databaseForm.mysqlDatabase || "-";
    const user = config.mysqlUser || databaseForm.mysqlUser || "-";
    return `${host}:${port} / ${databaseName} / ${user}${config.hasPassword ? " / 已保存密码" : ""}`;
  }
  return config.sqlitePath || databaseForm.sqlitePath || "使用默认 SQLite 路径";
}

async function loadMigrationStatus() {
  const data = await api<{ migration: any }>("/api/admin/system/migration");
  migrationStatus.value = data.migration || null;
}

async function loadBranding() {
  const data = await api<{ branding: any }>("/api/admin/system/branding");
  Object.assign(branding, {
    siteIconUrl: data.branding?.siteIconUrl || branding.siteIconUrl,
    developerAvatarUrl: data.branding?.developerAvatarUrl || branding.developerAvatarUrl,
    developerName: data.branding?.developerName || "YMhut",
    feedbackEmail: data.branding?.feedbackEmail || "support@ymhut.cn",
  });
}

async function saveBranding() {
  await guarded(async () => {
    const data = await api<{ branding: any }>("/api/admin/system/branding", {
      method: "POST",
      body: JSON.stringify({
        siteIconUrl: branding.siteIconUrl,
        developerAvatarUrl: branding.developerAvatarUrl,
        developerName: branding.developerName,
        feedbackEmail: branding.feedbackEmail,
      }),
    });
    Object.assign(branding, data.branding || {});
    if (!mailConfig.developerAddress) mailConfig.developerAddress = branding.feedbackEmail;
    setToast("站点品牌信息已保存");
  });
}

async function loadMailConfig(options: LoadMailOptions = {}) {
  const data = await api<{ config: any }>("/api/admin/system/mail/config");
  if (!options.preserveForm || !mailConfigEditing.value) {
    Object.assign(mailConfig, {
      host: data.config?.host || "",
      port: Number(data.config?.port || 465),
      secure: data.config?.secure || "ssl",
      username: data.config?.username || "",
      password: "",
      fromAddress: data.config?.fromAddress || "",
      fromName: data.config?.fromName || "YMhut Box Feedback",
      developerAddress: data.config?.developerAddress || "",
      timeoutSeconds: Number(data.config?.timeoutSeconds || 20),
      hasPassword: Boolean(data.config?.hasPassword),
      configured: Boolean(data.config?.configured),
    });
    mailConfigEditing.value = false;
  }
}

function mailPayload() {
  return {
    host: mailConfig.host,
    port: Number(mailConfig.port || 465),
    secure: mailConfig.secure,
    username: mailConfig.username,
    password: mailConfig.password,
    from_address: mailConfig.fromAddress,
    from_name: mailConfig.fromName,
    developer_address: mailConfig.developerAddress,
    timeout_seconds: Number(mailConfig.timeoutSeconds || 20),
  };
}

async function saveMailConfig() {
  await guarded(async () => {
    const data = await api<{ config: any }>("/api/admin/system/mail/config", { method: "POST", body: JSON.stringify(mailPayload()) });
    mailConfigEditing.value = false;
    Object.assign(mailConfig, { ...data.config, password: "" });
    setToast("邮件通知配置已保存");
  });
}

async function testMail() {
  await guarded(async () => {
    await api("/api/admin/system/mail/test", { method: "POST", body: "{}" });
    setToast("测试邮件已发送");
    await loadMailConfig({ preserveForm: true });
  });
}

function markMailConfigEditing() {
  mailConfigEditing.value = true;
}

async function reloadMailConfig() {
  mailConfigEditing.value = false;
  await guarded(async () => {
    await loadMailConfig();
    setToast("邮件配置已从服务端重新读取");
  });
}

async function retryFeedbackMail() {
  if (!selectedFeedback.value) return;
  await guarded(async () => {
    await api(`/api/admin/feedbacks/${encodeURIComponent(selectedFeedback.value.code)}/mail/retry`, { method: "POST", body: "{}" });
    setToast("反馈邮件已重新发送");
    await openFeedback(selectedFeedback.value);
    await loadFeedbacks();
  });
}

async function previewLegacySync() {
  legacySyncMode.value = "preview";
  legacySync.value = await api("/api/admin/sync/legacy/preview").catch((error) => ({ ok: false, errors: [String(error)] }));
}

async function runLegacySync() {
  await guarded(async () => {
    legacySyncMode.value = "run";
    legacySync.value = await api("/api/admin/sync/legacy/run", { method: "POST", body: "{}" });
    setToast("旧项目同步已完成");
    await Promise.all([loadDatabase({ previewLegacy: false, preserveForm: true }), loadReleases().catch(() => undefined), loadSources().catch(() => undefined)]);
  });
}

async function loadHealth() {
  healthSnapshot.value = await api("/api/admin/system/health");
}

async function loadAudit() {
  const params = new URLSearchParams({
    page: String(auditPage.page || 1),
    perPage: String(auditPage.perPage || 35),
  });
  if (auditPage.q) params.set("q", auditPage.q);
  if (auditPage.type) params.set("type", auditPage.type);
  if (auditPage.target) params.set("target", auditPage.target);
  const data = await api<{ items: any[]; page?: any }>(`/api/admin/system/audit?${params}`);
  const page = data.page || { items: data.items || [], total: data.items?.length || 0, page: auditPage.page, perPage: auditPage.perPage };
  auditLogs.value = page.items || [];
  Object.assign(auditPage, {
    items: page.items || [],
    total: Number(page.total || 0),
    page: Number(page.page || auditPage.page || 1),
    perPage: Number(page.perPage || auditPage.perPage || 35),
  });
}

function setAuditPage(page: number) {
  auditPage.page = Math.max(1, page);
  void loadAudit();
}

function selectAuditLog(item: any) {
  auditPage.selected = item;
}

async function loadSystemLogs() {
  const params = new URLSearchParams({
    page: String(systemLogPage.page || 1),
    perPage: String(systemLogPage.perPage || 35),
  });
  if (systemLogPage.q) params.set("q", systemLogPage.q);
  if (systemLogPage.category) params.set("category", systemLogPage.category);
  const data = await api<{ items: any[]; page?: any }>(`/api/admin/system/logs?${params}`);
  const page = data.page || { items: data.items || [], total: data.items?.length || 0, page: systemLogPage.page, perPage: systemLogPage.perPage };
  Object.assign(systemLogPage, {
    items: page.items || [],
    total: Number(page.total || 0),
    page: Number(page.page || systemLogPage.page || 1),
    perPage: Number(page.perPage || systemLogPage.perPage || 35),
  });
}

function setSystemLogPage(page: number) {
  systemLogPage.page = Math.max(1, page);
  void loadSystemLogs();
}

function selectSystemLog(item: any) {
  systemLogPage.selected = item;
}

async function changePassword() {
  await guarded(async () => {
    const data = await api<{ isDefaultPassword: boolean; warning?: string }>("/api/admin/auth/password", { method: "POST", body: JSON.stringify(passwordForm) });
    passwordForm.currentPassword = "";
    passwordForm.newPassword = "";
    if (authBootstrap.value) authBootstrap.value.isDefaultPassword = data.isDefaultPassword;
    setToast(data.warning || "后台密码已修改，登录页将不再提示默认密码", data.warning ? "warn" : "success");
  });
}

function endpointStatus(item: any) {
  return item.health?.status || item.lastStatus || "unknown";
}

function statusTone(status: string) {
  const value = String(status || "").toLowerCase();
  if (["ok", "online", "new", "sqlite", "mysql", "sent", "ready", "completed"].includes(value)) return "good";
  if (["redirected", "degraded", "pending", "processing", "queued", "missing", "skipped", "running", "normal"].includes(value)) return "warn";
  if (["error", "failed", "closed", "offline", "urgent", "high", "blocking", "major"].includes(value)) return "bad";
  return "neutral";
}

function objectEntries(value: Record<string, number>) {
  return Object.entries(value || {}).map(([name, item]) => ({ name: labelStatus(name), value: item || 0 }));
}

function labelStatus(value: string) {
  const labels: Record<string, string> = {
    ok: "正常",
    redirected: "重定向健康",
    error: "错误",
    degraded: "降级",
    unknown: "未知",
    new: "新建",
    processing: "处理中",
    closed: "已关闭",
    pending: "待发送",
    sent: "已发送",
    skipped: "已跳过",
    running: "执行中",
    completed: "已完成",
    failed: "失败",
  };
  return labels[value] || value || "未知";
}

function labelPriority(value: string) {
  const labels: Record<string, string> = {
    low: "低",
    minor: "低",
    normal: "普通",
    medium: "普通",
    high: "高",
    major: "高",
    urgent: "紧急",
    blocking: "紧急",
  };
  return labels[String(value || "").toLowerCase()] || value || "普通";
}

function auditTypeLabel(value: string) {
  const labels: Record<string, string> = {
    "auth.login": "管理员登录",
    "auth.password_changed": "修改后台密码",
    "feedback.created": "客户端提交反馈",
    "feedback.updated": "更新反馈工单",
    "legacy_json.saved": "保存兼容 JSON",
    "legacy_json.restored": "恢复兼容 JSON",
    "legacy_json.seeded": "导入 JSON 基板",
    "release_notice.saved": "保存版本日志",
    "release.package_uploaded": "上传发布包",
    "legacy.sync": "旧项目同步",
  };
  return labels[value] || value || "未知操作";
}

function auditMessage(item: any) {
  const message = String(item?.message || "");
  const legacy: Record<string, string> = {
    "Admin login": "管理员登录",
    "Admin password changed": "后台密码已修改",
    "Legacy JSON saved": "兼容 JSON 已保存",
    "Legacy JSON restored": "兼容 JSON 已恢复",
    "Release notice saved": "版本日志已保存",
    "Feedback updated": "反馈工单已更新",
  };
  return legacy[message] || message || auditTypeLabel(item?.type);
}

function databaseSyncDirectionLabel(value: string) {
  if (value === "sqlite_to_remote") return "SQLite -> MySQL";
  if (value === "remote_to_sqlite") return "MySQL -> SQLite";
  return value || "-";
}

function databaseSyncStatusLabel(value: string) {
  const labels: Record<string, string> = {
    completed: "已完成",
    skipped: "已跳过",
    running: "执行中",
    failed: "失败",
  };
  return labels[String(value || "").toLowerCase()] || value || "-";
}

function databaseSyncTableCount(result: any) {
  const tables = result?.tables || {};
  return Object.values(tables).reduce((total: number, value: any) => total + Number(value || 0), 0);
}

function formatHealthOutput(item: any) {
  const raw = String(item?.error || item?.lastError || "").trim();
  if (!raw) return "-";
  try {
    const meta = JSON.parse(raw);
    const parts = [
      meta.finalStatus ? `状态 ${meta.finalStatus}` : "",
      meta.finalUrl ? `最终 URL ${meta.finalUrl}` : "",
      meta.resolvedUrl ? `媒体 ${meta.resolvedUrl}` : "",
      meta.mediaType ? `类型 ${meta.mediaType}` : "",
      meta.error ? `错误 ${meta.error}` : "",
    ].filter(Boolean);
    return parts.length ? parts.join(" / ") : raw;
  } catch {
    return raw;
  }
}

function formatBytes(value: number) {
  if (!Number.isFinite(value) || value <= 0) return "0 B";
  const units = ["B", "KB", "MB", "GB"];
  let next = value;
  let index = 0;
  while (next >= 1024 && index < units.length - 1) {
    next /= 1024;
    index += 1;
  }
  return `${next.toFixed(index === 0 ? 0 : 1)} ${units[index]}`;
}

function timeLabel(value: string) {
  if (!value) return "-";
  return value.length > 10 ? value.slice(11, 19) : value;
}

function heartbeatTimeValue(value: string) {
  if (!value) return Date.now();
  const parsed = Date.parse(value);
  return Number.isFinite(parsed) ? parsed : value;
}

function pretty(value: any) {
  return JSON.stringify(value || {}, null, 2);
}

function clone<T>(value: T): T {
  return JSON.parse(JSON.stringify(value ?? null));
}

function parseJSONSafe(value: string, fallback: any) {
  try {
    return JSON.parse(value || "{}");
  } catch {
    return clone(fallback || {});
  }
}

function findByID(list: any, id: string) {
  if (!Array.isArray(list)) return null;
  return list.find((item) => item?.id === id) || null;
}

function splitList(value: string) {
  return String(value || "")
    .split(",")
    .map((item) => item.trim())
    .filter(Boolean);
}

function delay(ms: number) {
  return new Promise((resolve) => window.setTimeout(resolve, ms));
}

onMounted(() => {
  localStorage.removeItem("ymhut.csrf");
  void load();
  refreshTimer = window.setInterval(() => {
    if (!autoRefreshPaused.value && currentPath.value === "/admin/dashboard" && csrf.value) void loadDashboard();
  }, 15000);
  systemRefreshTimer = window.setInterval(() => {
    if (!autoRefreshPaused.value && currentPath.value === "/admin/system" && csrf.value) void loadSystem({ preserveForms: true });
  }, 60000);
});

watch(currentPath, () => {
  void load();
});

onUnmounted(() => {
  if (refreshTimer) window.clearInterval(refreshTimer);
  if (systemRefreshTimer) window.clearInterval(systemRefreshTimer);
  events?.close();
  events = null;
});

function connectAdminEvents() {
  if (!csrf.value || events) return;
  events = new EventSource("/api/admin/events", { withCredentials: true });
  const refreshCurrent = () => {
    if (autoRefreshPaused.value) return;
    if (currentPath.value === "/admin/dashboard") void Promise.all([loadDashboard(), loadSourceCheckJobs().catch(() => undefined)]);
    if (currentPath.value === "/admin/sources") void Promise.all([loadSources(), loadSourceCheckJobs().catch(() => undefined)]);
    if (currentPath.value === "/admin/endpoints") void loadEndpoints();
    if (currentPath.value === "/admin/system") void loadSystem({ preserveForms: true });
  };
  for (const name of ["source_check.item", "source_check.progress", "source_check.completed", "heartbeat"]) {
    events.addEventListener(name, refreshCurrent);
  }
  events.onerror = () => {
    events?.close();
    events = null;
    window.setTimeout(connectAdminEvents, 5000);
  };
}
</script>

<template>
  <Teleport to="body">
    <div v-if="toast" :class="['toast', toast.type]">{{ toast.message }}</div>
  </Teleport>

  <main v-if="currentPath === '/admin/login'" class="login-shell">
    <section class="login-panel">
      <div>
        <p class="eyebrow">YMhut Unified Management</p>
        <h1>后台登录</h1>
        <p class="muted">验证码和密码都由服务端校验，登录后写操作继续要求 CSRF Token。</p>
      </div>
      <p v-if="authBootstrap?.isDefaultPassword" class="alert-line">
        当前使用默认账号：{{ authBootstrap.defaultUsername || "admin" }} / {{ authBootstrap.defaultPassword || "admin" }}
      </p>
      <form class="form-stack" @submit.prevent="login">
        <label>账号<input v-model="loginForm.username" autocomplete="username" /></label>
        <label>密码<input v-model="loginForm.password" type="password" autocomplete="current-password" /></label>
        <label>
          验证码
          <div class="captcha-row">
            <input v-model="loginForm.captcha" />
            <button class="captcha-button" type="button" title="刷新验证码" @click="loadCaptcha">
              <img v-if="captcha?.image" :src="captcha.image" alt="验证码" />
              <span v-else>刷新</span>
            </button>
          </div>
        </label>
        <button class="btn primary full" type="submit">登录</button>
      </form>
    </section>
  </main>

  <main v-else class="app-shell">
    <aside class="sidebar">
      <div class="brand">
        <span class="brand-mark">
          <img v-if="branding.siteIconUrl" :src="branding.siteIconUrl" alt="YMhut" />
          <ShieldCheck v-else :size="22" />
        </span>
        <div><strong>{{ branding.developerName || "YMhut" }}</strong><small>统一管理台</small></div>
      </div>
      <nav class="nav-groups">
        <section v-for="group in navGroups" :key="group.label" class="nav-group">
          <p>{{ group.label }}</p>
          <button
            v-for="item in group.items"
            :key="item.path"
            :class="{ active: currentPath === item.path }"
            @click="navigate(item.path)"
          >
            <component :is="item.icon" :size="17" />
            <span>{{ item.label }}</span>
          </button>
        </section>
      </nav>
      <button class="logout" @click="logout"><LogOut :size="16" />退出</button>
    </aside>

    <section class="workspace">
      <header class="topbar">
        <div>
          <p class="eyebrow">update.ymhut.cn</p>
          <h1>{{ pageMeta.label }}</h1>
          <p class="muted">{{ pageMeta.description }}</p>
        </div>
        <div class="top-actions">
          <span v-if="loading" class="badge warn">加载中</span>
          <button class="btn ghost" @click="load"><RefreshCw :size="16" />刷新</button>
        </div>
      </header>
      <DashboardView v-if="currentPath === '/admin/dashboard'" :ctx="viewContext" />
      <FeedbacksView v-else-if="currentPath === '/admin/feedbacks'" :ctx="viewContext" />
      <ReleasesView v-else-if="currentPath === '/admin/releases'" :ctx="viewContext" />
      <LegacyJsonView v-else-if="activeLegacyName" :ctx="viewContext" />
      <SourcesView v-else-if="currentPath === '/admin/sources'" :ctx="viewContext" />
      <EndpointsView v-else-if="currentPath === '/admin/endpoints'" :ctx="viewContext" />
      <AuditLogView v-else-if="currentPath === '/admin/audit'" :ctx="viewContext" />
      <SystemView v-else-if="currentPath === '/admin/system'" :ctx="viewContext" />
    </section>
  </main>
</template>
