window.XiaobaiPageModules.register("screener", ["screenerView"], {
  bind: bindScreenerEvents,
  enter: ["loadScreener"],
});

window.XiaobaiPageModules.register("screener", ["screenerTrackingView"]);

function screenerStrategyKey(strategyId, strategyName) {
  return strategyId != null && strategyId !== 0 ? `id:${strategyId}` : `name:${strategyName || ""}`;
}

function currentScreenerStrategy(mode) {
  if (mode === "curated") return activeCuratedStrategy();
  if (mode === "smart") return state.selectedStrategy;
  return null;
}

function screenerResultContext(mode, result, { regime, strategyId = null, strategyName = "" } = {}) {
  const normalizedMode = ["smart", "curated", "quant"].includes(mode) ? mode : "smart";
  return {
    mode: normalizedMode,
    regime: regime || result?.meta?.regime || state.selectedRegime,
    strategyName: strategyName || result?.meta?.strategy_name || "",
    strategyKey: screenerStrategyKey(
      strategyId,
      strategyName || result?.meta?.strategy_name || "",
    ),
  };
}

function screenerResultKey(context) {
  if (!context) return "";
  if (context.mode === "quant") return "quant";
  if (context.mode === "curated") return JSON.stringify(["curated", context.strategyKey]);
  return JSON.stringify(["smart", context.regime, context.strategyKey]);
}

function selectedScreenerResultKey(mode) {
  const normalizedMode = ["smart", "curated", "quant"].includes(mode) ? mode : "smart";
  if (normalizedMode === "quant") return "quant";
  const strategy = currentScreenerStrategy(mode);
  if (!strategy) return "";
  return screenerResultKey(screenerResultContext(normalizedMode, null, {
    regime: state.selectedRegime,
    strategyId: strategy.id,
    strategyName: strategy.name,
  }));
}

function activeScreenerResultEntry(mode = state.screenerMode) {
  const key = selectedScreenerResultKey(mode);
  const stored = key ? state.screenerResultStore[key] || null : null;
  if (stored) return stored;
  const normalizedMode = ["smart", "curated", "quant"].includes(mode) ? mode : "smart";
  const latest = state.screenerResults[normalizedMode];
  const context = state.screenerResultContexts[normalizedMode];
  if (!latest || !context) return null;
  if (normalizedMode === "quant") return { result: latest, context };
  const selected = currentScreenerStrategy(normalizedMode);
  return selected?.name === context.strategyName ? { result: latest, context } : null;
}

function activeScreenerResult(mode = state.screenerMode) {
  return activeScreenerResultEntry(mode)?.result || null;
}

function activeScreenerResultContext(mode = state.screenerMode) {
  return activeScreenerResultEntry(mode)?.context || null;
}

function storeScreenerResult(
  mode,
  result,
  { regime, strategyId = null, strategyName = "" } = {},
  updateLatest = true,
) {
  const normalizedMode = ["smart", "curated", "quant"].includes(mode) ? mode : "smart";
  const context = result
    ? screenerResultContext(normalizedMode, result, { regime, strategyId, strategyName })
    : null;
  const key = screenerResultKey(context);
  if (key && result) state.screenerResultStore[key] = { result, context };
  if (updateLatest) {
    state.screenerResults[normalizedMode] = result || null;
    state.screenerResultContexts[normalizedMode] = context;
  }
}

function setScreenerResult(mode, result, options = {}) {
  storeScreenerResult(mode, result, options, true);
}

function applyScreenerSetup(payload, requestKey) {
  const dateChanged = Boolean(state.screenerSetupKey && state.screenerSetupKey !== requestKey);
  state.screenerResults = { smart: null, curated: null, quant: null };
  state.screenerResultContexts = { smart: null, curated: null, quant: null };
  state.screenerResultStore = {};
  if (dateChanged) state.screenerArchiveView = "latest";
  state.screenerSetup = payload;
  state.screenerSetupKey = requestKey;

  const latestResults = { ...(payload.latest_results || {}) };
  if (!latestResults.smart && payload.latest_result) latestResults.smart = payload.latest_result;
  const smartLatestMeta = latestResults.smart?.meta || {};
  const curatedLatestMeta = latestResults.curated?.meta || {};
  state.selectedRegime = payload.regime.id;

  const selectedId = state.selectedStrategy?.id;
  const smartStrategies = payload.strategies.filter((item) => item.formula?.meta?.library !== "curated");
  const curatedStrategies = payload.strategies.filter((item) => item.formula?.meta?.library === "curated");
  const latestSmartStrategy = smartStrategies.find((item) => item.name === smartLatestMeta.strategy_name);
  state.selectedStrategy = smartStrategies.find((item) => item.id === selectedId)
    || latestSmartStrategy
    || smartStrategies.find((item) => item.regimes.includes(state.selectedRegime))
    || smartStrategies[0]
    || null;

  if (!curatedStrategies.some((item) => item.id === state.selectedCuratedStrategyId) || dateChanged) {
    state.selectedCuratedStrategyId = curatedStrategies.find(
      (item) => item.name === curatedLatestMeta.strategy_name,
    )?.id || curatedStrategies[0]?.id || 0;
  }

  for (const result of [...(payload.recent_results || [])].reverse()) {
    const mode = ["smart", "curated", "quant"].includes(result.meta?.mode)
      ? result.meta.mode
      : "smart";
    const strategies = mode === "curated" ? curatedStrategies : smartStrategies;
    const strategy = strategies.find((item) => item.name === result.meta?.strategy_name);
    storeScreenerResult(mode, result, {
      regime: result.meta?.regime || payload.regime.id,
      strategyId: strategy?.id,
      strategyName: result.meta?.strategy_name || strategy?.name || "",
    }, false);
  }

  for (const mode of ["smart", "curated", "quant"]) {
    if (state.screenerResults[mode] || !latestResults[mode]) continue;
    const result = latestResults[mode];
    const meta = result.meta || {};
    const strategy = mode === "curated"
      ? curatedStrategies.find((item) => item.name === meta.strategy_name)
      : mode === "smart"
        ? smartStrategies.find((item) => item.name === meta.strategy_name)
        : null;
    setScreenerResult(mode, result, {
      regime: meta.regime || payload.regime.id,
      strategyId: strategy?.id,
      strategyName: meta.strategy_name || strategy?.name || "",
    });
  }
  if (!state.quantScores.length) resetQuantBuilder(false);
  renderScreenerSetup();
  renderScreenerResult();
}

async function loadScreenerSetup(force = false) {
  const requestKey = elements.tradeDate.value.replaceAll("-", "");
  if (!force && state.screenerSetup && state.screenerSetupKey === requestKey) {
    renderScreenerSetup();
    renderScreenerResult();
    return state.screenerSetup;
  }
  if (!force && state.screenerSetupPromise && state.screenerSetupRequestKey === requestKey) {
    return state.screenerSetupPromise;
  }
  const request = (async () => {
    try {
      const query = new URLSearchParams({ trade_date: elements.tradeDate.value });
      const payload = await apiRequest(`/api/screener/setup?${query}`);
      applyScreenerSetup(payload, requestKey);
      await loadScreenerTracking();
      return payload;
    } catch (error) {
      showToast(error.message || "选股配置加载失败");
      return null;
    } finally {
      if (state.screenerSetupPromise === request) {
        state.screenerSetupPromise = null;
        state.screenerSetupRequestKey = "";
      }
    }
  })();
  state.screenerSetupRequestKey = requestKey;
  state.screenerSetupPromise = request;
  return request;
}

