/* PRESERVATION-SOURCE-BEGIN app.js:1-1206 */
const {
  clamp,
  displayCompactDate,
  escapeHtml,
  formatNumber,
  formatTimestamp,
  localDateString,
  number,
  parseLocalDate,
  todayString,
} = window.XiaobaiUI;

const {
  emptyStateHtml,
  renderEmptyState,
} = window.XiaobaiComponents;

const HEART_BREATH_INHALE_MS = 3_000;
const HEART_BREATH_HOLD_MS = 2_000;
const HEART_BREATH_EXHALE_MS = 4_000;
const HEART_BREATH_PREPARE_MS = 1_000;
const HEART_BREATH_CYCLE_MS = HEART_BREATH_INHALE_MS + HEART_BREATH_HOLD_MS + HEART_BREATH_EXHALE_MS;
const HEART_BREATH_ACTIVE_MS = HEART_BREATH_CYCLE_MS * 5;
const HEART_BREATH_TOTAL_MS = HEART_BREATH_PREPARE_MS + HEART_BREATH_ACTIVE_MS;
const THEME_STORAGE_KEY = "xiaobaiTheme";
let activeThemeTransition = null;
let themeSwitchSequence = 0;

const state = window.XiaobaiState.create({
  session: {
    user: null,
    csrfToken: "",
    authMode: "login",
    started: false,
    activeView: "sentimentCycleView",
    dashboardLoading: false,
    dashboardRequestSequence: 0,
    dashboardRequestDate: "",
    adminModels: [],
    globalSearchResults: [],
    globalSearchActiveIndex: -1,
    globalSearchRequestSequence: 0,
  },
  market: {
    dashboard: null,
    filter: "all",
    query: "",
    sortKey: "streak",
    sortDirection: "desc",
    brokenQuery: "",
    brokenSortKey: "",
    brokenSortDirection: "desc",
    downQuery: "",
    downSortKey: "",
    downSortDirection: "asc",
    yesterdayFilter: "all",
    yesterdayQuery: "",
    yesterdaySortKey: "",
    yesterdaySortDirection: "desc",
    dragonTiger: null,
    dragonViewMode: "daily",
    dragonFilter: "all",
    dragonQuery: "",
    selectedDragonTraderId: "",
    hotMoneyProfiles: null,
    hotMoneyProfileQuery: "",
    selectedHotMoneyProfileId: "",
    rotationHistory: null,
    rotationHistoryKey: "",
    rotationSelectedSector: "",
    rotationSelectedDate: "",
    rotationMembers: null,
    rotationMembersKey: "",
    rotationMembersLoading: false,
    rotationOrder: localStorage.getItem("xiaobaiRotationOrder") === "latest" ? "latest" : "oldest",
    rotationLoading: false,
    auctionData: null,
    auctionDataset: "focus",
    auctionFilter: "all",
    auctionQuery: "",
    auctionSortKey: "attention_score",
    auctionSortDirection: "desc",
    auctionLoading: false,
    auctionTimer: null,
    themeLibrary: null,
    themeQuery: "",
    selectedThemeCode: "",
    themeDetail: null,
    themeLoading: false,
    popularityData: null,
    popularitySource: "combined",
    popularityQuery: "",
    popularityLoading: false,
    expandedLadderLevels: new Set(),
    ladderSortMode: "time",
    sentimentHistory: null,
    sentimentRange: 20,
    sentimentHistoryKey: "",
    sentimentLoading: false,
  },
  details: {
    stockDetail: null,
    activeStock: null,
    stockDetailChartMode: "daily",
    stockDetailIntraday: null,
    stockDetailRequestSequence: 0,
    entityDetailItem: null,
    entityDetailPayload: null,
    entityDetailChartMode: "daily",
    entityDetailIntraday: null,
    entityDetailRequestSequence: 0,
    stockPreviewCode: "",
    stockPreviewType: "stock",
    stockPreviewItem: null,
    stockPreviewPayload: null,
    stockPreviewChart: "daily",
    stockPreviewFallback: null,
    initialStockOpened: false,
  },
  review: {
    watchlist: [],
    watchlistSelection: null,
    watchlistSearchResults: [],
    watchlistSearchRequestSequence: 0,
    editingDailyNoteId: 0,
    notes: [],
    tradeEntries: [],
    tradeSummary: {},
    editingTradeId: 0,
    alerts: [],
    alertFilter: "all",
    alertUnreadCount: 0,
    assistantMessages: [],
    assistantLoading: false,
    assistantController: null,
  },
  screener: {
    screenerSetup: null,
    screenerSetupKey: "",
    screenerSetupRequestKey: "",
    screenerSetupPromise: null,
    selectedRegime: "",
    selectedStrategy: null,
    customStrategyDraft: null,
    screenerRunning: false,
    screenerRunningMode: "",
    screenerResults: { smart: null, curated: null, quant: null },
    screenerResultContexts: { smart: null, curated: null, quant: null },
    screenerResultStore: {},
    screenerTracking: null,
    screenerMode: ["smart", "curated", "quant"].includes(localStorage.getItem("xiaobaiScreenerMode"))
      ? localStorage.getItem("xiaobaiScreenerMode")
      : "smart",
    curatedCategory: "全部",
    curatedSchool: "全部",
    curatedQuery: "",
    curatedViewMode: localStorage.getItem("xiaobaiCuratedViewMode") === "grid" ? "grid" : "list",
    selectedCuratedStrategyId: 0,
    quantFilters: [],
    quantScores: [],
    screenerMobileView: "strategy",
  },
  mentor: {
    mentorSetup: null,
    selectedMentorId: "",
    mentorMessages: [],
    mentorLoading: false,
    mentorQuery: "",
    mentorGrade: "all",
    mentorDirectoryOpen: false,
    mentorSortMode: false,
    mentorSavingPreferences: false,
    mentorController: null,
  },
  heaven: {
    heavenSetup: null,
    heavenManualData: null,
    personalField: null,
    heavenPanel: "trend",
    heavenInterpretations: { trend: "", fortune: "", heart: "" },
    heavenReadingMode: "trend",
    heavenReadingTab: "current",
    heavenReadingHistory: { trend: [], fortune: [], heart: [] },
    heavenReadingSelectedId: 0,
    heavenReadingLoading: false,
    heavenReadingError: "",
    heartStage: "intro",
    heartTimer: null,
    heartSeconds: HEART_BREATH_TOTAL_MS / 1000,
    heartBreathingEndsAt: 0,
    heartLines: [],
    heartThrows: [],
    heartHexagram: null,
    heartCurtainTimer: null,
    heartStageToken: 0,
    heartRevealToken: 0,
    heavenPerformanceKey: "",
    heavenPerformancePanels: new Set(),
    heavenPerformanceActive: "",
    heavenRequestSequence: 0,
  },
});

window.XiaobaiAPI.configure({
  csrfToken: () => state.csrfToken,
  onUnauthorized: () => showAuthGate("登录状态已失效，请重新登录。"),
});

const applicationShell = window.XiaobaiShell.create({
  state,
  pages: window.XiaobaiPages,
  motionEnabled,
  animateRows,
  refreshIcons,
  tradeDate: () => displayCompactDate(
    state.dashboard?.meta?.trade_date || document.querySelector("#tradeDate")?.value || "",
  ),
  onNavigate: (viewId) => openView(viewId),
  onNavigationSync: () => toggleAccountDropdown(false),
});

const pageModules = window.XiaobaiPageModules.create({
  pages: window.XiaobaiPages,
  actions: {
    closeTransientUi: () => closeStockPreview(),
    applyAccess: () => applyMembershipAccess(),
    clearAuction: () => clearAuctionTimer(),
    stopHeaven: () => {
      stopQiFieldCanvas();
      stopHeartDust();
      cancelHeavenPerformance();
    },
    loadSentiment: () => loadSentimentHistory(),
    loadRotation: () => loadRotationHistory(),
    loadAuction: () => loadAuctionCenter(),
    loadThemes: () => loadThemeLibrary(),
    loadPopularity: () => loadPopularity(),
    loadDragonTiger: () => loadDragonTiger(),
    loadReview: () => loadReviewWorkspace(),
    loadScreener: () => {
      if (hasMemberAccess() && state.dashboard) loadScreenerSetup();
    },
    loadMentor: () => {
      if (hasMemberAccess()) loadMentorSetup();
    },
    loadHeaven: () => {
      if (!hasMemberAccess()) return;
      loadHeavenSetup(false, "", document.querySelector("#heavenStockInput").value.trim());
    },
  },
});

const elements = {
  tradeDate: document.querySelector("#tradeDate"),
  loading: document.querySelector("#loadingOverlay"),
  toast: document.querySelector("#toast"),
  stockDialog: document.querySelector("#stockDialog"),
  tradeLogDialog: document.querySelector("#tradeLogDialog"),
  watchlistDialog: document.querySelector("#watchlistDialog"),
  alertsDialog: document.querySelector("#alertsDialog"),
  assistantDialog: document.querySelector("#assistantDialog"),
  heavenReadingDialog: document.querySelector("#heavenReadingDialog"),
  globalSearchDialog: document.querySelector("#globalSearchDialog"),
  globalSearchInput: document.querySelector("#globalSearchInput"),
  globalSearchResults: document.querySelector("#globalSearchResults"),
  entityDetailDialog: document.querySelector("#entityDetailDialog"),
  entityDetailChart: document.querySelector("#entityDetailChart"),
  settingsDialog: document.querySelector("#settingsDialog"),
  adminDialog: document.querySelector("#adminDialog"),
  priceChart: document.querySelector("#priceChart"),
  stockPreview: document.querySelector("#stockPreview"),
  stockPreviewBackdrop: document.querySelector("#stockPreviewBackdrop"),
  stockPreviewChart: document.querySelector("#stockPreviewChart"),
};

function openModalDialog(dialog) {
  applicationShell.openModalDialog(dialog);
}

const metricAnimationFrames = new WeakMap();
const stockPreviewCache = new Map();
const STOCK_PREVIEW_DELAY = 380;
const STOCK_PREVIEW_CACHE_MS = 5 * 60 * 1000;
const LIVE_REFRESH_DEFAULT_MS = 10 * 1000;
let qiFieldAnimationFrame = 0;
let qiFieldSoloElement = "";
let heavenPerformanceToken = 0;
let heavenReadingAnimation = null;
let heartHoldTimer = null;
let heartHoldTriggered = false;
let heartHoldStartedAt = 0;
let heartHoldAnimationFrame = 0;
let heartCastingBusy = false;
let heartDustAnimationFrame = 0;
let heartDustParticles = [];
let heartIncenseAnimation = null;
const heartCoinRotations = [0, 0, 0];
let rowAnimationObserver = null;
let stockPreviewOpenTimer = null;
let stockPreviewCloseTimer = null;
let stockPreviewAbortController = null;
let stockPreviewAnchor = null;
let sentimentChartAnimationFrame = null;
let heavenResizeTimer = null;
let globalSearchTimer = null;
let watchlistSearchTimer = null;
let assistantRenderFrame = 0;

