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 stockPreviewOpenTimer = null;
let stockPreviewCloseTimer = null;
let stockPreviewAbortController = null;
let stockPreviewAnchor = null;

function bindStockRows(container) {
  animateRows(container);
  decorateStockPreviewTargets(container);
  container.querySelectorAll("[data-code]").forEach((rowElement) => {
    rowElement.addEventListener("click", (event) => {
      const interactive = event.target.closest("button, a, input, select, textarea, summary");
      if (interactive && interactive !== rowElement) return;
      openStock(rowElement.dataset.code, findStockFallback(rowElement.dataset.code));
    });
  });
}
function decorateStockPreviewTargets(container) {
  container.querySelectorAll(".stock-code").forEach((trigger) => {
    const code = stockCodeFromTrigger(trigger);
    if (!code) return;
    trigger.classList.add("stock-preview-trigger");
    trigger.tabIndex = 0;
    trigger.setAttribute("role", "button");
    trigger.setAttribute("aria-label", `预览 ${code} 行情`);
    trigger.title = "悬停预览行情，点击查看完整详情";
  });
}

function stockCodeFromTrigger(trigger) {
  const candidate = trigger?.dataset?.stockPreviewCode
    || trigger?.closest?.("[data-code]")?.dataset?.code
    || trigger?.textContent?.trim();
  const matched = String(candidate || "").match(/\b(\d{6})\b/);
  return matched ? matched[1] : "";
}

function marketPreviewTargetFromTrigger(trigger) {
  if (trigger?.classList?.contains("market-preview-trigger")) {
    const type = String(trigger.dataset.marketPreviewType || "").trim().toLowerCase();
    const id = String(trigger.dataset.marketPreviewId || "").trim().toUpperCase();
    if (type === "theme" && id) {
      const item = (state.themeLibrary?.items || []).find((row) => String(row.code) === id) || {};
      return {
        type,
        id,
        code: id,
        name: item.name || trigger.textContent?.trim() || "--",
        type_label: "题材",
        change: item.change,
        value: item.close,
      };
    }
  }
  const code = stockCodeFromTrigger(trigger);
  return code ? { type: "stock", id: code, code } : null;
}

function previewTriggerFromEvent(event) {
  return event.target.closest?.(".stock-preview-trigger, .market-preview-trigger");
}

function showMarketPreview(target, trigger) {
  if (!target) return;
  if (target.type === "stock") showStockPreview(target.id, trigger);
  else showEntityPreview(target, trigger);
}

function findStockFallback(code) {
  const dashboardRows = [
    ...(state.dashboard?.limits || []),
    ...(state.dashboard?.broken || []),
    ...(state.dashboard?.down_limits || []),
    ...(state.dashboard?.yesterday_limits || []),
  ];
  const screenerRows = Object.values(state.screenerResultStore)
    .flatMap((entry) => entry?.result?.candidates || []);
  const dragonRows = (state.dragonTiger?.traders || []).flatMap((trader) => trader.operations || []);
  const auctionRows = state.auctionData?.rows || [];
  const themeRows = state.themeDetail?.members || [];
  const popularityRows = state.popularityData?.combined || [];
  const row = [...dashboardRows, ...screenerRows, ...dragonRows, ...auctionRows, ...themeRows, ...popularityRows, ...(state.watchlist || [])]
    .find((item) => String(item.code) === String(code));
  if (!row) return { code, name: "--", sector: "其他" };
  return {
    ...row,
    code,
    change: row.change ?? row.current_change ?? row.pct_chg ?? 0,
    sector: row.sector || row.industry || "其他",
  };
}

function supportsStockPreviewHover() {
  return window.matchMedia("(hover: hover) and (pointer: fine)").matches
    && window.innerWidth > 767;
}

function handleStockPreviewPointerOver(event) {
  if (!supportsStockPreviewHover()) return;
  const trigger = previewTriggerFromEvent(event);
  if (!trigger || trigger === event.relatedTarget?.closest?.(".stock-preview-trigger, .market-preview-trigger")) return;
  const target = marketPreviewTargetFromTrigger(trigger);
  if (!target) return;
  cancelStockPreviewClose();
  clearTimeout(stockPreviewOpenTimer);
  stockPreviewOpenTimer = setTimeout(() => showMarketPreview(target, trigger), STOCK_PREVIEW_DELAY);
}

function handleStockPreviewPointerOut(event) {
  if (!supportsStockPreviewHover()) return;
  const trigger = previewTriggerFromEvent(event);
  if (!trigger || trigger === event.relatedTarget?.closest?.(".stock-preview-trigger, .market-preview-trigger")) return;
  clearTimeout(stockPreviewOpenTimer);
  if (event.relatedTarget instanceof Node && elements.stockPreview.contains(event.relatedTarget)) return;
  scheduleStockPreviewClose();
}