function renderScreenerSetup() {
  const setup = state.screenerSetup;
  if (!setup) return;
  const publishedDate = setup.published_batch?.trade_date;
  setText(
    "screenerDateLabel",
    publishedDate && publishedDate !== setup.trade_date
      ? `数据日期 ${displayCompactDate(setup.trade_date)} · 候选批次 ${displayCompactDate(publishedDate)}`
      : `数据日期 ${displayCompactDate(setup.trade_date)}`,
  );
  setText("regimeLabel", setup.regime.label);
  setText("regimeConfidence", `置信度 ${formatNumber(setup.regime.confidence, 0)}%`);
  setText("regimeStepStatus", `${setup.regime.label} · 置信度 ${formatNumber(setup.regime.confidence, 0)}%`);
  setText("regimeReason", setup.regime.reason);
  const evidence = (setup.regime.evidence || []).filter(Boolean);
  if (!evidence.some((item) => String(item).includes("情绪温度"))) {
    const temperature = formatNumber(state.dashboard?.overview?.sentiment_score, 0);
    const direction = state.dashboard?.overview?.sentiment_direction;
    evidence.unshift(`情绪温度 ${temperature}${direction ? `，较前一交易日${direction}` : ""}`);
  }
  document.querySelector("#regimeEvidenceList").textContent = evidence.join(" · ");
  setText("factorDateCount", `${number(setup.factor_data.date_count)} 日`);
  setText("factorDateRange", setup.factor_data.ready
    ? `${displayCompactDate(setup.factor_data.start_date)} 至 ${displayCompactDate(setup.factor_data.end_date)} · 竞价 ${number(setup.factor_data.auction_date_count)} 日`
    : "尚未达到 21 个交易日");
  setText("factorTaskStatus", setup.factor_data.ready ? `已就绪 · ${number(setup.factor_data.date_count)} 日` : "需要同步");
  setText("compilerStatus", "策略生成已就绪");
  setText(
    "screenerRunStatus",
    activeScreenerResult("smart") ? `已有结果 · ${(activeScreenerResult("smart").candidates || []).length} 只` : "等待执行",
  );
  setText("strategyCount", `${setup.strategies.filter((item) => item.formula?.meta?.library !== "curated").length} 套`);
  updateBacktestTaskStatus();
  selectScreenerMobileView(state.screenerMobileView);

  const selector = document.querySelector("#regimeSelector");
  selector.innerHTML = setup.regimes.map((item) => `
    <span class="regime-option ${item.id === state.selectedRegime ? "active" : ""}">${escapeHtml(item.label)}</span>
  `).join("");
  renderStrategyList();
  renderStrategySummary();
  renderScreenerMode();
  renderCuratedStrategyLibrary();
  renderQuantBuilder();
  renderScreenerProgress();
}

function renderStrategySummary() {
  const strategy = state.selectedStrategy;
  setText("activeStrategyHeading", strategy?.name || "--");
  setText("activeStrategyEditorHeading", strategy?.name || "--");
  setText("activeStrategyDescription", strategy?.description || "等待匹配当前市场阶段的策略。");
  setText("strategyStepStatus", strategy?.name || "等待匹配");
  document.querySelector("#activeStrategyRegimes").innerHTML = strategy
    ? `${strategy.regimes.map((item) => `<b>${escapeHtml(regimeLabel(item))}</b>`).join("")}<b class="neutral">${strategy.builtin ? "内置" : "自定义"}</b>`
    : "";
}

function selectScreenerMode(mode) {
  state.screenerMode = ["smart", "curated", "quant"].includes(mode) ? mode : "smart";
  state.screenerArchiveView = "latest";
  localStorage.setItem("xiaobaiScreenerMode", state.screenerMode);
  state.screenerMobileView = "strategy";
  renderScreenerMode();
  selectScreenerMobileView("strategy");
}

function renderScreenerMode() {
  const mode = state.screenerMode || "smart";
  document.querySelectorAll("[data-screener-mode]").forEach((button) => {
    const active = button.dataset.screenerMode === mode;
    button.classList.toggle("active", active);
    button.setAttribute("aria-selected", String(active));
  });
  document.querySelectorAll("[data-screener-panel]").forEach((panel) => {
    panel.hidden = panel.dataset.screenerPanel !== mode;
  });
  const results = document.querySelector("#screenerView .screener-results-view");
  const resultsSlot = document.querySelector(`[data-screener-results-slot="${mode}"]`);
  if (results && resultsSlot && results.parentElement !== resultsSlot) resultsSlot.append(results);
  const titles = { smart: "盘后候选结果", curated: "策略候选结果", quant: "自定义选股结果" };
  setText("screenerResultTitle", titles[mode]);
  renderScreenerResult();
}

function curatedStrategies() {
  return (state.screenerSetup?.strategies || []).filter((item) => item.formula?.meta?.library === "curated");
}

function activeCuratedStrategy() {
  const strategies = curatedStrategies();
  return strategies.find((item) => item.id === state.selectedCuratedStrategyId) || strategies[0] || null;
}

function curatedStrategySchool(strategy) {
  const category = String(strategy?.formula?.meta?.category || "");
  if (["红利价值", "质量价值", "现金流价值", "成长质量", "小盘质量"].includes(category)) return "基本面";
  if (["行业轮动", "形态突破", "趋势追踪"].includes(category)) return "趋势";
  if (["短线竞价", "连板接力", "低吸反核"].includes(category)) return "短线";
  if (["动量反转"].includes(category)) return "动量";
  if (["元策略", "多因子"].includes(category)) return "量化";
  if (["业绩事件", "热度观察"].includes(category)) return "事件";
  if (["资金席位"].includes(category)) return "资金";
  if (/红利|价值|质量|成长|财务|现金流/.test(category)) return "基本面";
  if (/趋势|轮动|突破/.test(category)) return "趋势";
  if (/竞价|连板|龙头|反核|首阴|反包|打板/.test(category)) return "短线";
  if (/动量|反转/.test(category)) return "动量";
  if (/因子|量化|元策略/.test(category)) return "量化";
  if (/事件|热度|公告|业绩/.test(category)) return "事件";
  if (/席位|资金/.test(category)) return "资金";
  return "其他";
}

function curatedSchoolIcon(school) {
  return {
    基本面: "circle-dollar-sign", 趋势: "trending-up", 短线: "zap",
    动量: "refresh-cw", 量化: "binary", 事件: "calendar-clock", 资金: "landmark", 其他: "boxes",
  }[school] || "boxes";
}

function curatedStrategyRunState(strategy, result) {
  const publishedRun = strategy?.published_run || {};
  if (publishedRun.status === "missing_data") {
    return { label: "数据不足", className: "missing", verifiedEmpty: false };
  }
  if (publishedRun.status === "no_signal") {
    return { label: "暂无信号", className: "quiet", verifiedEmpty: true };
  }
  if (publishedRun.status === "ready") {
    const count = (result?.candidates || []).length;
    return { label: `${count} 只候选`, className: "ready", verifiedEmpty: false };
  }
  const missingData = strategy?.missing_data || [];
  if (!strategy?.data_ready || missingData.length) {
    return { label: "数据不足", className: "missing", verifiedEmpty: false };
  }
  if (!result) return { label: "等待盘后", className: "pending", verifiedEmpty: false };
  const count = (result.candidates || []).length;
  if (count) return { label: `${count} 只候选`, className: "ready", verifiedEmpty: false };
  return { label: "暂无信号", className: "quiet", verifiedEmpty: true };
}