const heartSound = {
  enabled: false,
  context: null,
  ensure() {
    if (!this.context) {
      const AudioContextClass = window.AudioContext || window.webkitAudioContext;
      if (!AudioContextClass) return null;
      this.context = new AudioContextClass();
    }
    if (this.context.state === "suspended") this.context.resume();
    return this.context;
  },
  tone(frequency, duration, gain, type = "sine", delay = 0) {
    if (!this.enabled) return;
    const context = this.ensure();
    if (!context) return;
    const start = context.currentTime + delay;
    const oscillator = context.createOscillator();
    const volume = context.createGain();
    oscillator.type = type;
    oscillator.frequency.value = frequency;
    volume.gain.setValueAtTime(0.0001, start);
    volume.gain.linearRampToValueAtTime(gain, start + 0.015);
    volume.gain.exponentialRampToValueAtTime(0.0001, start + duration);
    oscillator.connect(volume).connect(context.destination);
    oscillator.start(start);
    oscillator.stop(start + duration + 0.05);
  },
  chime(frequency = 640) {
    this.tone(frequency, 4.8, 0.12);
    this.tone(frequency * 2.02, 3.6, 0.045);
    this.tone(frequency * 3.96, 2.2, 0.018);
  },
  coin(delay = 0) {
    this.tone(2350 + Math.random() * 260, 0.28, 0.055, "triangle", delay);
    this.tone(3250 + Math.random() * 260, 0.18, 0.025, "triangle", delay + 0.01);
  },
};

const HEART_WHISPERS = [
  ["应无所住，而生其心", 10, 12, 0],
  ["不是风动，不是幡动，仁者心动", 89, 8, 1],
  ["菩提本无树，明镜亦非台", 16, 52, 2],
  ["本来无一物，何处惹尘埃", 84, 54, 3],
  ["心外无物，心外无理", 22, 18, 4],
  ["知行合一", 78, 30, 5],
  ["此心光明，亦复何言", 90, 60, 6],
];

window.addEventListener("resize", () => {
  clearTimeout(heavenResizeTimer);
  heavenResizeTimer = setTimeout(() => {
    if (state.activeView !== "heavenView") return;
    if (state.heavenPanel === "fortune" && state.heavenSetup?.field) {
      renderQiFieldCanvas(state.heavenSetup.field.balance || [], { intro: false });
      drawQiUseConnections(false);
    }
    if (state.heavenPanel === "heart") startHeartDust();
  }, 120);
});

document.addEventListener("DOMContentLoaded", initialize);

function syncThemeControl() {
  const theme = document.documentElement.dataset.theme === "dark" ? "dark" : "light";
  const button = document.querySelector("#themeToggle");
  if (!button) return;
  const dark = theme === "dark";
  const label = dark ? "切换到日间模式" : "切换到夜间模式";
  button.title = label;
  button.setAttribute("aria-label", label);
  button.setAttribute("aria-pressed", String(dark));
  button.querySelector("i")?.setAttribute("data-lucide", dark ? "sun" : "moon");
}

function clearThemeTransitionEffects() {
  document.querySelectorAll(".row-enter, .row-pending, .view-entering").forEach((element) => {
    element.classList.remove("row-enter", "row-pending", "view-entering");
    element.style.removeProperty("--row-delay");
  });
}

function redrawThemeSensitiveVisuals() {
  if (!elements.stockPreview.hidden && state.stockPreviewPayload) {
    selectStockPreviewChart(state.stockPreviewChart);
  }
  if (elements.stockDialog.open) {
    if (state.stockDetailChartMode === "intraday" && state.stockDetailIntraday?.points?.length) {
      drawIntradayCanvas(
        elements.priceChart,
        state.stockDetailIntraday.points,
        [],
        state.stockDetailIntraday.meta?.previous_close,
      );
    } else if (state.stockDetail?.prices) drawPriceChart(state.stockDetail.prices);
  }
  if (elements.entityDetailDialog.open) {
    if (state.entityDetailChartMode === "intraday" && state.entityDetailIntraday?.points?.length) {
      drawIntradayCanvas(
        elements.entityDetailChart,
        state.entityDetailIntraday.points,
        [],
        state.entityDetailIntraday.meta?.previous_close,
      );
    } else if (state.entityDetailPayload?.series) {
      drawEntityDetailChart(state.entityDetailPayload.series);
    }
  }
  if (state.activeView === "sentimentCycleView" && state.sentimentHistory) {
    drawSentimentTrendChart(state.sentimentHistory.rows || []);
  }
  if (state.activeView === "heavenView") {
    if (state.heavenPanel === "fortune" && state.heavenSetup?.field) {
      renderQiFieldCanvas(state.heavenSetup.field.balance || [], { intro: false });
      drawQiUseConnections(false);
    }
    if (state.heavenPanel === "heart") startHeartDust();
  }
}

function commitTheme(normalized, persist) {
  document.documentElement.dataset.theme = normalized;
  document.documentElement.style.colorScheme = normalized;
  if (persist) {
    try {
      localStorage.setItem(THEME_STORAGE_KEY, normalized);
    } catch (_error) {
      // The selected theme still applies for the current page when storage is unavailable.
    }
  }
  syncThemeControl();
  refreshIcons();
  redrawThemeSensitiveVisuals();
}

function applyTheme(theme, persist = true) {
  const normalized = theme === "dark" ? "dark" : "light";
  const root = document.documentElement;
  if (root.dataset.theme === normalized) {
    commitTheme(normalized, persist);
    return;
  }
  const sequence = ++themeSwitchSequence;
  activeThemeTransition?.skipTransition?.();
  clearThemeTransitionEffects();
  root.classList.add("theme-switching");

  const update = () => commitTheme(normalized, persist);
  const finish = () => {
    if (sequence !== themeSwitchSequence) return;
    clearThemeTransitionEffects();
    root.classList.remove("theme-switching");
    activeThemeTransition = null;
  };
  const reducedMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
  if (!reducedMotion && typeof document.startViewTransition === "function") {
    activeThemeTransition = document.startViewTransition(update);
    activeThemeTransition.finished.then(finish, finish);
    return;
  }
  update();
  requestAnimationFrame(() => requestAnimationFrame(finish));
}

function toggleTheme() {
  applyTheme(document.documentElement.dataset.theme === "dark" ? "light" : "dark");
}

async function initialize() {
  syncThemeControl();
  refreshIcons();
  applicationShell.initialize();
  elements.tradeDate.value = todayString();
  const initialUrl = new URL(window.location.href);
  if (initialUrl.searchParams.has("date")) {
    initialUrl.searchParams.delete("date");
    history.replaceState(null, "", initialUrl);
  }
  elements.tradeDate.max = todayString();
  document.querySelector("#journalDate").value = elements.tradeDate.value;
  document.querySelector("#journalDate").max = todayString();
  document.querySelector("#tradeLogDate").value = elements.tradeDate.value;
  document.querySelector("#tradeLogDate").max = todayString();
  document.querySelector("#backfillStart").value = todayString();
  document.querySelector("#backfillEnd").value = todayString();
  document.querySelector("#backfillStart").max = todayString();
  document.querySelector("#backfillEnd").max = todayString();
  document.querySelector("#qiObservationDate").value = elements.tradeDate.value;
  document.querySelector("#qiObservationDate").max = todayString();
  document.querySelector("#accountBirthDate").max = todayString();
  document.querySelector("#alertDate").value = todayString();
  bindEvents();
  try {
    const session = await apiRequest("/api/auth/me");
    if (!session.authenticated) {
      if (session.registration_required) selectAuthMode("register");
      showAuthGate();
      return;
    }
    await applyAuthenticatedSession(session);
  } catch (error) {
    showAuthGate(error.message || "无法连接本地服务");
  }
}

async function startAuthenticatedApp() {
  if (state.started) return;
  state.started = true;
  const searchParams = new URLSearchParams(window.location.search);
  const requestedHeavenPanel = searchParams.get("heaven");
  if (["trend", "fortune", "heart"].includes(requestedHeavenPanel)) {
    state.heavenPanel = requestedHeavenPanel;
  }
  const requestedView = window.XiaobaiPages.resolve(searchParams.get("view"));
  if (
    requestedView
    && window.XiaobaiPages.has(requestedView)
    && document.getElementById(requestedView)?.classList.contains("workspace-view")
  ) {
    openView(requestedView, false);
    if (requestedView !== searchParams.get("view")) {
      const url = new URL(window.location.href);
      url.searchParams.set("view", requestedView);
      history.replaceState(null, "", url);
    }
  }
  loadDashboard();
  loadAlerts();
  if (new URLSearchParams(window.location.search).get("settings") === "1") {
    setTimeout(openSettings, 0);
  }
}

function selectAuthMode(mode) {
  state.authMode = mode === "register" ? "register" : "login";
  document.querySelectorAll("[data-auth-mode]").forEach((button) => {
    button.classList.toggle("active", button.dataset.authMode === state.authMode);
  });
  const registering = state.authMode === "register";
  document.querySelector("#authConfirmField").hidden = !registering;
  document.querySelector("#authPasswordConfirm").required = registering;
  document.querySelector("#authPassword").autocomplete = registering ? "new-password" : "current-password";
  document.querySelector("#authSubmitButton").textContent = registering ? "注册并进入" : "登录";
  document.querySelector("#authError").hidden = true;
}

async function submitAuthForm(event) {
  event.preventDefault();
  const username = document.querySelector("#authUsername").value.trim();
  const password = document.querySelector("#authPassword").value;
  const errorElement = document.querySelector("#authError");
  if (state.authMode === "register" && password !== document.querySelector("#authPasswordConfirm").value) {
    errorElement.textContent = "两次输入的密码不一致。";
    errorElement.hidden = false;
    return;
  }
  const button = document.querySelector("#authSubmitButton");
  button.disabled = true;
  try {
    const session = await apiRequest(`/api/auth/${state.authMode}`, "POST", { username, password });
    document.querySelector("#authForm").reset();
    await applyAuthenticatedSession(session);
  } catch (error) {
    errorElement.textContent = error.message || "账号操作失败";
    errorElement.hidden = false;
  } finally {
    button.disabled = false;
  }
}

async function applyAuthenticatedSession(session) {
  state.user = session.user;
  state.csrfToken = session.csrf_token || "";
  setText("accountName", session.user?.username || "账号");
  const isAdmin = session.user?.role === "admin";
  updateAccountIdentityBadges(session.user?.membership || {});
  document.querySelector("#settingsButton").hidden = !isAdmin;
  document.querySelector("#syncButton").hidden = !isAdmin;
  document.querySelector("#reasonForm").hidden = !isAdmin;
  document.querySelector("#sectorPhaseManager").hidden = !isAdmin;
  document.querySelector("#authGate").hidden = true;
  applyMembershipAccess();
  await startAuthenticatedApp();
}

function showAuthGate(message = "") {
  state.user = null;
  state.csrfToken = "";
  const gate = document.querySelector("#authGate");
  gate.hidden = false;
  const errorElement = document.querySelector("#authError");
  errorElement.textContent = message;
  errorElement.hidden = !message;
  document.querySelector("#authUsername").focus();
}