function handleStockPreviewFocus(event) {
  if (!supportsStockPreviewHover()) return;
  const trigger = event.target.closest?.(".stock-preview-trigger");
  if (!trigger) return;
  const code = stockCodeFromTrigger(trigger);
  if (!code) return;
  clearTimeout(stockPreviewOpenTimer);
  stockPreviewOpenTimer = setTimeout(() => showStockPreview(code, trigger), 120);
}

function handleStockPreviewFocusOut(event) {
  const trigger = event.target.closest?.(".stock-preview-trigger");
  if (!trigger) return;
  if (event.relatedTarget instanceof Node && elements.stockPreview.contains(event.relatedTarget)) return;
  clearTimeout(stockPreviewOpenTimer);
  scheduleStockPreviewClose();
}

function handleMobileStockPreviewClick(event) {
  if (window.innerWidth > 767) return;
  const trigger = event.target.closest?.(".stock-preview-trigger");
  if (!trigger) return;
  const code = stockCodeFromTrigger(trigger);
  if (!code) return;
  event.preventDefault();
  event.stopPropagation();
  showStockPreview(code, trigger);
}

function handleStockPreviewKeydown(event) {
  if (event.key === "Escape" && !elements.stockPreview.hidden) {
    closeStockPreview();
    stockPreviewAnchor?.focus?.();
    return;
  }
  if (event.key !== "Enter") return;
  const trigger = event.target.closest?.(".stock-preview-trigger");
  if (!trigger) return;
  const code = stockCodeFromTrigger(trigger);
  if (!code) return;
  event.preventDefault();
  if (window.innerWidth <= 767) showStockPreview(code, trigger);
  else openStock(code, findStockFallback(code));
}

function cancelStockPreviewClose() {
  clearTimeout(stockPreviewCloseTimer);
}

function scheduleStockPreviewClose() {
  clearTimeout(stockPreviewCloseTimer);
  stockPreviewCloseTimer = setTimeout(closeStockPreview, 160);
}

async function showStockPreview(code, trigger) {
  clearTimeout(stockPreviewOpenTimer);
  cancelStockPreviewClose();
  if (!/^\d{6}$/.test(String(code))) return;
  stockPreviewAnchor = trigger;
  state.stockPreviewCode = String(code);
  state.stockPreviewType = "stock";
  state.stockPreviewItem = null;
  state.stockPreviewFallback = findStockFallback(code);
  state.stockPreviewPayload = null;
  state.stockPreviewChart = "daily";
  renderStockPreviewLoading();
  elements.stockPreview.hidden = false;
  const mobile = window.innerWidth <= 767;
  elements.stockPreviewBackdrop.hidden = !mobile;
  document.body.classList.toggle("stock-preview-open", mobile);
  requestAnimationFrame(repositionStockPreview);

  const cacheKey = `${code}:latest`;
  const cached = stockPreviewCache.get(cacheKey);
  if (cached && cached.expiresAt > Date.now()) {
    renderStockPreview(cached.payload);
    return;
  }
  if (cached) stockPreviewCache.delete(cacheKey);
  stockPreviewAbortController?.abort();
  stockPreviewAbortController = new AbortController();
  try {
    const payload = await apiRequest(
      `/api/stock/${encodeURIComponent(code)}/preview`,
      "GET",
      null,
      { signal: stockPreviewAbortController.signal },
    );
    if (state.stockPreviewCode !== String(code) || elements.stockPreview.hidden) return;
    const cacheMs = payload.meta?.realtime ? LIVE_REFRESH_DEFAULT_MS : STOCK_PREVIEW_CACHE_MS;
    stockPreviewCache.set(cacheKey, { payload, expiresAt: Date.now() + cacheMs });
    while (stockPreviewCache.size > 48) stockPreviewCache.delete(stockPreviewCache.keys().next().value);
    renderStockPreview(payload);
  } catch (error) {
    if (error.name === "AbortError" || state.stockPreviewCode !== String(code)) return;
    renderStockPreviewError(error.message || "行情预览加载失败");
  }
}