function renderCuratedStrategyLibrary() {
  if (!state.screenerSetup) return;
  const strategies = curatedStrategies();
  const categories = ["全部", ...new Set(strategies.map((item) => item.formula?.meta?.category || "其他"))];
  const schools = ["全部", "基本面", "趋势", "短线", "动量", "量化", "事件", "资金"];
  if (!categories.includes(state.curatedCategory)) state.curatedCategory = "全部";
  if (!schools.includes(state.curatedSchool)) state.curatedSchool = "全部";
  setText("curatedStrategyCount", `${strategies.length} 套`);
  const categorySelect = document.querySelector("#curatedCategoryFilter");
  categorySelect.innerHTML = categories.map((category) => `
    <option value="${escapeHtml(category)}" ${category === state.curatedCategory ? "selected" : ""}>${escapeHtml(category)}</option>
  `).join("");
  document.querySelector("#curatedSchoolFilters").innerHTML = schools.map((school) => {
    const count = school === "全部" ? strategies.length : strategies.filter((item) => curatedStrategySchool(item) === school).length;
    return `<button class="${school === state.curatedSchool ? "active" : ""}" type="button" data-curated-school="${escapeHtml(school)}" aria-pressed="${school === state.curatedSchool}">${escapeHtml(school)}<small>${count}</small></button>`;
  }).join("");
  document.querySelectorAll("[data-curated-view]").forEach((button) => {
    const active = button.dataset.curatedView === state.curatedViewMode;
    button.classList.toggle("active", active);
    button.setAttribute("aria-pressed", String(active));
  });
  const query = state.curatedQuery;
  const visible = strategies.filter((item) => {
    const meta = item.formula?.meta || {};
    const categoryMatch = state.curatedCategory === "全部" || meta.category === state.curatedCategory;
    const school = curatedStrategySchool(item);
    const schoolMatch = state.curatedSchool === "全部" || school === state.curatedSchool;
    const queryMatch = !query || `${item.name} ${item.description} ${meta.category} ${school} ${meta.suitable_environment} ${meta.failure_risk}`.toLocaleLowerCase("zh-CN").includes(query);
    return categoryMatch && schoolMatch && queryMatch;
  });
  const list = document.querySelector("#curatedStrategyList");
  list.classList.toggle("is-grid", state.curatedViewMode === "grid");
  list.innerHTML = visible.length ? visible.map((strategy) => {
    const meta = strategy.formula?.meta || {};
    const school = curatedStrategySchool(strategy);
    const rank = strategies.findIndex((item) => item.id === strategy.id) + 1;
    const resultKey = screenerResultKey(screenerResultContext("curated", null, {
      regime: strategy.regimes[0] || state.selectedRegime,
      strategyId: strategy.id,
      strategyName: strategy.name,
    }));
    const result = state.screenerResultStore[resultKey]?.result;
    const runState = curatedStrategyRunState(strategy, result);
    return `<article class="curated-strategy-card ${strategy.id === activeCuratedStrategy()?.id ? "active" : ""}" data-curated-strategy="${strategy.id}">
      <span class="curated-strategy-icon" aria-hidden="true"><i data-lucide="${curatedSchoolIcon(school)}"></i></span>
      <span class="curated-card-head"><i class="curated-strategy-rank">${String(rank).padStart(2, "0")}</i><span><strong>${escapeHtml(strategy.name)}</strong><small>${escapeHtml(school)} · ${escapeHtml(meta.category || "策略")}</small></span><em class="curated-card-result ${runState.className}">${escapeHtml(runState.label)}</em></span>
      <span class="curated-card-tags"><em>${escapeHtml(meta.quality || "--")}</em><em>${escapeHtml(meta.frequency || "--")}</em><em>风险 ${escapeHtml(meta.risk || "--")}</em></span>
    </article>`;
  }).join("") : emptyStateHtml("没有符合条件的策略");
  renderCuratedStrategyDetail();
}

function renderCuratedStrategyDetail() {
  const strategy = activeCuratedStrategy();
  if (!strategy) return;
  const formula = strategy.formula || {};
  const meta = formula.meta || {};
  const result = activeScreenerResult("curated");
  const resultMeta = result?.meta || {};
  const health = resultMeta.health || {};
  const runState = curatedStrategyRunState(strategy, result);
  setText("curatedStrategyCategory", meta.category || "精选策略");
  setText("curatedStrategyName", strategy.name);
  setText("curatedStrategyDescription", strategy.description);
  document.querySelector("#curatedStrategyBadges").innerHTML = [
    `质量 ${meta.quality || "--"}`, meta.frequency || "--", `风险 ${meta.risk || "--"}`,
    meta.data_group || "行情因子",
  ].map((value) => `<span>${escapeHtml(value)}</span>`).join("");
  setText("curatedSuitableEnvironment", meta.suitable_environment || "以策略条件为准");
  setText("curatedFailureRisk", meta.failure_risk || "策略可能随市场结构变化而失效");
  const filters = formula.filters || [];
  setText("curatedFilterCount", `${filters.length} 项`);
  document.querySelector("#curatedFilterList").innerHTML = filters.map((item) => `
    <div class="curated-rule-row"><span>${escapeHtml(factorLabel(item.field))}</span><strong>${escapeHtml(formatRuleValue(item))}</strong></div>
  `).join("");
  const scores = formula.score || [];
  const total = scores.reduce((sum, item) => sum + number(item.weight), 0) || 1;
  setText("curatedWeightTotal", `${formatNumber(total * 100, 0)}%`);
  document.querySelector("#curatedScoreList").innerHTML = scores.map((item) => {
    const percent = number(item.weight) / total * 100;
    return `<div class="curated-score-row"><span>${escapeHtml(factorLabel(item.field))}</span><span class="curated-score-track"><i style="width:${Math.min(100, percent)}%"></i></span><strong>${formatNumber(percent, 0)}%</strong></div>`;
  }).join("");
  const candidateCount = (result?.candidates || []).length;
  const statusLabel = runState.className === "ready" ? "运行正常" : runState.label;
  const statusClass = runState.className;
  let updatedLabel = "--";
  if (resultMeta.updated_at) {
    const updated = new Date(resultMeta.updated_at);
    if (!Number.isNaN(updated.getTime())) {
      updatedLabel = `${String(updated.getMonth() + 1).padStart(2, "0")}-${String(updated.getDate()).padStart(2, "0")} ${updated.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", hour12: false })}`;
    }
  }
  document.querySelector("#curatedHealthMetrics").innerHTML = [
    ["运行状态", statusLabel, statusClass],
    ["批次信号", result ? `${candidateCount} 只` : "--", ""],
    ["字段覆盖", health.coverage != null ? `${formatNumber(health.coverage, 1)}%` : strategy.data_ready ? "数据已就绪" : "--", ""],
    ["最近更新", updatedLabel, ""],
  ].map(([label, value, className]) => `<div><span>${escapeHtml(label)}</span><strong class="${className}">${escapeHtml(value)}</strong></div>`).join("");
  const status = document.querySelector("#curatedDataStatus");
  status.classList.toggle("missing", statusClass === "missing");
  const publishedRun = strategy.published_run || {};
  if (publishedRun.status === "missing_data") {
    status.innerHTML = `<i data-lucide="circle-alert"></i><span><strong>缺少必需数据</strong><small>${escapeHtml(publishedRun.detail || "等待后台同步")}</small></span>`;
  } else if (publishedRun.status === "no_signal") {
    status.innerHTML = `<i data-lucide="circle-check"></i><span><strong>数据完整，暂无信号</strong><small>必需数据已完整，本日没有股票同时满足 ${filters.length} 项准入条件</small></span>`;
  } else if (result) {
    status.innerHTML = `<i data-lucide="database"></i><span><strong>盘后批次已发布</strong><small>${health.required_field_count != null ? `已核验 ${number(health.required_field_count)} 项因子 · ${number(health.complete_rows)} 只股票` : "盘后定格结果已载入"}</small></span>`;
  } else if (!strategy.data_ready) {
    status.innerHTML = `<i data-lucide="circle-alert"></i><span><strong>数据尚未完备</strong><small>${escapeHtml((strategy.missing_data || []).join("、") || "等待后台同步")}</small></span>`;
  } else {
    status.innerHTML = "<i data-lucide=\"clock-3\"></i><span><strong>等待成功批次</strong><small>运行中或失败的批次不会覆盖上一成功结果</small></span>";
  }
  refreshIcons();
}

function factorLabel(field) {
  return state.screenerSetup?.factor_fields?.find((item) => item.id === field)?.label || field;
}