async function logoutAccount() {
  toggleAccountDropdown(false);
  try {
    await apiRequest("/api/auth/logout", "POST", {});
  } catch (error) {
    showToast(error.message || "退出失败");
    return;
  }
  window.location.reload();
}

function bindEvents() {
  document.querySelectorAll("[data-auth-mode]").forEach((button) => {
    button.addEventListener("click", () => selectAuthMode(button.dataset.authMode));
  });
  document.querySelector("#authForm").addEventListener("submit", submitAuthForm);
  document.querySelector("#refreshButton").addEventListener("click", async (event) => {
    const button = event.currentTarget;
    button.disabled = true;
    try {
      await loadDashboard(false, false, false);
    } finally {
      button.disabled = false;
    }
  });
  document.querySelector("#syncButton").addEventListener("click", startAdminRefresh);
  elements.tradeDate.addEventListener("change", () => {
    state.dashboardRequestSequence += 1;
    state.heavenRequestSequence += 1;
    state.heavenManualData = null;
    document.querySelector("#qiObservationDate").value = elements.tradeDate.value;
    loadDashboard();
  });
  document.querySelector("#prevDate").addEventListener("click", () => shiftDate(-1));
  document.querySelector("#nextDate").addEventListener("click", () => shiftDate(1));
  document.querySelector("#stockSearch").addEventListener("input", (event) => {
    state.query = event.target.value.trim().toLowerCase();
    renderLimitTable();
  });
  document.querySelectorAll("[data-table-search]").forEach((input) => {
    input.addEventListener("input", () => {
      const query = input.value.trim().toLowerCase();
      const body = document.querySelector(`#${CSS.escape(input.dataset.tableSearch)}`);
      body?.querySelectorAll("tr").forEach((row) => {
        row.hidden = Boolean(query) && !row.textContent.toLowerCase().includes(query);
      });
    });
  });

  document.querySelectorAll("[data-filter]").forEach((button) => {
    button.addEventListener("click", () => {
      document.querySelectorAll("[data-filter]").forEach((item) => item.classList.remove("active"));
      button.classList.add("active");
      state.filter = button.dataset.filter;
      renderLimitTable();
    });
  });

  document.querySelector("#globalSearchButton").addEventListener("click", openGlobalSearch);
  document.querySelector("#themeToggle").addEventListener("click", toggleTheme);
  document.querySelector("#alertButton").addEventListener("click", openAlerts);
  document.querySelector("#assistantButton").addEventListener("click", openReviewAssistant);
  document.querySelector("#closeAssistantDialog").addEventListener("click", () => elements.assistantDialog.close());
  document.querySelector("#assistantForm").addEventListener("submit", sendAssistantQuestion);
  document.querySelector("#stopAssistant").addEventListener("click", stopAssistantResponse);
  document.querySelector("#clearAssistantMessages").addEventListener("click", clearAssistantConversation);
  document.querySelectorAll("[data-assistant-prompt]").forEach((button) => {
    button.addEventListener("click", () => useAssistantPrompt(button.dataset.assistantPrompt));
  });
  document.querySelector("#closeAlertsDialog").addEventListener("click", () => elements.alertsDialog.close());
  document.querySelector("#alertForm").addEventListener("submit", saveAlert);
  document.querySelector("#markAllAlertsRead").addEventListener("click", markAllAlertsRead);
  document.querySelector("#alertList").addEventListener("click", handleAlertAction);
  document.querySelectorAll("[data-alert-filter]").forEach((button) => {
    button.addEventListener("click", () => selectAlertFilter(button.dataset.alertFilter));
  });
  document.querySelector("#closeGlobalSearch").addEventListener("click", closeGlobalSearch);
  document.querySelector("#closeEntityDetail").addEventListener("click", () => elements.entityDetailDialog.close());
  document.querySelectorAll("[data-entity-detail-chart]").forEach((button) => {
    button.addEventListener("click", () => selectEntityDetailChart(button.dataset.entityDetailChart));
  });
  elements.globalSearchDialog.addEventListener("click", (event) => {
    if (event.target === elements.globalSearchDialog) closeGlobalSearch();
  });
  elements.globalSearchInput.addEventListener("input", scheduleGlobalSearch);
  elements.globalSearchInput.addEventListener("keydown", handleGlobalSearchInputKeydown);
  elements.globalSearchResults.addEventListener("click", (event) => {
    const result = event.target.closest("[data-search-result-index]");
    if (result) openGlobalSearchResult(number(result.dataset.searchResultIndex));
  });
  document.addEventListener("click", (event) => {
    if (!event.target.closest(".account-menu-shell")) toggleAccountDropdown(false);
  });
  window.addEventListener("keydown", handleGlobalSearchShortcut);
  document.addEventListener("keydown", (event) => {
    if (event.key === "Escape") {
      toggleAccountDropdown(false, true);
      toggleMentorDirectory(false);
    }
    handleAccountMenuKeydown(event);
  });
  window.addEventListener("resize", () => {
    if (window.innerWidth > 720) toggleMentorDirectory(false);
    if (!elements.stockPreview.hidden) closeStockPreview();
    if (state.activeView === "dragonView") layoutDragonCards();
  });
  document.querySelectorAll("[data-open-account]").forEach((button) => {
    button.addEventListener("click", () => openSettings("membership"));
  });
  document.querySelectorAll("#limitTable th[data-sort]").forEach((header) => {
    header.addEventListener("click", () => changeSort(header.dataset.sort));
  });
  document.querySelector("#brokenSearch").addEventListener("input", (event) => {
    state.brokenQuery = event.target.value.trim().toLowerCase();
    renderBrokenTable(state.dashboard?.broken || []);
  });
  document.querySelectorAll("#brokenTable th[data-broken-sort]").forEach((header) => {
    header.addEventListener("click", () => changeBrokenSort(header.dataset.brokenSort));
  });
  document.querySelector("#downSearch").addEventListener("input", (event) => {
    state.downQuery = event.target.value.trim().toLowerCase();
    renderDownTable(state.dashboard?.down_limits || []);
  });
  document.querySelectorAll("#downTable th[data-down-sort]").forEach((header) => {
    header.addEventListener("click", () => changeDownSort(header.dataset.downSort));
  });
  document.querySelector("#yesterdaySearch").addEventListener("input", (event) => {
    state.yesterdayQuery = event.target.value.trim().toLowerCase();
    renderYesterdayTable(state.dashboard?.yesterday_limits || []);
  });
  document.querySelectorAll("[data-yesterday-filter]").forEach((button) => {
    button.addEventListener("click", () => {
      state.yesterdayFilter = button.dataset.yesterdayFilter;
      renderYesterdayTable(state.dashboard?.yesterday_limits || []);
    });
  });
  document.querySelectorAll("#yesterdayTable th[data-yesterday-sort]").forEach((header) => {
    header.addEventListener("click", () => changeYesterdaySort(header.dataset.yesterdaySort));
  });
  document.querySelectorAll("[data-ladder-sort]").forEach((button) => {
    button.addEventListener("click", () => {
      state.ladderSortMode = button.dataset.ladderSort === "open" ? "open" : "time";
      document.querySelectorAll("[data-ladder-sort]").forEach((item) => {
        const active = item === button;
        item.classList.toggle("active", active);
        item.setAttribute("aria-pressed", String(active));
      });
      renderLadderBoard(state.dashboard?.ladders || []);
    });
  });

  document.querySelector("#exportButton").addEventListener("click", exportStocks);
  document.querySelector("#brokenExportButton").addEventListener("click", exportBroken);
  document.querySelector("#downExportButton").addEventListener("click", exportDown);
  document.querySelector("#yesterdayExportButton").addEventListener("click", exportYesterday);
  document.querySelector("#ladderExportButton").addEventListener("click", exportLadder);
  document.querySelector("#rotationExportButton").addEventListener("click", exportRotation);
  document.querySelectorAll("[data-rotation-order]").forEach((button) => {
    button.addEventListener("click", () => {
      state.rotationOrder = button.dataset.rotationOrder === "latest" ? "latest" : "oldest";
      localStorage.setItem("xiaobaiRotationOrder", state.rotationOrder);
      renderRotationHistory();
    });
  });
  document.querySelector("#sentimentExportButton").addEventListener("click", exportSentimentHistory);
  document.querySelectorAll("[data-sentiment-range]").forEach((button) => {
    button.addEventListener("click", () => {
      state.sentimentRange = number(button.dataset.sentimentRange) || 20;
      document.querySelectorAll("[data-sentiment-range]").forEach((item) => {
        item.classList.toggle("active", item === button);
      });
      loadSentimentHistory(true);
    });
  });
  document.querySelector("#settingsButton").addEventListener("click", () => openAdminSettings());
  document.querySelector("#accountButton").addEventListener("click", (event) => {
    event.stopPropagation();
    toggleAccountDropdown();
  });
  document.querySelector("#accountVipBadge").addEventListener("click", () => openSettings("membership"));
  document.querySelectorAll("[data-account-panel]").forEach((button) => {
    button.addEventListener("click", () => openSettings(button.dataset.accountPanel));
  });
  document.querySelector("#switchAccountMenuButton").addEventListener("click", switchAccount);
  document.querySelector("#logoutMenuButton").addEventListener("click", logoutAccount);
  document.querySelector("#closeSettingsDialog").addEventListener("click", () => elements.settingsDialog.close());
  document.querySelector("#closeAdminDialog").addEventListener("click", () => elements.adminDialog.close());
  document.querySelector("#closeStockDialog").addEventListener("click", () => elements.stockDialog.close());
  document.querySelectorAll("[data-stock-detail-chart]").forEach((button) => {
    button.addEventListener("click", () => selectStockDetailChart(button.dataset.stockDetailChart));
  });
  document.querySelector("#closeStockPreview").addEventListener("click", closeStockPreview);
  elements.stockPreviewBackdrop.addEventListener("click", closeStockPreview);
  document.querySelector("#openStockDetailFromPreview").addEventListener("click", openStockDetailFromPreview);
  document.querySelectorAll("[data-preview-chart]").forEach((button) => {
    button.addEventListener("click", () => selectStockPreviewChart(button.dataset.previewChart));
  });
  elements.stockPreview.addEventListener("pointerenter", cancelStockPreviewClose);
  elements.stockPreview.addEventListener("pointerleave", scheduleStockPreviewClose);
  document.addEventListener("pointerover", handleStockPreviewPointerOver);
  document.addEventListener("pointerout", handleStockPreviewPointerOut);
  document.addEventListener("focusin", handleStockPreviewFocus);
  document.addEventListener("focusout", handleStockPreviewFocusOut);
  document.addEventListener("click", handleMobileStockPreviewClick, true);
  document.addEventListener("keydown", handleStockPreviewKeydown);
  document.addEventListener("scroll", repositionStockPreview, true);
  document.querySelector("#auctionRefreshButton").addEventListener("click", () => loadAuctionCenter(true));
  document.querySelector("#auctionExportButton").addEventListener("click", exportAuctionRows);
  document.querySelector("#auctionSearch").addEventListener("input", (event) => {
    state.auctionQuery = event.target.value.trim().toLocaleLowerCase("zh-CN");
    renderAuctionTable();
  });
  document.querySelectorAll("[data-auction-dataset]").forEach((button) => {
    button.addEventListener("click", () => {
      state.auctionDataset = button.dataset.auctionDataset || "focus";
      state.auctionFilter = "all";
      state.auctionSortKey = state.auctionDataset === "onePrice" ? "amount_million" : "attention_score";
      state.auctionSortDirection = "desc";
      document.querySelectorAll("[data-auction-dataset]").forEach((item) => {
        const active = item === button;
        item.classList.toggle("active", active);
        item.setAttribute("aria-selected", String(active));
      });
      document.querySelectorAll("[data-auction-filter]").forEach((item) => item.classList.toggle("active", item.dataset.auctionFilter === "all"));
      renderAuctionTable();
    });
  });
  document.querySelectorAll("[data-auction-filter]").forEach((button) => {
    button.addEventListener("click", () => {
      state.auctionFilter = button.dataset.auctionFilter || "all";
      document.querySelectorAll("[data-auction-filter]").forEach((item) => item.classList.toggle("active", item === button));
      renderAuctionTable();
    });
  });
  document.querySelector("#auctionTable").addEventListener("click", (event) => {
    const header = event.target.closest("th[data-auction-sort]");
    if (!header) return;
    const key = header.dataset.auctionSort;
    if (state.auctionSortKey === key) state.auctionSortDirection = state.auctionSortDirection === "asc" ? "desc" : "asc";
    else {
      state.auctionSortKey = key;
      state.auctionSortDirection = "desc";
    }
    renderAuctionTable();
  });
  document.querySelector("#openStrategyDrawerButton").addEventListener("click", openCustomStrategyDrawer);
  document.querySelector("#closeStrategyDrawerButton").addEventListener("click", () => document.querySelector("#strategyDrawer").close());
  document.querySelector("#strategyDrawer").addEventListener("click", (event) => {
    if (event.target === event.currentTarget) event.currentTarget.close();
  });
  document.querySelector("#themeRefreshButton").addEventListener("click", () => loadThemeLibrary(true));
  document.querySelector("#themeSearch").addEventListener("input", (event) => {
    state.themeQuery = event.target.value.trim().toLocaleLowerCase("zh-CN");
    renderThemeDirectory();
  });
  document.querySelector("#themeDirectory").addEventListener("click", (event) => {
    const button = event.target.closest("[data-theme-code]");
    if (button) selectTheme(button.dataset.themeCode);
  });
  document.querySelector("#popularityRefreshButton").addEventListener("click", () => loadPopularity(true));
  document.querySelector("#popularitySearch").addEventListener("input", (event) => {
    state.popularityQuery = event.target.value.trim().toLocaleLowerCase("zh-CN");
    renderPopularityTable();
  });
  document.querySelectorAll("[data-popularity-source]").forEach((button) => {
    button.addEventListener("click", () => {
      state.popularitySource = button.dataset.popularitySource || "combined";
      document.querySelectorAll("[data-popularity-source]").forEach((item) => {
        const active = item === button;
        item.classList.toggle("active", active);
        item.setAttribute("aria-selected", String(active));
      });
      renderPopularityTable();
    });
  });
  document.querySelector("#dragonRefreshButton").addEventListener("click", () => {
    if (state.dragonViewMode === "profiles") loadHotMoneyProfiles(true);
    else loadDragonTiger(true);
  });
  document.querySelector("#dragonEmptyRefreshButton").addEventListener("click", () => loadDragonTiger(true));
  document.querySelector("#dragonPreviousButton").addEventListener("click", () => shiftDate(-1));
  document.querySelector("#dragonExportButton").addEventListener("click", () => {
    if (state.dragonViewMode === "profiles") exportHotMoneyProfiles();
    else exportDragonTiger();
  });
  document.querySelectorAll("[data-dragon-view-mode]").forEach((button) => {
    button.addEventListener("click", () => selectDragonViewMode(button.dataset.dragonViewMode));
  });
  document.querySelector("#dragonSearch").addEventListener("input", (event) => {
    state.dragonQuery = event.target.value.trim().toLowerCase();
    renderDragonTraderList();
  });
  document.querySelectorAll("[data-dragon-filter]").forEach((button) => {
    button.addEventListener("click", () => {
      state.dragonFilter = button.dataset.dragonFilter;
      document.querySelectorAll("[data-dragon-filter]").forEach((item) => {
        item.classList.toggle("active", item === button);
      });
      renderDragonTraderList();
    });
  });
  document.querySelector("#hotMoneyProfileSearch").addEventListener("input", (event) => {
    state.hotMoneyProfileQuery = event.target.value.trim().toLocaleLowerCase("zh-CN");
    renderHotMoneyProfiles();
  });
  document.querySelector("#hotMoneyProfileList").addEventListener("click", (event) => {
    const button = event.target.closest("[data-hot-money-profile]");
    if (!button) return;
    state.selectedHotMoneyProfileId = button.dataset.hotMoneyProfile;
    renderHotMoneyProfiles();
  });
  document.querySelector("#journalForm").addEventListener("submit", saveJournal);
  document.querySelector("#journalDate").addEventListener("change", populateJournalForm);
  document.querySelector("#openWatchlistDialog").addEventListener("click", () => openWatchlistDialog());
  document.querySelector("#closeWatchlistDialog").addEventListener("click", closeWatchlistDialog);
  document.querySelector("#cancelWatchlistEdit").addEventListener("click", closeWatchlistDialog);
  document.querySelector("#changeWatchlistSelection").addEventListener("click", clearWatchlistSelection);
  document.querySelector("#watchlistSearchInput").addEventListener("input", scheduleWatchlistSearch);
  document.querySelector("#watchlistForm").addEventListener("submit", saveWatchlistFromDialog);
  document.querySelector("#watchlistSearchResults").addEventListener("click", handleWatchlistSearchResult);
  document.querySelector("#reviewHistoryToggle").addEventListener("click", (event) => {
    const panel = document.querySelector("#reviewHistoryPanel");
    const expanded = event.currentTarget.getAttribute("aria-expanded") === "true";
    event.currentTarget.setAttribute("aria-expanded", String(!expanded));
    event.currentTarget.querySelector("span").textContent = expanded ? "历史复盘" : "收起历史";
    panel.hidden = expanded;
    if (!expanded) panel.scrollIntoView({ behavior: "smooth", block: "nearest" });
  });
  document.querySelector("#openTradeLogDialog").addEventListener("click", openTradeLogDialog);
  document.querySelector("#closeTradeLogDialog").addEventListener("click", closeTradeLogDialog);
  document.querySelector("#tradeLogForm").addEventListener("submit", saveTradeLog);
  document.querySelector("#cancelTradeEdit").addEventListener("click", closeTradeLogDialog);
  elements.tradeLogDialog.addEventListener("close", resetTradeLogForm);
  document.querySelector("#tradeLogTableBody").addEventListener("click", handleTradeLogAction);
  document.querySelector("#stockNoteForm").addEventListener("submit", saveStockNote);
  document.querySelector("#watchStockButton").addEventListener("click", toggleActiveWatchlist);
  document.querySelector("#stockHeavenButton").addEventListener("click", openActiveStockInHeaven);
  document.querySelector("#stockReminderButton").addEventListener("click", openStockReminder);
  document.querySelector("#reasonForm").addEventListener("submit", saveReasonOverride);
  document.querySelector("#backfillButton").addEventListener("click", backfillData);
  document.querySelector("#openScreenerTrackingButton").addEventListener("click", async () => {
    await loadScreenerTracking(true);
    openView("screenerTrackingView");
  });
  document.querySelector("#closeScreenerTrackingButton").addEventListener("click", () => openView("screenerView"));
  document.querySelector("#refreshTrackingButton").addEventListener("click", refreshScreenerTracking);
  document.querySelector("#trackingTableBody").addEventListener("click", handleTrackingTableAction);
  document.querySelectorAll("[data-screener-mobile-view]").forEach((button) => {
    button.addEventListener("click", () => selectScreenerMobileView(button.dataset.screenerMobileView));
  });
  document.querySelector("#compileStrategyButton").addEventListener("click", compileStrategy);
  document.querySelector("#saveStrategyButton").addEventListener("click", saveCurrentStrategy);
  document.querySelector("#deleteStrategyButton").addEventListener("click", deleteCurrentStrategy);
  document.querySelector("#screenerExportButton").addEventListener("click", exportScreenerResults);
  document.querySelector("#runBacktestToggle").addEventListener("change", updateBacktestTaskStatus);
  document.querySelectorAll("[data-screener-mode]").forEach((button) => {
    button.addEventListener("click", () => selectScreenerMode(button.dataset.screenerMode));
  });
  document.querySelector("#curatedStrategyList").addEventListener("click", (event) => {
    if (event.target.closest("button")) return;
    const card = event.target.closest("[data-curated-strategy]");
    if (!card) return;
    state.selectedCuratedStrategyId = number(card.dataset.curatedStrategy);
    renderCuratedStrategyLibrary();
    renderScreenerResult();
  });
  document.querySelector("#curatedStrategySearch").addEventListener("input", (event) => {
    state.curatedQuery = event.target.value.trim().toLocaleLowerCase("zh-CN");
    renderCuratedStrategyLibrary();
  });
  document.querySelector("#curatedCategoryFilter").addEventListener("change", (event) => {
    state.curatedCategory = event.target.value;
    renderCuratedStrategyLibrary();
  });
  document.querySelector("#curatedSchoolFilters").addEventListener("click", (event) => {
    const button = event.target.closest("[data-curated-school]");
    if (!button) return;
    state.curatedSchool = button.dataset.curatedSchool;
    renderCuratedStrategyLibrary();
  });
  document.querySelectorAll("[data-curated-view]").forEach((button) => {
    button.addEventListener("click", () => {
      state.curatedViewMode = button.dataset.curatedView === "grid" ? "grid" : "list";
      localStorage.setItem("xiaobaiCuratedViewMode", state.curatedViewMode);
      renderCuratedStrategyLibrary();
    });
  });
  document.querySelector("#quantResetButton").addEventListener("click", resetQuantBuilder);
  document.querySelector("#addQuantFilterButton").addEventListener("click", () => addQuantFilter());
  document.querySelector("#addQuantScoreButton").addEventListener("click", () => addQuantScore());
  document.querySelector("#quantFilterRows").addEventListener("input", handleQuantBuilderInput);
  document.querySelector("#quantFilterRows").addEventListener("change", handleQuantBuilderInput);
  document.querySelector("#quantFilterRows").addEventListener("click", handleQuantBuilderClick);
  document.querySelector("#quantScoreRows").addEventListener("input", handleQuantBuilderInput);
  document.querySelector("#quantScoreRows").addEventListener("change", handleQuantBuilderInput);
  document.querySelector("#quantScoreRows").addEventListener("click", handleQuantBuilderClick);
  ["quantListedDays", "quantLimit", "quantMinScore", "quantExcludeSt"].forEach((id) => {
    document.querySelector(`#${id}`).addEventListener("input", renderQuantSummary);
    document.querySelector(`#${id}`).addEventListener("change", renderQuantSummary);
  });
  document.querySelector("#quantRunButton").addEventListener("click", runQuantStrategy);
  document.querySelector("#quantSaveButton").addEventListener("click", saveQuantAsStrategy);
  document.querySelector("#quantBacktestToggle").addEventListener("change", updateBacktestTaskStatus);
  document.querySelector("#mentorChatForm").addEventListener("submit", sendMentorQuestion);
  document.querySelector("#clearMentorChatButton").addEventListener("click", clearMentorConversation);
  document.querySelector("#mentorDirectoryToggle").addEventListener("click", () => {
    toggleMentorDirectory(!state.mentorDirectoryOpen);
  });
  document.querySelector("#closeMentorDirectory").addEventListener("click", () => toggleMentorDirectory(false));
  document.querySelector("#mentorDirectoryBackdrop").addEventListener("click", () => toggleMentorDirectory(false));
  document.querySelector("#mentorSortToggle").addEventListener("click", toggleMentorSortMode);
  document.querySelector("#mentorSearchInput").addEventListener("input", (event) => {
    state.mentorQuery = event.target.value.trim().toLocaleLowerCase("zh-CN");
    renderMentorDirectory();
  });
  document.querySelectorAll("[data-mentor-grade]").forEach((button) => {
    button.addEventListener("click", () => {
      state.mentorGrade = button.dataset.mentorGrade || "all";
      document.querySelectorAll("[data-mentor-grade]").forEach((item) => {
        item.classList.toggle("active", item === button);
      });
      renderMentorDirectory();
    });
  });
  document.querySelectorAll("[data-mentor-prompt]").forEach((button) => {
    button.addEventListener("click", () => useMentorQuickPrompt(button.dataset.mentorPrompt));
  });
  document.querySelectorAll("[data-heaven-panel]").forEach((button) => {
    button.addEventListener("click", () => selectHeavenPanel(button.dataset.heavenPanel, true));
  });
  document.querySelector("#loadHeavenSelectionButton").addEventListener("click", loadHeavenSelection);
  document.querySelector("#heavenCalibrationForm").addEventListener("submit", applyHeavenCalibration);
  document.querySelector("#resetHeavenCalibrationButton").addEventListener("click", resetHeavenCalibration);
  document.querySelector("#heavenStockInput").addEventListener("keydown", (event) => {
    if (event.key === "Enter") {
      event.preventDefault();
      loadHeavenSelection();
    }
  });
  document.querySelector("#interpretTrendButton").addEventListener("click", () => interpretHeaven("trend"));
  document.querySelector("#interpretFortuneButton").addEventListener("click", () => interpretHeaven("fortune"));
  document.querySelector("#historyTrendButton").addEventListener("click", () => openHeavenHistory("trend"));
  document.querySelector("#historyFortuneButton").addEventListener("click", () => openHeavenHistory("fortune"));
  document.querySelector("#qiObservationDate").addEventListener("change", () => {
    state.personalField = null;
    state.heavenManualData = null;
    state.heavenInterpretations.fortune = "";
    loadHeavenSetup(
      true,
      "",
      document.querySelector("#heavenStockInput").value.trim(),
    );
  });
  document.querySelector("#openPersonalSettingsButton").addEventListener("click", () => openSettings("profile"));
  document.querySelector("#accountBirthForm").addEventListener("submit", saveAccountBirthProfile);
  document.querySelector("#deleteBirthProfileButton").addEventListener("click", deleteAccountBirthProfile);
  document.querySelector("#passwordForm").addEventListener("submit", changeAccountPassword);
  document.querySelector("#sectorPhaseForm").addEventListener("submit", saveSectorPhaseOverride);
  document.querySelector("#startBreathingButton").addEventListener("click", startHeartBreathing);
  document.querySelector("#beginCastingButton").addEventListener("click", beginHeartCasting);
  document.querySelector("#heartSoundToggle").addEventListener("click", toggleHeartSound);
  document.querySelector("#historyHeartButton").addEventListener("click", () => openHeavenHistory("heart"));
  initializeHeartCoinHold();
  initializeHeartLineInspection();
  document.querySelector("#interpretHeartButton").addEventListener("click", () => interpretHeaven("heart"));
  document.querySelector("#viewHeartReadingButton").addEventListener("click", () => openHeavenReading("heart"));
  document.querySelector("#restartHeartButton").addEventListener("click", resetHeartRitual);
  document.querySelector("#closeHeavenReadingDialog").addEventListener("click", () => elements.heavenReadingDialog.close());
  elements.heavenReadingDialog.addEventListener("close", stopHeavenReadingAnimation);
  document.querySelectorAll("[data-heaven-reading-tab]").forEach((button) => {
    button.addEventListener("click", () => selectHeavenReadingTab(button.dataset.heavenReadingTab));
  });
  document.querySelector("#heavenReadingHistoryList").addEventListener("click", handleHeavenHistorySelection);
  document.querySelector("#heavenReadingHistoryDetail").addEventListener("click", handleHeavenHistoryAction);
  document.querySelectorAll("[data-heart-return]").forEach((button) => {
    button.addEventListener("click", resetHeartRitual);
  });
  document.querySelector("#adminSectionSelect").addEventListener("change", (event) => selectAdminPanel(event.target.value));
  document.querySelector("#systemMarketForm").addEventListener("submit", saveMarketSettings);
  document.querySelector("#systemModelsForm").addEventListener("submit", saveModelPool);
  document.querySelector("#membershipSettingsForm").addEventListener("submit", saveMembershipSettings);
  document.querySelector("#addPlatformModel").addEventListener("click", addPlatformModel);
  document.querySelector("#adminRefreshButton").addEventListener("click", startAdminRefresh);
  window.addEventListener("resize", redrawThemeSensitiveVisuals);
  initializeAutoTableSorting();
}