async function showEntityPreview(item, trigger) {
  const type = String(item?.type || "").trim().toLowerCase();
  const id = String(item?.id || item?.code || "").trim().toUpperCase();
  if (type !== "theme" || !id) return;
  clearTimeout(stockPreviewOpenTimer);
  cancelStockPreviewClose();
  stockPreviewAnchor = trigger;
  state.stockPreviewCode = id;
  state.stockPreviewType = type;
  state.stockPreviewItem = { ...item, id, code: item.code || id, type, type_label: item.type_label || "题材" };
  state.stockPreviewFallback = {
    code: item.code || id,
    name: item.name || "--",
    sector: item.type_label || "题材",
    price: item.value,
    change: item.change,
  };
  state.stockPreviewPayload = null;
  state.stockPreviewChart = "daily";
  renderStockPreviewLoading();
  elements.stockPreview.hidden = false;
  const mobile = window.innerWidth <= 767;
  elements.stockPreviewBackdrop.hidden = !mobile;
  document.body.classList.toggle("stock-preview-open", mobile);
  requestAnimationFrame(repositionStockPreview);

  const cacheKey = `${type}:${id}:latest`;
  const cached = stockPreviewCache.get(cacheKey);
  if (cached && cached.expiresAt > Date.now()) {
    renderStockPreview(cached.payload);
    return;
  }
  if (cached) stockPreviewCache.delete(cacheKey);
  stockPreviewAbortController?.abort();
  stockPreviewAbortController = new AbortController();
  try {
    const params = new URLSearchParams({ type, id, trade_date: todayString() });
    const detail = await apiRequest(
      `/api/search/detail?${params}`,
      "GET",
      null,
      { signal: stockPreviewAbortController.signal },
    );
    if (state.stockPreviewType !== type || state.stockPreviewCode !== id || elements.stockPreview.hidden) return;
    const entity = detail.entity || {};
    const payload = {
      stock: {
        code: entity.code || id,
        name: entity.name || item.name || "--",
        industry: entity.type_label || item.type_label || "题材",
        price: entity.value,
        change: entity.change,
      },
      prices: detail.series || [],
      intraday: [],
      meta: {
        trade_date: detail.meta?.trade_date || "",
        realtime: Boolean(detail.meta?.realtime),
        intraday_status: "idle",
        intraday_notice: "",
      },
    };
    stockPreviewCache.set(cacheKey, { payload, expiresAt: Date.now() + STOCK_PREVIEW_CACHE_MS });
    while (stockPreviewCache.size > 48) stockPreviewCache.delete(stockPreviewCache.keys().next().value);
    renderStockPreview(payload);
  } catch (error) {
    if (error.name === "AbortError" || state.stockPreviewType !== type || state.stockPreviewCode !== id) return;
    renderStockPreviewError(error.message || "题材行情预览加载失败");
  }
}

function renderStockPreviewLoading() {
  const fallback = state.stockPreviewFallback || {};
  selectStockPreviewChart("daily");
  setText("stockPreviewCode", state.stockPreviewCode || "--");
  setText("stockPreviewName", fallback.name || "正在加载");
  setText("stockPreviewSector", fallback.sector || "--");
  setText("stockPreviewPrice", "--");
  setText("stockPreviewChange", "--");
  document.querySelector("#stockPreviewChange").className = "";
  setText("stockPreviewDate", "最新行情");
  setText("stockPreviewSource", "正在读取行情");
  setText("stockPreviewSummary", "等待行情数据");
  document.querySelector("#stockPreviewLoading").hidden = false;
  clearStockPreviewChart("");
}

function renderStockPreview(payload) {
  state.stockPreviewPayload = payload;
  const fallback = state.stockPreviewFallback || {};
  const stock = payload.stock || {};
  const price = stock.price;
  const change = stock.change;
  setText("stockPreviewCode", stock.code || state.stockPreviewCode);
  setText("stockPreviewName", stock.name && stock.name !== "--" ? stock.name : fallback.name || "--");
  setText("stockPreviewSector", stock.industry && stock.industry !== "其他" ? stock.industry : fallback.sector || "其他");
  setText("stockPreviewPrice", meaningfulNumber(price) ? formatNumber(price, 2) : "--");
  setText("stockPreviewChange", meaningfulNumber(change) ? `${signed(change)}%` : "--");
  document.querySelector("#stockPreviewChange").className = changeClass(change);
  document.querySelector("#stockPreviewLoading").hidden = true;
  selectStockPreviewChart("daily");
  requestAnimationFrame(repositionStockPreview);
}

function renderStockPreviewError(message) {
  document.querySelector("#stockPreviewLoading").hidden = true;
  setText("stockPreviewSource", "行情加载失败");
  setText("stockPreviewSummary", message);
  clearStockPreviewChart("加载失败");
}