function formatRuleValue(item) {
  const operator = { between: "介于", ">=": "不低于", "<=": "不高于", ">": "高于", "<": "低于", "==": "等于" }[item.op] || item.op;
  const value = Array.isArray(item.value) ? item.value.join(" ～ ") : item.value;
  return `${operator} ${value}`;
}

function groupedFactorOptions(selected = "") {
  return (state.screenerSetup?.factor_groups || []).map((group) => `
    <optgroup label="${escapeHtml(group.name)}">${group.fields.map((field) => `<option value="${escapeHtml(field.id)}" ${field.id === selected ? "selected" : ""}>${escapeHtml(field.label)}</option>`).join("")}</optgroup>
  `).join("");
}

function quantId() {
  return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
}

function resetQuantBuilder(render = true) {
  state.quantFilters = [
    { id: quantId(), field: "amount_billion", op: ">=", value: "1" },
    { id: quantId(), field: "above_ma20", op: "==", value: "1" },
  ];
  state.quantScores = [
    { id: quantId(), field: "relative_strength", weight: 30, direction: "desc" },
    { id: quantId(), field: "sector_strength", weight: 25, direction: "desc" },
    { id: quantId(), field: "volume_ratio_5d", weight: 20, direction: "desc" },
    { id: quantId(), field: "amount_billion", weight: 15, direction: "desc" },
    { id: quantId(), field: "volatility_10d", weight: 10, direction: "asc" },
  ];
  if (render) renderQuantBuilder();
}

function addQuantFilter() {
  const used = new Set(state.quantFilters.map((item) => item.field));
  const field = state.screenerSetup.factor_fields.find((item) => !used.has(item.id))?.id || "pct_chg";
  state.quantFilters.push({ id: quantId(), field, op: ">=", value: "0" });
  renderQuantBuilder();
}

function addQuantScore() {
  const used = new Set(state.quantScores.map((item) => item.field));
  const field = state.screenerSetup.factor_fields.find((item) => !used.has(item.id))?.id || "pct_chg";
  state.quantScores.push({ id: quantId(), field, weight: 10, direction: "desc" });
  renderQuantBuilder();
}

function renderQuantBuilder() {
  if (!state.screenerSetup) return;
  document.querySelector("#quantFilterRows").innerHTML = state.quantFilters.map((item) => `
    <div class="quant-rule-row quant-filter-row" data-quant-filter="${item.id}">
      <select aria-label="过滤因子" data-quant-key="field">${groupedFactorOptions(item.field)}</select>
      <select aria-label="比较方式" data-quant-key="op">${(state.screenerSetup.operators || []).map((op) => `<option value="${op}" ${op === item.op ? "selected" : ""}>${escapeHtml({ between: "区间", ">=": "≥", "<=": "≤", ">": ">", "<": "<", "==": "=" }[op] || op)}</option>`).join("")}</select>
      <input class="quant-value-input" data-quant-key="value" aria-label="条件值" type="text" value="${escapeHtml(item.value)}" placeholder="区间用逗号分隔">
      <button class="quant-remove-button" type="button" data-quant-action="remove" aria-label="删除过滤条件"><i data-lucide="trash-2"></i></button>
    </div>
  `).join("");
  document.querySelector("#quantScoreRows").innerHTML = state.quantScores.map((item) => `
    <div class="quant-rule-row quant-score-row" data-quant-score="${item.id}">
      <span class="quant-factor-identity"><i aria-hidden="true">✓</i><span><select aria-label="评分因子" data-quant-key="field">${groupedFactorOptions(item.field)}</select><button type="button" data-quant-action="direction" data-direction="${item.direction === "desc" ? "asc" : "desc"}">${item.direction === "desc" ? "数值越高越优" : "数值越低越优"}</button></span></span>
      <label class="quant-weight-control">
        <span class="visually-hidden">权重百分比</span>
        <input data-quant-key="weight" aria-label="权重百分比" type="range" min="1" max="100" step="1" value="${number(item.weight)}">
        <output>${number(item.weight)}%</output>
      </label>
      <button class="quant-remove-button" type="button" data-quant-action="remove" aria-label="删除评分因子"><i data-lucide="trash-2"></i></button>
    </div>
  `).join("");
  renderQuantSummary();
  refreshIcons();
}

function handleQuantBuilderInput(event) {
  const row = event.target.closest("[data-quant-filter], [data-quant-score]");
  const key = event.target.dataset.quantKey;
  if (!row || !key) return;
  const collection = row.dataset.quantFilter ? state.quantFilters : state.quantScores;
  const id = row.dataset.quantFilter || row.dataset.quantScore;
  const item = collection.find((entry) => entry.id === id);
  if (!item) return;
  item[key] = key === "weight" ? number(event.target.value) : event.target.value;
  if (key === "weight") {
    const output = event.target.closest(".quant-weight-control")?.querySelector("output");
    if (output) output.textContent = `${number(event.target.value)}%`;
  }
  renderQuantSummary();
}

function handleQuantBuilderClick(event) {
  const button = event.target.closest("[data-quant-action]");
  if (!button) return;
  const row = button.closest("[data-quant-filter], [data-quant-score]");
  const isFilter = Boolean(row?.dataset.quantFilter);
  const id = row?.dataset.quantFilter || row?.dataset.quantScore;
  const collection = isFilter ? state.quantFilters : state.quantScores;
  const item = collection.find((entry) => entry.id === id);
  if (button.dataset.quantAction === "remove") {
    if (!isFilter && collection.length <= 1) {
      showToast("至少保留一个评分因子");
      return;
    }
    const index = collection.findIndex((entry) => entry.id === id);
    if (index >= 0) collection.splice(index, 1);
    renderQuantBuilder();
  } else if (button.dataset.quantAction === "direction" && item) {
    item.direction = button.dataset.direction;
    renderQuantBuilder();
  }
}

function buildQuantFormula() {
  const filters = state.quantFilters.map((item) => {
    let value;
    if (item.op === "between") {
      value = String(item.value).split(/[,，~～]/).map((part) => Number(part.trim()));
      if (value.length !== 2 || value.some((part) => !Number.isFinite(part))) throw new Error(`${factorLabel(item.field)}需要两个有效区间值`);
      if (value[0] > value[1]) value.reverse();
    } else {
      value = Number(item.value);
      if (!Number.isFinite(value)) throw new Error(`${factorLabel(item.field)}的条件值无效`);
    }
    return { field: item.field, op: item.op, value };
  });
  const score = state.quantScores.map((item) => {
    const weight = number(item.weight) / 100;
    if (weight <= 0 || weight > 1) throw new Error(`${factorLabel(item.field)}的权重应为1%至100%`);
    return { field: item.field, weight, direction: item.direction };
  });
  return {
    meta: { library: "custom", category: "量化公式", frequency: "按需", risk: "自定义", data_group: "组合因子" },
    universe: {
      exclude_st: document.querySelector("#quantExcludeSt").checked,
      listed_days_min: Math.max(0, Math.min(5000, number(document.querySelector("#quantListedDays").value))),
    },
    filters,
    score,
    limit: Math.max(1, Math.min(50, number(document.querySelector("#quantLimit").value))),
    min_score: Math.max(0, Math.min(1, number(document.querySelector("#quantMinScore").value) / 100)),
  };
}

function formulaMissingData(formula) {
  const health = state.screenerSetup?.factor_data?.health || {};
  const fields = new Set([...(formula.filters || []), ...(formula.score || [])].map((item) => item.field));
  const missing = [];
  if (!state.screenerSetup?.factor_data?.ready) missing.push("基础行情");
  if (["pe_ttm", "pb", "ps_ttm", "dividend_yield_ttm", "total_mv_billion"].some((field) => fields.has(field)) && !health.valuation) missing.push("估值数据");
  if (["roe", "roa", "roic", "gross_margin", "netprofit_yoy", "revenue_yoy", "ocf_to_opincome"].some((field) => fields.has(field)) && !health.fundamental) missing.push("财务质量");
  if (fields.has("dividend_years") && !health.dividend_history) missing.push("历年分红");
  if (["auction_change", "auction_amount_million", "auction_turnover_rate", "auction_volume_ratio"].some((field) => fields.has(field)) && !health.auction) missing.push("竞价数据");
  return missing;
}