async function loadDashboard(force = false, background = false, showOverlay = true) {
  const requestedDate = elements.tradeDate.value;
  if (state.dashboardLoading && state.dashboardRequestDate === requestedDate) return;
  state.dashboardLoading = true;
  state.dashboardRequestDate = requestedDate;
  const requestSequence = ++state.dashboardRequestSequence;
  if (force) stockPreviewCache.clear();
  if (!background && showOverlay) {
    setLoading(true, "正在加载市场数据");
    setStatus("正在加载市场数据");
  } else if (!background) {
    setStatus("正在刷新行情");
  }
  try {
    const query = new URLSearchParams({ trade_date: elements.tradeDate.value });
    if (force) query.set("force", "1");
    const payload = await apiRequest(`/api/dashboard?${query}`);
    if (
      requestSequence !== state.dashboardRequestSequence
      || requestedDate !== elements.tradeDate.value
    ) return;
    applyDashboard(payload, background);
  } catch (error) {
    if (background) {
      setStatus("实时刷新暂时中断，正在等待重试");
    } else {
      showToast(error.message || "无法连接本地服务");
      setStatus("加载失败");
    }
  } finally {
    if (requestSequence === state.dashboardRequestSequence) {
      state.dashboardLoading = false;
      state.dashboardRequestDate = "";
      if (!background && showOverlay) setLoading(false);
      updateDateButtons();
    }
  }
}