function selectStockPreviewChart(chart) {
  state.stockPreviewChart = chart === "daily" ? "daily" : "intraday";
  document.querySelectorAll("[data-preview-chart]").forEach((button) => {
    const active = button.dataset.previewChart === state.stockPreviewChart;
    button.classList.toggle("active", active);
    button.setAttribute("aria-selected", String(active));
  });
  const payload = state.stockPreviewPayload;
  if (!payload) return;
  if (state.stockPreviewChart === "intraday") {
    if (state.stockPreviewType !== "stock" && payload.meta?.intraday_status === "idle") {
      payload.meta.intraday_status = "loading";
      setText("stockPreviewDate", "正在加载分时");
      setText("stockPreviewSource", "正在读取最新分时");
      setText("stockPreviewSummary", "等待分时行情数据");
      clearStockPreviewChart("");
      loadEntityPreviewIntraday();
      return;
    }
    if (state.stockPreviewType !== "stock" && payload.meta?.intraday_status === "loading") return;
    setText("stockPreviewDate", payload.meta?.intraday_trade_date || payload.meta?.trade_date || "最新行情");
    setText(
      "stockPreviewSource",
      (payload.intraday || []).length ? "最新分时 · 1分钟" : "分时暂不可用",
    );
    if ((payload.intraday || []).length) {
      drawIntradayPreviewChart(
        payload.intraday,
        payload.prices || [],
        payload.meta?.intraday_previous_close,
      );
    }
    else {
      clearStockPreviewChart("分时数据不可用");
      setText("stockPreviewSummary", payload.meta?.intraday_notice || "该交易日暂无分时数据。");
    }
  } else if ((payload.prices || []).length) {
    setText("stockPreviewDate", payload.meta?.trade_date || "最新行情");
    setText("stockPreviewSource", `日 K 行情 · ${payload.prices.length} 个交易日`);
    drawDailyPreviewChart(payload.prices);
  } else {
    setText("stockPreviewDate", payload.meta?.trade_date || "最新行情");
    setText("stockPreviewSource", "日 K 行情暂不可用");
    clearStockPreviewChart("暂无日K数据");
    setText("stockPreviewSummary", "该股票暂无可用的日K数据。");
  }
}

async function loadEntityPreviewIntraday() {
  const type = state.stockPreviewType;
  const id = state.stockPreviewCode;
  const payload = state.stockPreviewPayload;
  if (type === "stock" || !id || !payload) return;
  stockPreviewAbortController?.abort();
  stockPreviewAbortController = new AbortController();
  try {
    const params = new URLSearchParams({ type, id });
    const intraday = await apiRequest(
      `/api/chart/intraday?${params}`,
      "GET",
      null,
      { signal: stockPreviewAbortController.signal },
    );
    if (state.stockPreviewType !== type || state.stockPreviewCode !== id || elements.stockPreview.hidden) return;
    payload.intraday = intraday.points || [];
    payload.meta.intraday_status = payload.intraday.length ? "available" : "empty";
    payload.meta.intraday_trade_date = intraday.meta?.trade_date || "";
    payload.meta.intraday_previous_close = intraday.meta?.previous_close || 0;
    payload.meta.intraday_notice = payload.intraday.length ? "" : "该题材暂无可用分时数据。";
    if (state.stockPreviewChart === "intraday") selectStockPreviewChart("intraday");
  } catch (error) {
    if (error.name === "AbortError" || state.stockPreviewType !== type || state.stockPreviewCode !== id) return;
    payload.meta.intraday_status = "unavailable";
    payload.meta.intraday_notice = error.message || "题材分时行情暂不可用。";
    if (state.stockPreviewChart === "intraday") selectStockPreviewChart("intraday");
  }
}

function closeStockPreview() {
  clearTimeout(stockPreviewOpenTimer);
  clearTimeout(stockPreviewCloseTimer);
  stockPreviewAbortController?.abort();
  stockPreviewAbortController = null;
  elements.stockPreview.hidden = true;
  elements.stockPreviewBackdrop.hidden = true;
  document.body.classList.remove("stock-preview-open");
  state.stockPreviewPayload = null;
  state.stockPreviewCode = "";
  state.stockPreviewType = "stock";
  state.stockPreviewItem = null;
}

function openStockDetailFromPreview() {
  const code = state.stockPreviewCode;
  const fallback = state.stockPreviewFallback;
  const type = state.stockPreviewType;
  const item = state.stockPreviewItem;
  if (!code) return;
  closeStockPreview();
  if (type === "stock") openStock(code, fallback);
  else if (item) openEntityDetail(item);
}

function repositionStockPreview() {
  if (elements.stockPreview.hidden || window.innerWidth <= 767 || !stockPreviewAnchor?.isConnected) return;
  const anchor = stockPreviewAnchor.getBoundingClientRect();
  const preview = elements.stockPreview.getBoundingClientRect();
  const gap = 12;
  let left = anchor.right + gap;
  if (left + preview.width > window.innerWidth - 8) left = anchor.left - preview.width - gap;
  left = clamp(left, 8, Math.max(8, window.innerWidth - preview.width - 8));
  const top = clamp(anchor.top - 48, 64, Math.max(64, window.innerHeight - preview.height - 8));
  elements.stockPreview.style.left = `${Math.round(left)}px`;
  elements.stockPreview.style.top = `${Math.round(top)}px`;
}