function renderQuantSummary() {
  if (!state.screenerSetup) return;
  const total = state.quantScores.reduce((sum, item) => sum + number(item.weight), 0);
  setText("quantWeightTotal", `${formatNumber(total, 0)}%`);
  const bar = document.querySelector("#quantWeightBar");
  bar.style.width = `${Math.min(100, total)}%`;
  bar.style.background = Math.abs(total - 100) < 0.01 ? "#2563eb" : "#d97706";
  const message = document.querySelector("#quantValidationMessage");
  try {
    const formula = buildQuantFormula();
    const missing = formulaMissingData(formula);
    message.classList.toggle("error", Boolean(missing.length));
    message.textContent = missing.length ? `需要先同步：${missing.join("、")}` : "公式有效，可执行并生成逐股贡献解释。";
    document.querySelector("#quantRunButton").disabled = Boolean(missing.length);
  } catch (error) {
    message.classList.add("error");
    message.textContent = error.message;
    document.querySelector("#quantRunButton").disabled = true;
  }
}

function renderScreenerProgress() {
  const hasSetup = Boolean(state.screenerSetup?.regime);
  const hasStrategy = Boolean(state.selectedStrategy);
  const hasResult = Boolean(activeScreenerResult("smart"));
  const states = {
    regime: hasSetup ? "complete" : "current",
    strategy: hasStrategy ? "complete" : hasSetup ? "current" : "pending",
    run: state.screenerRunning ? "current" : hasResult ? "complete" : hasStrategy ? "current" : "pending",
    result: hasResult ? "current" : "pending",
  };
  const steps = [...document.querySelectorAll("[data-screener-step]")];
  steps.forEach((step, index) => {
    const status = states[step.dataset.screenerStep] || "pending";
    step.dataset.state = status;
    if (status === "current") step.setAttribute("aria-current", "step");
    else step.removeAttribute("aria-current");
    const line = step.nextElementSibling;
    if (line?.classList.contains("step-line")) line.classList.toggle("complete", status === "complete" && index < steps.length - 1);
  });
}

function openStrategyDrawer(target = "editor") {
  const drawer = document.querySelector("#strategyDrawer");
  openModalDialog(drawer);
  requestAnimationFrame(() => {
    const focusTarget = target === "library"
      ? document.querySelector("#strategyList .strategy-item.active") || document.querySelector("#strategyList .strategy-item")
      : document.querySelector("#strategyNameInput");
    focusTarget?.focus();
  });
}

function openCustomStrategyDrawer() {
  if (!state.customStrategyDraft) {
    state.customStrategyDraft = {
      id: null,
      builtin: false,
      name: "自定义选股策略",
      description: "",
      regimes: [state.selectedRegime],
      formula: buildQuantFormula(),
    };
  }
  populateStrategyEditor(state.customStrategyDraft);
  renderStrategyList();
  openStrategyDrawer("editor");
}

function selectScreenerMobileView(view) {
  state.screenerMobileView = view === "results" ? "results" : "strategy";
  const workspace = document.querySelector("#screenerView");
  workspace.classList.toggle("mobile-strategy", state.screenerMobileView === "strategy");
  workspace.classList.toggle("mobile-results", state.screenerMobileView === "results");
  document.querySelectorAll("[data-screener-mobile-view]").forEach((button) => {
    const active = button.dataset.screenerMobileView === state.screenerMobileView;
    button.classList.toggle("active", active);
    button.setAttribute("aria-selected", String(active));
  });
}

function updateBacktestTaskStatus() {
  setText("backtestTaskStatus", activeScreenerResult("smart") ? "结果已归档" : "等待盘后生成");
  renderScreenerProgress();
}

function renderStrategyList() {
  const list = document.querySelector("#strategyList");
  const strategies = state.screenerSetup.strategies.filter((item) => !item.builtin && item.formula?.meta?.library !== "curated");
  list.innerHTML = strategies.map((strategy) => `
    <button type="button" class="strategy-item ${strategy.id === state.customStrategyDraft?.id ? "active" : ""}" data-strategy-id="${strategy.id}">
      <strong>${escapeHtml(strategy.name)}</strong><span>${escapeHtml(strategy.description || "--")}</span>
      <small>${strategy.regimes.map((item) => regimeLabel(item)).join(" / ")}</small>
    </button>
  `).join("") || emptyStateHtml("暂无已保存的自定义公式");
  list.querySelectorAll("[data-strategy-id]").forEach((button) => {
    button.addEventListener("click", () => {
      state.customStrategyDraft = state.screenerSetup.strategies.find((item) => item.id === number(button.dataset.strategyId));
      populateStrategyEditor(state.customStrategyDraft);
      renderStrategyList();
    });
  });
}

function populateStrategyEditor(strategy) {
  const deleteButton = document.querySelector("#deleteStrategyButton");
  deleteButton.hidden = !strategy?.id || Boolean(strategy.builtin);
  if (!strategy) {
    setText("activeStrategyEditorHeading", "--");
    return;
  }
  setText("activeStrategyEditorHeading", strategy.name || "未命名策略");
  document.querySelector("#strategyNameInput").value = strategy.name || "";
  document.querySelector("#strategyDescriptionInput").value = strategy.description || "";
  document.querySelector("#strategyPrompt").value = strategy.builtin ? strategy.description || "" : document.querySelector("#strategyPrompt").value;
  document.querySelector("#formulaEditor").value = JSON.stringify(strategy.formula, null, 2);
}

async function compileStrategy() {
  const prompt = document.querySelector("#strategyPrompt").value.trim();
  const button = document.querySelector("#compileStrategyButton");
  button.disabled = true;
  setText("compilerStatus", "正在编译");
  setStatus("正在编译选股策略");
  try {
    const payload = await apiRequest("/api/screener/compile", "POST", {
      prompt,
      regime: state.selectedRegime,
    });
    const strategy = payload.strategy;
    state.customStrategyDraft = { ...strategy, id: null, builtin: false };
    document.querySelector("#deleteStrategyButton").hidden = true;
    document.querySelector("#strategyNameInput").value = strategy.name;
    document.querySelector("#strategyDescriptionInput").value = strategy.description;
    document.querySelector("#formulaEditor").value = JSON.stringify(strategy.formula, null, 2);
    setText("compilerStatus", "策略生成完成");
    if (strategy.notice) showToast(strategy.notice);
    setStatus("选股策略已编译");
  } catch (error) {
    showToast(error.message);
    setStatus("策略编译失败");
    setText("compilerStatus", "编译失败");
  } finally {
    button.disabled = false;
  }
}

async function saveCurrentStrategy() {
  try {
    const formula = parseFormulaEditor();
    const payload = await apiRequest("/api/screener/strategies", "POST", {
      name: document.querySelector("#strategyNameInput").value,
      description: document.querySelector("#strategyDescriptionInput").value,
      regimes: [state.selectedRegime],
      formula,
    });
    state.screenerSetup.strategies = payload.strategies;
    state.customStrategyDraft = payload.strategies.find((item) => item.id === payload.id);
    renderStrategyList();
    populateStrategyEditor(state.customStrategyDraft);
    showToast("自定义策略已保存");
  } catch (error) {
    showToast(error.message);
  }
}