async function startAdminRefresh() {
  const buttons = [document.querySelector("#syncButton"), document.querySelector("#adminRefreshButton")].filter(Boolean);
  buttons.forEach((button) => { button.disabled = true; });
  try {
    const payload = await apiRequest("/api/admin/refresh", "POST", { trade_date: elements.tradeDate.value });
    showToast(payload.message || "后台刷新已提交");
    setStatus("后台刷新运行中，当前页面保持不变");
  } catch (error) {
    showToast(error.message || "后台刷新启动失败");
  } finally {
    buttons.forEach((button) => { button.disabled = false; });
  }
}

function applyDashboard(payload, background = false) {
  state.dashboard = payload;
  const selectedDate = payload.meta.requested_date || payload.meta.trade_date;
  elements.tradeDate.value = selectedDate;
  document.querySelector("#qiObservationDate").value = selectedDate;
  document.querySelector("#journalDate").value = selectedDate;
  renderDashboard();
  setStatus(`${dashboardSourceLabel(payload.meta)} · 数据已更新`);
  if (!background) {
    if (state.activeView === "dragonView") loadDragonTiger();
    if (state.activeView === "screenerView") loadScreenerSetup();
    if (state.activeView === "screenerTrackingView") loadScreenerTracking(true);
    if (state.activeView === "mentorView") loadMentorSetup(true);
    if (state.activeView === "heavenView") loadHeavenSetup(true);
    if (state.activeView === "sentimentCycleView") loadSentimentHistory(true);
    if (state.activeView === "rotationView") loadRotationHistory(true);
    if (state.activeView === "auctionView") loadAuctionCenter(true);
    if (state.activeView === "themeLibraryView") loadThemeLibrary(true);
    if (state.activeView === "popularityView") loadPopularity(true);
  }
  const requestedStock = new URLSearchParams(window.location.search).get("stock");
  if (!state.initialStockOpened && /^\d{6}$/.test(requestedStock || "")) {
    state.initialStockOpened = true;
    openStock(requestedStock);
  }
}

function dashboardSourceLabel(meta = {}) {
  if (meta.realtime && !["closed", "after_hours"].includes(String(meta.market_status || ""))) return "盘中行情";
  if (meta.carried_forward) return "最近收盘行情";
  if (meta.market_status === "historical") return "历史行情";
  return "收盘行情";
}

function renderDashboard() {
  const { meta, overview, ladders, sectors } = state.dashboard;
  animateMetric("tapeUp", overview.up_count, (value) => Math.round(value));
  animateMetric("tapeDown", overview.down_count, (value) => Math.round(value));
  setText("tapeLimit", `${overview.limit_up_count} / 跌停 ${overview.limit_down_count}`);
  animateMetric("tapeAmount", overview.amount_billion, (value) => `${formatNumber(value, 1)} 亿`);
  animateMetric("limitUpMetric", overview.limit_up_count, (value) => `${Math.round(value)} 家`);
  animateMetric("limitDownMetric", overview.limit_down_count, (value) => `${Math.round(value)} 家`);
  animateMetric("brokenMetric", overview.broken_count, (value) => `${Math.round(value)} 家`);
  animateMetric("sealRateMetric", overview.seal_rate, (value) => `${formatNumber(value, 1)}%`);
  animateMetric("amountMetric", overview.amount_billion, (value) => `${formatNumber(value, 1)} 亿`);
  setText("dataDateMetric", dashboardDataTimestamp(meta));
  animateMetric("sentimentScore", overview.sentiment_score, (value) => Math.round(value));
  setText("sentimentText", sentimentLabel(overview.sentiment_score));
  updateSentimentGauge(overview.sentiment_score);
  setText("updatedAt", `${dashboardSourceLabel(meta)} · 更新 ${formatTimestamp(meta.updated_at)}`);

  renderLimitTable();
  renderLadderMini(ladders || []);
  renderSectorMini(sectors || []);
  renderBrokenTable(state.dashboard.broken || []);
  renderDownTable(state.dashboard.down_limits || []);
  renderYesterdayTable(state.dashboard.yesterday_limits || []);
  renderPerformance(state.dashboard.limit_performance || []);
  renderLadderBoard(ladders || []);
  renderRotationMembers();
}