async function deleteCurrentStrategy() {
  const strategy = state.customStrategyDraft;
  if (!strategy?.id || strategy.builtin) {
    showToast("只能删除已保存的自定义策略");
    return;
  }
  if (!window.confirm(`确定删除策略“${strategy.name}”吗？此操作不可撤销。`)) return;

  const button = document.querySelector("#deleteStrategyButton");
  button.disabled = true;
  try {
    const payload = await apiRequest(`/api/screener/strategies/${strategy.id}`, "DELETE");
    state.screenerSetup.strategies = payload.strategies;
    state.customStrategyDraft = null;
    renderStrategyList();
    openCustomStrategyDrawer();
    showToast("自定义策略已删除");
  } catch (error) {
    showToast(error.message || "策略删除失败");
  } finally {
    button.disabled = false;
  }
}

async function runQuantStrategy() {
  let formula;
  try {
    formula = buildQuantFormula();
  } catch (error) {
    showToast(error.message);
    return;
  }
  await executeScreenerFormula({
    mode: "quant",
    formula,
    strategyName: "自定义选股公式",
    regime: state.selectedRegime,
    runBacktest: document.querySelector("#quantBacktestToggle").checked,
    button: document.querySelector("#quantRunButton"),
    loadingText: "正在执行自定义公式并计算因子贡献",
  });
}

function saveQuantAsStrategy() {
  try {
    const formula = buildQuantFormula();
    state.customStrategyDraft = {
      id: null,
      builtin: false,
      name: "自定义选股策略",
      description: "由自定义因子工作台生成，可在高级公式中继续调整。",
      regimes: [state.selectedRegime],
      formula,
    };
    populateStrategyEditor(state.customStrategyDraft);
    document.querySelector("#strategyPrompt").value = "自定义因子工作台生成的选股公式";
    openStrategyDrawer("editor");
  } catch (error) {
    showToast(error.message);
  }
}

async function executeScreenerFormula({ mode, formula, strategyName, strategyId = null, regime, runBacktest, button, loadingText }) {
  if (!state.screenerSetup?.factor_data?.ready) {
    showToast("请先同步至少 21 个交易日的因子数据");
    return;
  }
  const missing = formulaMissingData(formula);
  if (missing.length) {
    showToast(`请先同步${missing.join("、")}`);
    return;
  }
  const executionMode = ["smart", "curated", "quant"].includes(mode) ? mode : state.screenerMode;
  button.disabled = true;
  state.screenerRunning = true;
  state.screenerRunningMode = executionMode;
  if (executionMode === "smart") {
    setText("screenerRunStatus", "正在计算");
    setText("backtestTaskStatus", runBacktest ? "正在回测" : "本次不执行");
  }
  setLoading(true, loadingText, "screener");
  setStatus(`正在执行${strategyName}`);
  try {
    const payload = await apiRequest("/api/screener/run", "POST", {
      trade_date: elements.tradeDate.value,
      regime,
      strategy_name: strategyName,
      formula,
      mode: executionMode,
      run_backtest: runBacktest,
    });
    setScreenerResult(executionMode, payload.result, { regime, strategyId, strategyName });
    renderScreenerResult();
    if (executionMode === "smart") setText("screenerRunStatus", `完成 · ${payload.result.candidates.length} 只`);
    updateBacktestTaskStatus();
    if (window.innerWidth <= 767) selectScreenerMobileView("results");
    setStatus(`${strategyName}完成 · ${payload.result.candidates.length} 只候选`);
  } catch (error) {
    showToast(error.message);
    setStatus("选股执行失败");
    if (executionMode === "smart") setText("screenerRunStatus", "执行失败");
    updateBacktestTaskStatus();
  } finally {
    state.screenerRunning = false;
    state.screenerRunningMode = "";
    setLoading(false);
    button.disabled = false;
    updateBacktestTaskStatus();
  }
}

function renderScreenerResult() {
  renderScreenerArchiveTabs();
  renderScreenerBatchNotice();
  if (state.screenerArchiveView === "latest") {
    renderLatestScreenerResult();
  } else {
    renderScreenerArchiveResult();
  }
}

function renderScreenerArchiveTabs() {
  document.querySelectorAll("[data-screener-archive-view]").forEach((button) => {
    const active = button.dataset.screenerArchiveView === state.screenerArchiveView;
    button.classList.toggle("active", active);
    button.setAttribute("aria-selected", String(active));
  });
}

function renderScreenerBatchNotice() {
  const notice = document.querySelector("#screenerBatchNotice");
  const setup = state.screenerSetup || {};
  const batch = setup.published_batch;
  const requestStatus = setup.automatic_status || {};
  notice.classList.toggle("warning", Boolean(batch?.is_fallback) || ["failed", "partial"].includes(requestStatus.status));
  if (state.screenerMode === "quant") {
    notice.innerHTML = "<strong>手动执行</strong><span>自定义选股结果独立保存，不会覆盖阶段选股或策略选股。</span>";
    notice.hidden = false;
    return;
  }
  if (!batch) {
    const statusText = {
      running: "盘后候选正在生成，完成前不会发布局部结果。",
      failed: "最近一次盘后任务失败，尚无可展示的成功批次。",
      partial: "最近一次盘后任务未完整完成，尚无可展示的成功批次。",
      pending: "盘后候选尚未生成。",
    }[requestStatus.status] || "尚无成功发布的盘后候选批次。";
    notice.innerHTML = `<strong>尚未发布</strong><span>${escapeHtml(statusText)}</span>`;
    notice.hidden = false;
    return;
  }
  const completed = number(batch.completed_count);
  const skipped = number(batch.skipped_count);
  const detail = batch.notice
    || `盘后候选已完整发布${completed ? ` · 完成 ${completed} 套策略` : ""}${skipped ? ` · ${skipped} 套因数据不足跳过` : ""}`;
  const time = batch.finished_at ? ` · ${formatTimestamp(batch.finished_at)}` : "";
  notice.innerHTML = `<strong>候选基准 ${escapeHtml(displayCompactDate(batch.trade_date))}${time}</strong><span>${escapeHtml(detail)}</span>`;
  notice.hidden = false;
}

function selectedScreenerArchiveRows() {
  const mode = state.screenerMode || "smart";
  const source = state.screenerArchiveView === "active"
    ? state.screenerSetup?.active_signals || []
    : state.screenerSetup?.candidate_history || [];
  const strategy = currentScreenerStrategy(mode);
  return source.filter((row) => {
    if (row.mode !== mode) return false;
    if (mode === "quant") return true;
    return (row.hits || []).some((hit) => hit.strategy_name === strategy?.name);
  }).map((row) => {
    let hits = row.hits || [];
    if (mode !== "quant") hits = hits.filter((hit) => hit.strategy_name === strategy?.name);
    if (state.screenerArchiveView === "active") hits = hits.filter((hit) => hit.active);
    const latestHit = [...hits].sort((left, right) => String(right.selection_date).localeCompare(String(left.selection_date)))[0] || {};
    const active = hits.some((hit) => hit.active);
    const labels = [...new Set(hits.map((hit) => hit.validity?.label).filter(Boolean))];
    return {
      ...row,
      selection_date: latestHit.selection_date || row.selection_date,
      score_display: latestHit.score_display ?? row.score_display,
      matched_strategies: [...new Set(hits.map((hit) => hit.strategy_name).filter(Boolean))],
      status: active ? "持续有效" : "已到期",
      active,
      validity_label: labels.join(" / ") || row.validity_label || "--",
      valid_until: latestHit.valid_until || "",
      remaining_trading_days: latestHit.remaining_trading_days,
    };
  });
}

function renderScreenerArchiveResult() {
  const mode = state.screenerMode || "smart";
  const rows = selectedScreenerArchiveRows();
  setText("screenerResultTitle", state.screenerArchiveView === "active" ? "持续有效信号" : "历史入选记录");
  const latestTable = document.querySelector("#screenerLatestTable");
  const archiveTable = document.querySelector("#screenerArchiveTable");
  latestTable.hidden = true;
  archiveTable.hidden = false;
  setText("screenerResultCount", `${rows.length} 只`);
  const modeLabels = { smart: "阶段选股", curated: "策略选股", quant: "自定义选股" };
  const strategy = currentScreenerStrategy(mode);
  const source = document.querySelector("#screenerResultSource");
  source.textContent = [modeLabels[mode], strategy?.name].filter(Boolean).join(" · ");
  source.hidden = false;
  setText(
    "screenerDisclaimer",
    state.screenerArchiveView === "active"
      ? "持续有效按策略频率与阶段变化计算，不代表未来收益"
      : "历史入选仅保留当时策略结果，不使用未来数据回写",
  );
  const body = document.querySelector("#screenerArchiveTableBody");
  body.innerHTML = rows.map((row) => {
    const validity = row.valid_until
      ? `${row.validity_label} · 至 ${displayCompactDate(row.valid_until)}`
      : row.validity_label;
    const matched = (row.matched_strategies || []).join("、");
    return `<tr data-code="${escapeHtml(row.code)}">
      <td class="num">${escapeHtml(displayCompactDate(row.selection_date))}</td>
      <td><span class="stock-cell"><strong class="stock-name sname">${escapeHtml(row.name)}</strong><small class="stock-code scode">${escapeHtml(row.code)}</small></span></td>
      <td>${escapeHtml(row.sector)}</td>
      <td><span class="screener-signal-status ${row.active ? "active" : "expired"}">${escapeHtml(row.status)}</span></td>
      <td class="screener-archive-strategies" title="${escapeHtml(matched)}">${escapeHtml(matched)}</td>
      <td class="number num">${row.score_display == null ? "" : formatNumber(row.score_display, 1)}</td>
      <td>${escapeHtml(validity)}</td>
    </tr>`;
  }).join("");
  bindStockRows(body);
  const empty = document.querySelector("#screenerEmpty");
  empty.textContent = state.screenerArchiveView === "active"
    ? "当前策略暂无仍在有效期内的信号"
    : "当前策略暂无历史入选记录";
  empty.hidden = rows.length > 0;
  renderBacktest(null);
  updateBacktestTaskStatus();
}

function latestScreenerEmptyMessage(mode) {
  if (mode === "quant") return "尚未执行自定义选股";
  const strategy = currentScreenerStrategy(mode);
  const publishedRun = strategy?.published_run || {};
  if (publishedRun.status === "missing_data") {
    return `缺少必需数据：${publishedRun.detail || "等待后台同步"}`;
  }
  if (publishedRun.status === "no_signal") return "数据完整，暂无符合条件个股";
  const status = state.screenerSetup?.automatic_status?.status;
  if (status === "running") return "盘后候选正在生成，成功发布前不会显示局部结果";
  if (status === "failed") return "本次盘后任务失败，当前策略暂无已发布结果";
  if (status === "partial") return "本次盘后任务未完整完成，当前策略暂无已发布结果";
  return "尚无成功发布的盘后候选结果";
}

function renderLatestScreenerResult() {
  const mode = state.screenerMode || "smart";
  const titles = { smart: "盘后候选结果", curated: "策略候选结果", quant: "自定义选股结果" };
  setText("screenerResultTitle", titles[mode]);
  const result = activeScreenerResult(mode);
  const context = activeScreenerResultContext(mode);
  const source = document.querySelector("#screenerResultSource");
  const emptyMessage = latestScreenerEmptyMessage(mode);
  document.querySelector("#screenerLatestTable").hidden = false;
  document.querySelector("#screenerArchiveTable").hidden = true;
  if (!result) {
    setText("screenerResultCount", "0 只");
    source.hidden = true;
    source.textContent = "";
    setText("screenerDisclaimer", "历史统计不代表未来收益");
    document.querySelector("#screenerTableBody").innerHTML = "";
    document.querySelector("#screenerEmpty").textContent = emptyMessage;
    document.querySelector("#screenerEmpty").hidden = false;
    renderBacktest(null);
    if (mode === "smart") setText("screenerRunStatus", "等待执行");
    updateBacktestTaskStatus();
    renderScreenerProgress();
    return;
  }
  const candidates = result.candidates || [];
  setText("screenerResultCount", `${candidates.length} 只`);
  const modeLabels = { smart: "阶段选股", curated: "策略选股", quant: "自定义选股" };
  const sourceParts = [modeLabels[mode]];
  if (mode === "smart" && context?.regime) sourceParts.push(regimeLabel(context.regime));
  sourceParts.push(mode === "quant" ? "自定义因子权重" : context?.strategyName || result.meta?.strategy_name || "未命名策略");
  source.textContent = sourceParts.join(" · ");
  source.hidden = false;
  const meta = result.meta || {};
  setText(
    "screenerDisclaimer",
    meta.realtime
      ? `盘中行情 · 历史样本截至 ${displayCompactDate(meta.history_cutoff)} · ${result.disclaimer}`
      : `盘后数据 ${displayCompactDate(meta.trade_date)} · ${result.disclaimer}`,
  );
  const empty = document.querySelector("#screenerEmpty");
  const verifiedEmpty = mode === "curated" ? "暂无符合条件个股" : "数据完整，暂无符合条件个股";
  empty.textContent = mode === "quant"
    ? emptyMessage
    : `${verifiedEmpty}${mode === "curated" ? "（必需数据已完整）" : ""}`;
  empty.hidden = candidates.length > 0;
  const body = document.querySelector("#screenerTableBody");
  const runId = number(meta.run_id);
  body.innerHTML = candidates.map((row, index) => `
    <tr data-code="${escapeHtml(row.code)}"><td class="row-number num muted">${index + 1}</td>
      <td><span class="stock-cell"><strong class="stock-name sname">${escapeHtml(row.name)}</strong><small class="stock-code scode">${escapeHtml(row.code)}</small></span></td><td>${escapeHtml(row.sector)}</td>
      <td class="number num streak-value">${formatNumber(row.score_display, 1)}</td>
      <td class="number num probability-value"><strong>${row.historical_probability === null ? "" : formatNumber(row.historical_probability, 1)}</strong><small>${number(row.probability_samples)} 个样本</small></td>
      <td class="number num ${changeClass(row.pct_chg)}">${signed(row.pct_chg)}</td>
      <td class="number num ${changeClass(row.return_5d)}">${signed(row.return_5d)}</td>
      <td class="number num">${formatNumber(row.volume_ratio_5d, 2)}</td><td class="number num">${formatNumber(row.sector_strength, 1)}</td>
      <td class="reason-column" title="${escapeHtml(row.reason)}">${escapeHtml(row.reason)}</td>
      <td class="risk-cell" title="${escapeHtml(row.risk_flags.join("；"))}">${escapeHtml(row.risk_flags.join("；"))}</td>
      <td><span class="screener-row-actions"><button class="table-action" type="button" data-screen-detail="${escapeHtml(row.code)}">详情</button><button class="table-action tracking-action ${isCandidateTracked(runId, row.code) ? "tracked" : ""}" type="button" data-add-tracking="${escapeHtml(row.code)}" ${!runId || isCandidateTracked(runId, row.code) ? "disabled" : ""}>${isCandidateTracked(runId, row.code) ? "已跟踪" : "加入跟踪"}</button></span></td></tr>
  `).join("");
  body.querySelectorAll("[data-screen-detail]").forEach((button) => {
    button.addEventListener("click", () => {
      const row = candidates.find((item) => item.code === button.dataset.screenDetail);
      openStock(row.code, row);
    });
  });
  body.querySelectorAll("[data-add-tracking]").forEach((button) => {
    button.addEventListener("click", () => addCandidateToTracking(button.dataset.addTracking, button));
  });
  bindStockRows(body);
  renderBacktest(result.backtest);
  if (mode === "smart") setText("screenerRunStatus", `完成 · ${candidates.length} 只`);
  updateBacktestTaskStatus();
  renderScreenerProgress();
}