/* PRESERVATION-SOURCE-END app.js:1-1206 */
/* PRESERVATION-SOURCE-BEGIN app.js:3471-3489 */
async function backfillData() {
  const button = document.querySelector("#backfillButton");
  button.disabled = true;
  setLoading(true, "正在回补历史交易日");
  try {
    const payload = await apiRequest("/api/backfill", "POST", {
      start_date: document.querySelector("#backfillStart").value,
      end_date: document.querySelector("#backfillEnd").value,
    });
    showToast(`历史回补完成，共处理 ${payload.results.length} 个工作日`);
    await openAdminSettings(true);
  } catch (error) {
    showToast(error.message);
  } finally {
    setLoading(false);
    button.disabled = false;
  }
}

/* PRESERVATION-SOURCE-END app.js:3471-3489 */
/* PRESERVATION-SOURCE-BEGIN app.js:8427-8904 */
function hasMemberAccess() {
  return state.user?.role === "admin" || Boolean(state.user?.membership?.active);
}

function updateAccountIdentityBadges(membership = {}) {
  const isAdmin = state.user?.role === "admin" || Boolean(membership.is_admin);
  const subscribed = Boolean(membership.subscribed);
  document.querySelector("#accountAdminBadge").hidden = !isAdmin;
  const vipBadge = document.querySelector("#accountVipBadge");
  vipBadge.hidden = false;
  vipBadge.classList.toggle("is-nonmember", !subscribed);
  setText("accountVipLabel", subscribed ? "会员" : "非会员");
  vipBadge.title = subscribed ? "查看会员状态" : "查看会员权益";
}

function applyMembershipAccess() {
  const unlocked = hasMemberAccess();
  document.querySelectorAll(".member-feature-view").forEach((view) => {
    view.classList.toggle("member-locked", !unlocked);
    const gate = view.querySelector(".member-gate");
    if (gate) gate.hidden = unlocked;
    view.querySelectorAll("button, input, textarea, select").forEach((control) => {
      if (control.closest(".member-gate") || control.hasAttribute("data-member-navigation")) return;
      control.disabled = !unlocked;
    });
  });
  const assistantButton = document.querySelector("#assistantButton");
  assistantButton.classList.toggle("member-locked-control", !unlocked);
  assistantButton.title = unlocked ? "复盘助手" : "复盘助手（会员可用）";
  updateAssistantControls();
}

function openView(viewId, updateHash = true) {
  if (!applicationShell.page(viewId) || !pageModules.has(viewId)) return;
  const previousView = state.activeView;
  pageModules.beforeMount(viewId, previousView);
  if (!applicationShell.mount(viewId, { updateUrl: updateHash })) return;
  pageModules.afterMount(viewId, previousView);
}

function initializeAutoTableSorting() {
  markAutoSortableHeaders(document);
  document.addEventListener("click", (event) => {
    const header = event.target.closest?.("th[data-auto-sort]");
    if (!header || header.closest("#limitTable")) return;
    const table = header.closest("table");
    const body = table?.tBodies?.[0];
    if (!body || body.rows.length < 2) return;
    const direction = header.classList.contains("sort-asc") ? "desc" : "asc";
    table.querySelectorAll("th.sort-asc, th.sort-desc").forEach((item) => {
      item.classList.remove("sort-asc", "sort-desc", "sorted");
      item.removeAttribute("aria-sort");
      const arrow = item.querySelector(".arr");
      if (arrow) arrow.textContent = "↕";
    });
    header.classList.add(`sort-${direction}`, "sorted");
    header.setAttribute("aria-sort", direction === "asc" ? "ascending" : "descending");
    const activeArrow = header.querySelector(".arr");
    if (activeArrow) activeArrow.textContent = direction === "asc" ? "▲" : "▼";
    const columnIndex = header.cellIndex;
    const rows = [...body.rows].map((row, index) => ({ row, index }));
    rows.sort((left, right) => {
      const leftValue = autoSortValue(left.row.cells[columnIndex]);
      const rightValue = autoSortValue(right.row.cells[columnIndex]);
      let result;
      if (leftValue.kind === "number" && rightValue.kind === "number") result = leftValue.value - rightValue.value;
      else result = String(leftValue.value).localeCompare(String(rightValue.value), "zh-CN", { numeric: true, sensitivity: "base" });
      if (result === 0) result = left.index - right.index;
      return direction === "asc" ? result : -result;
    });
    rows.forEach(({ row }) => body.appendChild(row));
    const firstHeader = [...header.parentElement.cells][0]?.textContent.trim();
    if (["#", "排名"].includes(firstHeader)) {
      [...body.rows].forEach((row, index) => {
        if (row.cells[0]) row.cells[0].textContent = String(index + 1);
      });
    }
  });
}

function markAutoSortableHeaders(root) {
  root.querySelectorAll?.(".data-table:not(#limitTable) thead th").forEach((header) => {
    if (header.closest("#brokenTable, #downTable, #yesterdayTable, #rotationTable")) return;
    if (number(header.colSpan) > 1) return;
    const label = header.textContent.trim();
    if (!label || ["#", "操作"].includes(label)) return;
    header.dataset.autoSort = "true";
    header.classList.add("sortable");
    if (!header.querySelector(".arr")) header.insertAdjacentHTML("beforeend", '<span class="arr">↕</span>');
    header.title = `${label}：点击排序`;
  });
}

function autoSortValue(cell) {
  const text = String(cell?.dataset?.sortValue || cell?.textContent || "").trim();
  if (!text || text === "--" || text.includes("样本不足")) return { kind: "text", value: "\uffff" };
  const boardMatch = text.match(/(\d+)\s*板/);
  if (boardMatch) return { kind: "number", value: Number(boardMatch[1]) };
  const normalized = text.replaceAll(",", "").replace(/[+%]/g, "");
  const numericMatch = normalized.match(/^-?\d+(?:\.\d+)?/);
  if (numericMatch) {
    let value = Number(numericMatch[0]);
    if (text.includes("亿")) value *= 10000;
    return { kind: "number", value };
  }
  return { kind: "text", value: text };
}

function changeSort(key) {
  if (state.sortKey === key) state.sortDirection = state.sortDirection === "asc" ? "desc" : "asc";
  else {
    state.sortKey = key;
    state.sortDirection = ["name", "code", "sector", "first_time", "last_time"].includes(key) ? "asc" : "desc";
  }
  renderLimitTable();
}

function compareRows(left, right) {
  const leftValue = left[state.sortKey] ?? "";
  const rightValue = right[state.sortKey] ?? "";
  let result = typeof leftValue === "number" || typeof rightValue === "number"
    ? number(leftValue) - number(rightValue)
    : String(leftValue).localeCompare(String(rightValue), "zh-CN", { numeric: true });
  if (result === 0 && state.sortKey !== "first_time") result = String(left.first_time || "").localeCompare(String(right.first_time || ""));
  return state.sortDirection === "asc" ? result : -result;
}

function updateSortHeaders() {
  document.querySelectorAll("#limitTable th[data-sort]").forEach((header) => {
    header.classList.remove("sort-asc", "sort-desc", "sorted");
    const active = header.dataset.sort === state.sortKey;
    if (active) header.classList.add(state.sortDirection === "asc" ? "sort-asc" : "sort-desc", "sorted");
    const arrow = header.querySelector(".arr");
    if (arrow) arrow.textContent = active ? (state.sortDirection === "asc" ? "▲" : "▼") : "↕";
  });
}

function shiftDate(delta) {
  const current = parseLocalDate(elements.tradeDate.value);
  current.setDate(current.getDate() + delta);
  const next = localDateString(current);
  if (next > todayString()) return;
  elements.tradeDate.value = next;
  state.heavenManualData = null;
  document.querySelector("#qiObservationDate").value = next;
  loadDashboard();
}

function updateDateButtons() {
  document.querySelector("#nextDate").disabled = elements.tradeDate.value >= todayString();
}

function selectAccountPanel(panel) {
  const selected = ["profile", "membership", "password"].includes(panel) ? panel : "profile";
  const titles = { profile: "个人资料", membership: "会员状态", password: "修改密码" };
  setText("accountDialogTitle", titles[selected]);
  document.querySelectorAll("[data-account-panel-content]").forEach((section) => {
    section.hidden = section.dataset.accountPanelContent !== selected;
  });
  document.querySelector("#connectionStatus").hidden = selected !== "membership";
  return selected;
}

async function openSettings(panel = "profile") {
  selectAccountPanel(panel);
  toggleAccountDropdown(false);
  toggleHeaderCommandMenu(false);
  const status = document.querySelector("#connectionStatus");
  status.className = "connection-status";
  status.textContent = "正在读取账号状态";
  openModalDialog(elements.settingsDialog);
  try {
    const payload = await apiRequest("/api/account/status");
    const access = payload.llm_access || {};
    const membership = access.membership || {};
    if (state.user) {
      state.user.membership = membership;
      updateAccountIdentityBadges(membership);
      applyMembershipAccess();
    }
    status.textContent = membership.active ? "账户权益已同步" : "账户信息已同步";
    status.classList.toggle("connected", true);
    setText("membershipBadge", membership.subscribed ? "会员有效" : membership.is_admin ? "管理员权限" : "普通用户");
    setText("membershipStateValue", membership.subscribed ? "已开通" : membership.is_admin ? "管理员可用" : "未开通");
    setText("membershipRemainingValue", membership.subscribed && membership.expires_at
      ? `${number(membership.remaining_days)} 天`
      : membership.is_admin || membership.subscribed ? "长期有效" : "--");
    setText("membershipDetail", membership.subscribed
      ? `${membership.plan || "会员"}${membership.expires_at ? ` · 有效至 ${membershipDateDisplay(membership.expires_at, true)}` : " · 长期有效"}`
      : membership.is_admin
        ? "管理员拥有智能功能管理权限，但不会因此显示为已开通会员。"
      : "开通会员后可使用智能选股、问师、问天、复盘助手等智能功能。");
    setText("membershipQuotaHint", `会员默认每日智能分析额度 ${number(access.daily_limit)} 次，由管理员统一设置。`);
    setText("membershipUsage", membership.active
      ? `今日已用 ${number(access.used_today)} 次`
      : "今日智能分析：--");
    setText("membershipUsageSummary", membership.active ? `${number(access.used_today)} / ${number(access.daily_limit)}` : "--");
    setText("membershipRemainingUsage", membership.is_admin ? "不限" : membership.active ? `${number(access.remaining_calls)} 次` : "--");
    const birth = payload.birth_profile || {};
    if (birth.birth_datetime) {
      const [birthDate, birthTime] = String(birth.birth_datetime).split("T");
      document.querySelector("#accountBirthDate").value = birthDate || "";
      document.querySelector("#accountBirthTime").value = (birthTime || "").slice(0, 5);
      document.querySelector("#accountBirthGender").value = birth.gender || "unspecified";
    }
    setText("birthProfileStatus", payload.birth_profile_configured ? "已加密保存" : "尚未设置");
    document.querySelector("#deleteBirthProfileButton").disabled = !payload.birth_profile_configured;
  } catch (error) {
    status.hidden = false;
    status.textContent = "账户状态暂时无法同步";
    showToast(error.message || "账号信息加载失败");
  }
}