async function loadScreenerTracking(force = false) {
  if (state.screenerTracking && !force) {
    renderScreenerTracking();
    return;
  }
  try {
    state.screenerTracking = await apiRequest("/api/screener/tracking?limit=12");
    renderScreenerTracking();
    if (activeScreenerResult()) renderScreenerResult();
  } catch (error) {
    showToast(error.message || "策略跟踪加载失败");
  }
}

function isCandidateTracked(runId, code) {
  if (!runId) return false;
  return (state.screenerTracking?.batches || []).some((batch) =>
    number(batch.run_id) === number(runId)
    && (batch.items || []).some((item) => item.code === code));
}

async function addCandidateToTracking(code, button) {
  const runId = number(activeScreenerResult()?.meta?.run_id);
  if (!runId) {
    showToast("本次结果缺少选股批次，请重新执行后再加入跟踪");
    return;
  }
  button.disabled = true;
  try {
    const payload = await apiRequest("/api/screener/tracking", "POST", { run_id: runId, code });
    state.screenerTracking = payload.tracking;
    renderScreenerTracking();
    renderScreenerResult();
    showToast(`${code} 已加入策略跟踪`);
  } catch (error) {
    button.disabled = false;
    showToast(error.message || "加入跟踪失败");
  }
}

async function refreshScreenerTracking() {
  const button = document.querySelector("#refreshTrackingButton");
  button.disabled = true;
  setStatus("正在更新策略跟踪");
  try {
    const payload = await apiRequest("/api/screener/tracking/refresh", "POST", {
      trade_date: elements.tradeDate.value,
    });
    state.screenerTracking = payload.tracking;
    renderScreenerTracking();
    if (payload.notice) showToast(payload.notice);
    setStatus("策略跟踪已更新");
  } catch (error) {
    showToast(error.message || "策略跟踪刷新失败");
    setStatus("策略跟踪刷新失败");
  } finally {
    button.disabled = false;
  }
}

function renderScreenerTracking() {
  const payload = state.screenerTracking || { batches: [], summary: {} };
  const batches = payload.batches || [];
  const rows = batches.flatMap((batch) => (batch.items || []).map((item) => ({
    ...item,
    run_id: batch.run_id,
    selection_date: batch.selection_date,
    strategy_name: batch.strategy_name,
  })));
  setText("trackingBatchCount", `${batches.length} 批`);
  const summary = payload.summary || {};
  document.querySelector("#trackingSummary").innerHTML = [
    ["跟踪标的", `${number(summary.total)} 只`],
    ["已有 T+1", `${number(summary.observed)} 只`],
    ["T+1 胜率", trackingPercent(summary.t1_win_rate)],
    ["T+5 胜率", trackingPercent(summary.t5_win_rate)],
    ["T+5 平均", trackingReturn(summary.average_t5)],
  ].map(([label, value]) => `<div><span>${label}</span><strong>${value}</strong></div>`).join("");
  document.querySelector("#trackingEmpty").hidden = rows.length > 0;
  document.querySelector("#trackingTableBody").innerHTML = rows.map((row) => `
    <tr data-code="${escapeHtml(row.code)}">
      <td>${displayCompactDate(row.selection_date)}</td>
      <td class="tracking-strategy" title="${escapeHtml(row.strategy_name)}">${escapeHtml(row.strategy_name)}</td>
      <td><span class="stock-cell"><strong class="sname">${escapeHtml(row.name)}</strong><small class="stock-code scode">${escapeHtml(row.code)}</small></span></td>
      <td class="number num">${row.entry_price == null ? "" : formatNumber(row.entry_price, 2)}</td>
      ${["t1_open", "t1_close", "t3_close", "t5_close", "max_gain", "max_drawdown"].map((key) => `<td class="number num ${row[key] == null ? "" : changeClass(row[key])}">${trackingReturn(row[key], false)}</td>`).join("")}
      <td><span class="tracking-status ${row.status === "已完成" ? "complete" : row.observed_days ? "active" : "pending"}">${escapeHtml(row.status)}</span></td>
      <td><button class="table-action danger" type="button" data-remove-tracking="${row.id}">移除</button></td>
    </tr>
  `).join("");
  bindStockRows(document.querySelector("#trackingTableBody"));
}

async function handleTrackingTableAction(event) {
  const button = event.target.closest("[data-remove-tracking]");
  if (!button) return;
  if (!window.confirm("确定停止跟踪这只股票吗？")) return;
  button.disabled = true;
  try {
    const payload = await apiRequest(`/api/screener/tracking/${button.dataset.removeTracking}`, "DELETE");
    state.screenerTracking = payload.tracking;
    renderScreenerTracking();
    if (activeScreenerResult()) renderScreenerResult();
    showToast("已移出策略跟踪");
  } catch (error) {
    button.disabled = false;
    showToast(error.message || "移除跟踪失败");
  }
}

function trackingReturn(value, includeUnit = true) {
  return value == null ? (includeUnit ? "--" : "") : `${signed(value)}${includeUnit ? "%" : ""}`;
}

function trackingPercent(value) {
  return value == null ? "--" : `${formatNumber(value, 1)}%`;
}

function renderBacktest(backtest) {
  const panel = document.querySelector("#backtestPanel");
  panel.hidden = !backtest;
  if (!backtest) return;
  setText("backtestDefinition", backtest.definition);
  document.querySelector("#backtestMetrics").innerHTML = [
    ["历史样本", `${number(backtest.samples)} 个`],
    ["条件胜率", `${formatNumber(backtest.win_rate, 1)}%`],
    ["平均3日收益", `${signed(backtest.average_3d_return)}%`],
    ["平均最大回撤", `${signed(backtest.average_drawdown)}%`],
  ].map(([label, value]) => `<div class="dragon-metric"><span>${label}</span><strong>${value}</strong></div>`).join("");
}

function parseFormulaEditor() {
  try {
    return JSON.parse(document.querySelector("#formulaEditor").value);
  } catch {
    throw new Error("受控公式不是有效的 JSON");
  }
}

function exportScreenerResults() {
  const modeLabels = { smart: "阶段选股", curated: "策略选股", quant: "自定义选股" };
  if (state.screenerArchiveView !== "latest") {
    const viewLabel = state.screenerArchiveView === "active" ? "持续有效" : "入选历史";
    const rows = selectedScreenerArchiveRows().map((row) => ({
      ...row,
      matched_strategy_names: (row.matched_strategies || []).join("、"),
    }));
    exportRows(`${modeLabels[state.screenerMode]}-${viewLabel}`, rows, [
      ["入选日", "selection_date"], ["股票代码", "code"], ["股票名称", "name"],
      ["板块", "sector"], ["状态", "status"], ["命中策略", "matched_strategy_names"],
      ["最新评分", "score_display"], ["有效期", "validity_label"],
    ]);
    return;
  }
  exportRows(modeLabels[state.screenerMode] || "智能选股", activeScreenerResult()?.candidates || [], [
    ["股票代码", "code"], ["股票名称", "name"], ["板块", "sector"], ["综合分", "score_display"],
    ["历史条件估计%", "historical_probability"], ["当日涨幅%", "pct_chg"], ["5日涨幅%", "return_5d"],
    ["10日涨幅%", "return_10d"], ["量比", "volume_ratio_5d"], ["板块强度", "sector_strength"],
    ["主要贡献", "reason"], ["风险标记", "risk_flags"],
  ]);
}

function regimeLabel(regime) {
  return state.screenerSetup?.regimes?.find((item) => item.id === regime)?.label || regime;
}


function bindScreenerEvents() {
  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("#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.querySelectorAll("[data-screener-archive-view]").forEach((button) => {
    button.addEventListener("click", () => {
      state.screenerArchiveView = ["active", "history"].includes(button.dataset.screenerArchiveView)
        ? button.dataset.screenerArchiveView
        : "latest";
      renderScreenerResult();
    });
  });
  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);
}