async function changeAccountPassword(event) {
  event.preventDefault();
  const form = event.currentTarget;
  const button = form.querySelector("button[type='submit']");
  button.disabled = true;
  try {
    await apiRequest("/api/account/password", "POST", {
      current_password: document.querySelector("#currentPassword").value,
      new_password: document.querySelector("#newPassword").value,
      confirm_password: document.querySelector("#confirmPassword").value,
    });
    form.reset();
    showToast("密码已更新");
  } catch (error) {
    showToast(error.message || "密码更新失败");
  } finally {
    button.disabled = false;
  }
}

async function switchAccount() {
  const button = document.querySelector("#switchAccountMenuButton");
  button.disabled = true;
  toggleAccountDropdown(false);
  try {
    await apiRequest("/api/auth/logout", "POST", {});
    window.location.reload();
  } catch (error) {
    showToast(error.message || "切换账号失败");
    button.disabled = false;
  }
}

async function openAdminSettings(refreshOnly = false) {
  if (state.user?.role !== "admin") return;
  if (!refreshOnly) openModalDialog(elements.adminDialog);
  const status = document.querySelector("#adminConnectionStatus");
  status.textContent = "正在读取系统状态";
  try {
    const payload = await apiRequest("/api/admin/settings");
    const data = payload.data || {};
    const ifind = data.ifind || {};
    const llm = payload.llm || {};
    const membership = payload.membership || {};
    status.textContent = `Tushare ${data.configured ? "已配置" : "未配置"} · iFinD ${ifind.configured ? "已配置" : "未配置"} · ${number(data.snapshot_dates)} 个交易日`;
    status.classList.toggle("connected", Boolean(data.configured));
    setText("systemDataStatus", data.background_refresh_enabled ? "后台刷新已启用" : "后台刷新已暂停");
    document.querySelector("#systemTokenInput").value = "";
    document.querySelector("#systemIfindTokenInput").value = "";
    document.querySelector("#systemBackgroundRefresh").checked = Boolean(data.background_refresh_enabled);
    document.querySelector("#memberDailyLimit").value = number(membership.member_daily_limit) || 50;
    renderModelPool(llm.models || [], llm.primary_model_id || "", llm.fallback_model_id || "");
    renderAdminUsers(payload.users || []);
  } catch (error) {
    status.textContent = error.message || "系统配置读取失败";
  }
}

function selectAdminPanel(panel) {
  const selected = ["market", "models", "members"].includes(panel) ? panel : "market";
  document.querySelector("#adminSectionSelect").value = selected;
  document.querySelectorAll("[data-admin-panel]").forEach((item) => {
    item.hidden = item.dataset.adminPanel !== selected;
  });
}

function renderModelPool(models, primaryId = "", fallbackId = "") {
  state.adminModels = models.map((item) => ({ ...item, api_key: item.api_key || "" }));
  const container = document.querySelector("#modelPoolList");
  container.innerHTML = state.adminModels.map((item, index) => `
    <article class="model-pool-row" data-model-id="${escapeHtml(item.id)}">
      <div class="model-pool-heading"><strong>${escapeHtml(item.name || `模型 ${index + 1}`)}</strong><span>${item.configured ? "已保存密钥" : "待配置"}</span></div>
      <div class="model-pool-fields">
        <label class="form-field"><span>显示名称 *</span><input data-model-field="name" maxlength="50" value="${escapeHtml(item.name || "")}" required></label>
        <label class="form-field"><span>API Base URL *</span><input data-model-field="base_url" type="url" value="${escapeHtml(item.base_url || "https://api.openai.com/v1")}" required></label>
        <label class="form-field"><span>模型标识 *</span><input data-model-field="model" maxlength="100" value="${escapeHtml(item.model || "")}" required></label>
        <label class="form-field"><span>API Key${item.configured ? "" : " *"}</span><input data-model-field="api_key" type="password" autocomplete="off" maxlength="300" placeholder="${item.configured ? "留空保留已保存的 Key" : "输入 API Key"}" ${item.configured ? "" : "required"}></label>
      </div>
      <div class="model-test-row"><button class="button" type="button" data-test-model>测试连接</button><span class="model-test-status" aria-live="polite">未测试</span><button class="icon-button model-delete-button" type="button" data-delete-model aria-label="删除模型" title="删除模型"><i data-lucide="trash-2"></i></button></div>
    </article>
  `).join("") || emptyStateHtml("模型池为空，请先添加模型");
  updateModelRoleOptions(primaryId, fallbackId);
  container.querySelectorAll("[data-test-model]").forEach((button) => button.addEventListener("click", () => testPlatformModel(button.closest("[data-model-id]"))));
  container.querySelectorAll("[data-delete-model]").forEach((button) => button.addEventListener("click", () => deletePlatformModel(button.closest("[data-model-id]"))));
  container.querySelectorAll("[data-model-field='name']").forEach((input) => input.addEventListener("input", updateModelRoleLabels));
  refreshIcons();
}

function collectModelPool() {
  const saved = new Map(state.adminModels.map((item) => [item.id, item]));
  return [...document.querySelectorAll("#modelPoolList [data-model-id]")].map((row) => ({
    id: row.dataset.modelId,
    name: row.querySelector("[data-model-field='name']").value.trim(),
    base_url: row.querySelector("[data-model-field='base_url']").value.trim(),
    model: row.querySelector("[data-model-field='model']").value.trim(),
    api_key: row.querySelector("[data-model-field='api_key']").value.trim(),
    configured: Boolean(saved.get(row.dataset.modelId)?.configured),
  }));
}

function updateModelRoleOptions(primaryId = document.querySelector("#platformPrimaryModelSelect").value, fallbackId = document.querySelector("#platformFallbackModelSelect").value) {
  const models = collectModelPool();
  const options = models.map((item) => `<option value="${escapeHtml(item.id)}">${escapeHtml(item.name || item.model || "未命名模型")}</option>`).join("");
  const primary = document.querySelector("#platformPrimaryModelSelect");
  const fallback = document.querySelector("#platformFallbackModelSelect");
  primary.innerHTML = models.length ? options : '<option value="">暂无模型</option>';
  fallback.innerHTML = `<option value="">不启用辅助模型</option>${options}`;
  primary.value = models.some((item) => item.id === primaryId) ? primaryId : models[0]?.id || "";
  fallback.value = models.some((item) => item.id === fallbackId) && fallbackId !== primary.value ? fallbackId : "";
}

function updateModelRoleLabels() {
  updateModelRoleOptions();
}

function addPlatformModel() {
  const models = collectModelPool();
  const id = `model-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
  models.push({ id, name: `模型 ${models.length + 1}`, base_url: "https://api.openai.com/v1", model: "", api_key: "", configured: false });
  renderModelPool(models, document.querySelector("#platformPrimaryModelSelect").value || id, document.querySelector("#platformFallbackModelSelect").value);
  document.querySelector(`[data-model-id="${CSS.escape(id)}"] [data-model-field="name"]`)?.focus();
}

function deletePlatformModel(row) {
  if (!row) return;
  const id = row.dataset.modelId;
  const primary = document.querySelector("#platformPrimaryModelSelect").value;
  const fallback = document.querySelector("#platformFallbackModelSelect").value;
  if (id === primary || id === fallback) {
    showToast("请先为主模型或辅助模型选择其他模型，再删除当前模型");
    return;
  }
  const models = collectModelPool().filter((item) => item.id !== id);
  renderModelPool(models, primary, fallback);
}

function renderAdminUsers(users) {
  const container = document.querySelector("#adminUsersList");
  container.innerHTML = users.map((user) => {
    const admin = user.role === "admin";
    const member = Boolean(user.membership_subscribed);
    const identityLabels = [admin ? "管理员" : "", member ? "会员有效" : "普通用户"].filter(Boolean).join(" · ");
    const expiry = member
      ? (user.membership_expires_at ? `有效至 ${membershipDateDisplay(user.membership_expires_at)}` : "永久有效")
      : user.membership_status === "suspended"
        ? "会员已停用"
        : user.membership_status === "active" && user.membership_expires_at
          ? `已于 ${membershipDateDisplay(user.membership_expires_at)} 到期`
          : "尚未开通";
    return `<article class="admin-user-row" data-admin-user="${number(user.id)}">
      <div class="admin-user-identity"><strong>${escapeHtml(user.username)}</strong><span>${escapeHtml(identityLabels)}</span><small>${escapeHtml(expiry)}</small></div>
      <div class="admin-user-usage">今日调用 <b>${number(user.used_today)}</b></div>
      <form class="membership-form">
        <input type="hidden" name="user_id" value="${number(user.id)}">
        <label><span>状态</span><select name="status"><option value="inactive" ${user.membership_status === "inactive" ? "selected" : ""}>未开通</option><option value="active" ${user.membership_status === "active" ? "selected" : ""}>有效</option><option value="suspended" ${user.membership_status === "suspended" ? "selected" : ""}>停用</option></select></label>
        <label><span>开通 / 续期时长</span><select name="duration"><option value="">选择时长</option><option value="1_month">1个月</option><option value="3_months">3个月</option><option value="12_months">12个月</option><option value="3_years">3年</option><option value="permanent">永久</option></select></label>
        <div class="membership-expiry"><span>当前到期</span><strong>${escapeHtml(expiry)}</strong></div>
        <button class="button" type="submit">应用</button>
      </form>
    </article>`;
  }).join("") || emptyStateHtml("暂无注册用户");
  container.querySelectorAll(".membership-form").forEach((form) => form.addEventListener("submit", saveMembership));
}

async function saveMembership(event) {
  event.preventDefault();
  const form = event.currentTarget;
  const data = Object.fromEntries(new FormData(form).entries());
  const button = form.querySelector("button[type='submit']");
  button.disabled = true;
  try {
    const payload = await apiRequest("/api/admin/membership", "POST", data);
    renderAdminUsers(payload.users || []);
    showToast("会员状态已更新");
  } catch (error) {
    showToast(error.message || "会员状态保存失败");
  } finally {
    button.disabled = false;
  }
}

async function saveMarketSettings(event) {
  event.preventDefault();
  const button = event.currentTarget.querySelector("button[type='submit']");
  button.disabled = true;
  try {
    await apiRequest("/api/admin/settings", "POST", {
      tushare_token: document.querySelector("#systemTokenInput").value.trim(),
      ifind_refresh_token: document.querySelector("#systemIfindTokenInput").value.trim(),
      background_refresh_enabled: document.querySelector("#systemBackgroundRefresh").checked,
    });
    document.querySelector("#systemTokenInput").value = "";
    document.querySelector("#systemIfindTokenInput").value = "";
    showToast("行情配置已保存");
    await openAdminSettings(true);
  } catch (error) {
    showToast(error.message || "系统配置保存失败");
  } finally {
    button.disabled = false;
  }
}

async function saveModelPool(event) {
  event.preventDefault();
  const button = event.currentTarget.querySelector("button[type='submit']");
  button.disabled = true;
  try {
    await apiRequest("/api/admin/settings", "POST", {
      models: collectModelPool(),
      primary_model_id: document.querySelector("#platformPrimaryModelSelect").value,
      fallback_model_id: document.querySelector("#platformFallbackModelSelect").value,
    });
    showToast("模型池已保存");
    await openAdminSettings(true);
  } catch (error) {
    showToast(error.message || "模型池保存失败");
  } finally {
    button.disabled = false;
  }
}

async function saveMembershipSettings(event) {
  event.preventDefault();
  const button = event.currentTarget.querySelector("button[type='submit']");
  button.disabled = true;
  try {
    await apiRequest("/api/admin/settings", "POST", {
      member_daily_limit: number(document.querySelector("#memberDailyLimit").value),
    });
    showToast("会员调用额度已保存");
    await openAdminSettings(true);
  } catch (error) {
    showToast(error.message || "会员调用额度保存失败");
  } finally {
    button.disabled = false;
  }
}

async function testPlatformModel(row) {
  if (!row) return;
  const button = row.querySelector("[data-test-model]");
  const status = row.querySelector(".model-test-status");
  const profile = collectModelPool().find((item) => item.id === row.dataset.modelId) || {};
  button.disabled = true;
  status.textContent = "连接中";
  try {
    const payload = await apiRequest("/api/admin/settings/test", "POST", { model_id: row.dataset.modelId, profile });
    status.textContent = `已连通 · ${number(payload.result.latency_ms)} ms`;
    status.className = "model-test-status success";
  } catch (error) {
    status.textContent = error.message;
    status.className = "model-test-status failure";
  } finally {
    button.disabled = false;
  }
}

function membershipDateDisplay(value) {
  if (!value) return "";
  const parsed = new Date(value);
  if (Number.isNaN(parsed.getTime())) return String(value).slice(0, 10);
  return new Intl.DateTimeFormat("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit" }).format(parsed);
}

/* PRESERVATION-SOURCE-END app.js:8427-8904 */
/* PRESERVATION-SOURCE-BEGIN app.js:9052-9283 */
function trendClass(trend) {
  return { "升温": "trend-hot", "降温": "trend-cool", "新进": "trend-new", "持平": "trend-flat" }[trend] || "trend-flat";
}

function changeClass(value) {
  return number(value) > 0 ? "up" : number(value) < 0 ? "down" : "";
}

function sentimentLabel(score) {
  const value = number(score);
  if (value >= 80) return "情绪高涨";
  if (value >= 60) return "情绪偏强";
  if (value >= 40) return "情绪中性";
  if (value >= 20) return "情绪偏弱";
  return "情绪冰点";
}

function streakLabel(streak) {
  const value = Math.max(1, number(streak));
  return value === 1 ? "首板" : `${value}板`;
}

function signed(value) {
  const parsed = number(value);
  return `${parsed > 0 ? "+" : ""}${formatNumber(parsed, 2)}`;
}

function dashboardDataTimestamp(meta = {}) {
  const tradeDate = displayCompactDate(meta.trade_date);
  if (tradeDate === "--") return "--";
  const intraday = tradeDate === todayString() && Boolean(meta.realtime) && !["closed", "after_hours"].includes(String(meta.market_status || ""));
  if (intraday) {
    const updated = new Date(meta.updated_at);
    if (!Number.isNaN(updated.getTime())) {
      const dateText = `${updated.getFullYear()}-${String(updated.getMonth() + 1).padStart(2, "0")}-${String(updated.getDate()).padStart(2, "0")}`;
      const timeText = updated.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", hour12: false });
      return `${dateText} ${timeText}`;
    }
  }
  return `${tradeDate} 15:00`;
}

function formatMoneyMillion(value) {
  const parsed = number(value);
  const sign = parsed > 0 ? "+" : "";
  if (Math.abs(parsed) >= 100) return `${sign}${formatNumber(parsed / 100, 2)} 亿`;
  return `${sign}${formatNumber(parsed * 100, 0)} 万`;
}

async function apiRequest(url, method = "GET", body = null, requestOptions = {}) {
  return window.XiaobaiAPI.request(url, method, body, requestOptions);
}

function setLoading(loading, text = "正在加载复盘数据", context = "default") {
  elements.loading.hidden = !loading;
  elements.loading.dataset.context = loading ? context : "default";
  setText("loadingTitle", text);
  setText(
    "loadingHint",
    context === "screener"
      ? "正在完成因子筛选、候选排序与历史样本回测，这通常需要一点时间"
      : "请稍候",
  );
}

function setStatus(text) {
  applicationShell.setStatus(text);
}

let toastTimer;
function showToast(message) {
  clearTimeout(toastTimer);
  elements.toast.textContent = message;
  elements.toast.hidden = false;
  toastTimer = setTimeout(() => { elements.toast.hidden = true; }, 3600);
}

function setText(id, value) {
  const element = document.getElementById(id);
  if (element) element.textContent = value;
}

function motionEnabled() {
  return !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}

function refreshIcons() {
  if (!window.lucide?.createIcons) return;
  window.lucide.createIcons({ attrs: { "aria-hidden": "true" } });
}

function toggleHeaderCommandMenu(force) {
  applicationShell.toggleHeaderCommandMenu(force);
}

function toggleAccountDropdown(force, returnFocus = false) {
  const menu = document.querySelector("#accountDropdown");
  const button = document.querySelector("#accountButton");
  if (!menu || !button) return;
  const open = typeof force === "boolean" ? force : menu.hidden;
  menu.hidden = !open;
  button.setAttribute("aria-expanded", String(open));
  document.querySelector(".account-menu-shell")?.classList.toggle("is-open", open);
  if (open) {
    setText("accountMenuName", state.user?.username || "当前账号");
    const membership = state.user?.membership || {};
    setText("accountMenuRole", state.user?.role === "admin" ? (membership.subscribed ? "管理员 · 会员" : "管理员") : membership.subscribed ? "会员用户" : "普通用户");
  } else if (returnFocus) {
    button.focus();
  }
}

function handleAccountMenuKeydown(event) {
  const menu = document.querySelector("#accountDropdown");
  if (!menu) return;
  if (menu.hidden) {
    if (document.activeElement?.id === "accountButton" && event.key === "ArrowDown") {
      event.preventDefault();
      toggleAccountDropdown(true);
      menu.querySelector('[role="menuitem"]')?.focus();
    }
    return;
  }
  const items = [...menu.querySelectorAll('[role="menuitem"]:not(:disabled)')];
  if (!items.length) return;
  const current = items.indexOf(document.activeElement);
  if (event.key === "ArrowDown" || event.key === "ArrowUp") {
    event.preventDefault();
    const offset = event.key === "ArrowDown" ? 1 : -1;
    items[(current + offset + items.length) % items.length].focus();
  } else if (event.key === "Home" || event.key === "End") {
    event.preventDefault();
    items[event.key === "Home" ? 0 : items.length - 1].focus();
  }
}

function updateSentimentGauge(rawScore) {
  const gauge = document.querySelector("#sentimentGauge");
  if (!gauge) return;
  const score = clamp(rawScore, 0, 100);
  const previous = Number(gauge.dataset.score);
  gauge.dataset.score = String(score);
  gauge.style.setProperty("--score", score);
  if (!motionEnabled() || !Number.isFinite(previous) || Math.abs(previous - score) < 15) return;
  gauge.classList.remove("sentiment-pulse");
  void gauge.offsetWidth;
  gauge.classList.add("sentiment-pulse");
  gauge.addEventListener("animationend", () => gauge.classList.remove("sentiment-pulse"), { once: true });
}

function animateMetric(id, rawValue, formatter = (value) => value) {
  const element = document.getElementById(id);
  const target = Number(rawValue);
  if (!element || !Number.isFinite(target)) {
    setText(id, formatter(rawValue));
    return;
  }
  const storedValue = Number(element.dataset.metricValue);
  const previous = Number.isFinite(storedValue) ? storedValue : 0;
  element.dataset.metricValue = String(target);
  const existingFrame = metricAnimationFrames.get(element);
  if (existingFrame) cancelAnimationFrame(existingFrame);
  if (!motionEnabled() || previous === target) {
    element.textContent = formatter(target);
    return;
  }
  element.classList.remove("metric-changed");
  void element.offsetWidth;
  element.classList.add("metric-changed");
  const startedAt = performance.now();
  const duration = 560;
  const update = (now) => {
    const progress = Math.min(1, (now - startedAt) / duration);
    const eased = 1 - (1 - progress) ** 3;
    element.textContent = formatter(previous + (target - previous) * eased);
    if (progress < 1) {
      metricAnimationFrames.set(element, requestAnimationFrame(update));
    } else {
      element.textContent = formatter(target);
      metricAnimationFrames.delete(element);
      setTimeout(() => element.classList.remove("metric-changed"), 80);
    }
  };
  metricAnimationFrames.set(element, requestAnimationFrame(update));
}

function animateRows(container) {
  if (!container) return;
  const rows = [...container.children].filter((item) => item.matches("tr, [data-code]"));
  if (!motionEnabled()) {
    rows.forEach((row) => row.classList.remove("row-pending", "row-enter"));
    return;
  }
  const unseenRows = rows.filter((row) => row.dataset.motionSeen !== "1");
  unseenRows.slice(0, 12).forEach((row, index) => {
    row.dataset.motionSeen = "1";
    row.classList.remove("row-pending", "row-enter");
    row.style.setProperty("--row-delay", `${index * 24}ms`);
    requestAnimationFrame(() => row.classList.add("row-enter"));
    row.addEventListener("animationend", () => row.classList.remove("row-enter"), { once: true });
  });
  if (!("IntersectionObserver" in window)) {
    unseenRows.slice(12).forEach((row) => { row.dataset.motionSeen = "1"; });
    return;
  }
  if (!rowAnimationObserver) {
    rowAnimationObserver = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (!entry.isIntersecting) return;
        const row = entry.target;
        rowAnimationObserver.unobserve(row);
        row.dataset.motionSeen = "1";
        row.classList.remove("row-pending");
        row.style.setProperty("--row-delay", "0ms");
        requestAnimationFrame(() => row.classList.add("row-enter"));
        row.addEventListener("animationend", () => row.classList.remove("row-enter"), { once: true });
      });
    }, { threshold: 0.08, rootMargin: "0px 0px 40px 0px" });
  }
  unseenRows.slice(12).forEach((row) => {
    row.classList.add("row-pending");
    rowAnimationObserver.observe(row);
  });
}

function waitForMotion(duration) {
  return new Promise((resolve) => setTimeout(resolve, motionEnabled() ? duration : 0));
}
/* PRESERVATION-SOURCE-END app.js:9052-9283 */
