import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
  Alert,
  Button,
  DatePicker,
  Form,
  Input,
  InputNumber,
  List,
  message,
  Modal,
  Pagination,
  Select,
  Skeleton,
  Space,
  Table,
  Tabs,
  Tag,
  Typography,
} from "antd";
import type { ColumnsType } from "antd/es/table";
import type { SortOrder } from "antd/es/table/interface";
import dayjs from "dayjs";
import type { Dayjs } from "dayjs";
import HotContentSourcePage from "./HotContentSourcePage";
import DemandNavBar from "./DemandNavBar";
import EllipsisCell from "./EllipsisCell";

type DemandPoolItem = {
  id: number;
  strategy: string | null;
  demand_name: string | null;
  type: string | null;
  weight: number | null;
  video_count: number | null;
  dt: string | null;
  reason: string | null;
};

type QueryResponse = {
  total: number;
  page: number;
  page_size: number;
  items: DemandPoolItem[];
};

type StrategyOption = {
  strategy: string;
  record_count: number;
};

type StrategyResponse = {
  items: StrategyOption[];
};

type ElementDemandItem = {
  strategy: string | null;
  demand_id: string | null;
  demand_name: string | null;
  weight: number | null;
  video_count: number | null;
  video_list: string | null;
  month_list?: string | null;
  frequency?: number | null;
  ext_info: string | null;
};

type ElementDemandResponse = {
  items: ElementDemandItem[];
};

function parseVideoIdsFromList(raw: string | null): string[] {
  const t = (raw ?? "").trim();
  if (!t) {
    return [];
  }
  try {
    const parsed: unknown = JSON.parse(t);
    if (Array.isArray(parsed)) {
      return parsed.map((item) => String(item));
    }
  } catch {
    /* 非 JSON 数组则走下方原始文本展示 */
  }
  return [];
}

function formatYmDisplay(ym: string): string {
  if (/^\d{6}$/.test(ym)) {
    return `${ym.slice(0, 4)}-${ym.slice(4, 6)}`;
  }
  return ym;
}

function formatMonthListPreview(raw: string | null): string {
  const months = parseVideoIdsFromList(raw);
  if (months.length > 0) {
    return `共 ${months.length} 个月，点击查看`;
  }
  const trimmed = (raw ?? "").trim();
  if (!trimmed) {
    return "-";
  }
  return trimmed.length > 36 ? `${trimmed.slice(0, 36)}…` : trimmed;
}

const API_BASE_URL =
  import.meta.env.VITE_API_BASE_URL ?? "/demand/api/v1";

const getResolvedApiBaseUrl = () => {
  if (API_BASE_URL.startsWith("http://") || API_BASE_URL.startsWith("https://")) {
    return API_BASE_URL;
  }
  return new URL(API_BASE_URL, window.location.origin).toString();
};

function parseFilenameFromContentDisposition(header: string | null): string | null {
  if (!header) {
    return null;
  }
  const utf8Match = header.match(/filename\*=UTF-8''([^;]+)/i);
  if (utf8Match?.[1]) {
    try {
      return decodeURIComponent(utf8Match[1]);
    } catch {
      return utf8Match[1];
    }
  }
  const asciiMatch = header.match(/filename="([^"]+)"/i);
  return asciiMatch?.[1] ?? null;
}

async function downloadExcelExport(url: string, defaultFilename: string) {
  const response = await fetch(url, { method: "GET" });
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  const blob = await response.blob();
  const filename =
    parseFilenameFromContentDisposition(response.headers.get("Content-Disposition")) ??
    defaultFilename;
  const objectUrl = URL.createObjectURL(blob);
  const link = document.createElement("a");
  link.href = objectUrl;
  link.download = filename;
  link.click();
  URL.revokeObjectURL(objectUrl);
}

/** 票圈后台：视频详情页（新标签打开） */
const CMS_VIDEO_POST_DETAIL_BASE =
  "https://admin.piaoquantv.com/cms/post-detail/";

const HOT_DEMAND_POOL_STRATEGY = "新热事件";

type HotSourceViewParams = {
  demandName: string;
  demandType: string;
  dt: string;
};

function readHotSourceViewFromUrl(): HotSourceViewParams | null {
  const params = new URLSearchParams(window.location.search);
  if (params.get("view") !== "hot-source") {
    return null;
  }
  const demandName = (params.get("demand_name") ?? "").trim();
  const demandType = (params.get("demand_type") ?? "").trim();
  const dt = (params.get("dt") ?? "").trim();
  if (!demandName || !demandType || !dt) {
    return null;
  }
  return { demandName, demandType, dt };
}

function buildHotSourceViewUrl(params: HotSourceViewParams): string {
  const search = new URLSearchParams({
    view: "hot-source",
    demand_name: params.demandName,
    demand_type: params.demandType,
    dt: params.dt,
  });
  return `${window.location.origin}${window.location.pathname}?${search.toString()}`;
}

function openHotSourceViewInNewTab(params: HotSourceViewParams) {
  window.open(buildHotSourceViewUrl(params), "_blank", "noopener,noreferrer");
}

function closeHotSourceView() {
  if (window.opener && !window.opener.closed) {
    window.close();
    return;
  }
  window.location.href = window.location.pathname;
}

function DemandPoolPanel() {
  const [strategies, setStrategies] = useState<string[]>([]);
  const [dateRange, setDateRange] = useState<[Dayjs, Dayjs] | null>(() => {
    const today = dayjs();
    return [today, today];
  });
  const [strategyOptions, setStrategyOptions] = useState<StrategyOption[]>([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [pageSize, setPageSize] = useState(20);
  const [refreshTick, setRefreshTick] = useState(0);
  const [minWeightInput, setMinWeightInput] = useState<number | null>(null);
  const [maxWeightInput, setMaxWeightInput] = useState<number | null>(null);
  const [appliedMinWeight, setAppliedMinWeight] = useState<number | null>(null);
  const [appliedMaxWeight, setAppliedMaxWeight] = useState<number | null>(null);
  const [demandNameInput, setDemandNameInput] = useState("");
  const [appliedDemandName, setAppliedDemandName] = useState("");
  const [sortBy, setSortBy] = useState("weight");
  const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc");
  const [loading, setLoading] = useState(false);
  const [exporting, setExporting] = useState(false);
  const [hasLoaded, setHasLoaded] = useState(false);
  const [loadingStrategies, setLoadingStrategies] = useState(false);
  const [error, setError] = useState("");
  const [data, setData] = useState<QueryResponse>({
    total: 0,
    page: 1,
    page_size: pageSize,
    items: []
  });

  const startDate = dateRange?.[0]?.format("YYYYMMDD") ?? "";
  const endDate = dateRange?.[1]?.format("YYYYMMDD") ?? "";
  const dateRangeInvalid =
    Boolean(startDate) && Boolean(endDate) && startDate > endDate;
  const weightRangeInvalid =
    minWeightInput !== null && maxWeightInput !== null && minWeightInput > maxWeightInput;
  const queryKey = JSON.stringify({
    strategies,
    startDate,
    endDate,
    appliedMinWeight,
    appliedMaxWeight,
    appliedDemandName,
    sortBy,
    sortOrder,
    currentPage,
    pageSize,
    refreshTick,
  });

  const buildRequestUrl = (page: number, size: number = pageSize) => {
    const resolvedBase = getResolvedApiBaseUrl();
    const baseWithSlash = resolvedBase.endsWith("/")
      ? resolvedBase
      : `${resolvedBase}/`;
    const url = new URL("demand-pool", baseWithSlash);
    for (const strategyValue of strategies) {
      url.searchParams.append("strategy", strategyValue);
    }
    if (startDate) {
      url.searchParams.set("start_dt", startDate);
    }
    if (endDate) {
      url.searchParams.set("end_dt", endDate);
    }
    if (appliedMinWeight !== null) {
      url.searchParams.set("min_weight", String(appliedMinWeight));
    }
    if (appliedMaxWeight !== null) {
      url.searchParams.set("max_weight", String(appliedMaxWeight));
    }
    const trimmedDemandName = appliedDemandName.trim();
    if (trimmedDemandName) {
      url.searchParams.set("demand_name", trimmedDemandName);
    }
    url.searchParams.set("sort_by", sortBy);
    url.searchParams.set("sort_order", sortOrder);
    url.searchParams.set("page", String(page));
    url.searchParams.set("page_size", String(size));
    return url.toString();
  };

  const buildExportUrl = () => {
    const resolvedBase = getResolvedApiBaseUrl();
    const baseWithSlash = resolvedBase.endsWith("/")
      ? resolvedBase
      : `${resolvedBase}/`;
    const url = new URL("demand-pool/export", baseWithSlash);
    for (const strategyValue of strategies) {
      url.searchParams.append("strategy", strategyValue);
    }
    if (startDate) {
      url.searchParams.set("start_dt", startDate);
    }
    if (endDate) {
      url.searchParams.set("end_dt", endDate);
    }
    if (appliedMinWeight !== null) {
      url.searchParams.set("min_weight", String(appliedMinWeight));
    }
    if (appliedMaxWeight !== null) {
      url.searchParams.set("max_weight", String(appliedMaxWeight));
    }
    const trimmedDemandName = appliedDemandName.trim();
    if (trimmedDemandName) {
      url.searchParams.set("demand_name", trimmedDemandName);
    }
    url.searchParams.set("sort_by", sortBy);
    url.searchParams.set("sort_order", sortOrder);
    return url.toString();
  };

  const handleExport = async () => {
    if (dateRangeInvalid || weightRangeInvalid) {
      return;
    }
    setExporting(true);
    try {
      await downloadExcelExport(buildExportUrl(), "需求池.xlsx");
      message.success("导出成功");
    } catch {
      message.error("导出失败，请重试");
    } finally {
      setExporting(false);
    }
  };

  const buildStrategyUrl = () => {
    const resolvedBase = getResolvedApiBaseUrl();
    const baseWithSlash = resolvedBase.endsWith("/")
      ? resolvedBase
      : `${resolvedBase}/`;
    const url = new URL("demand-pool/strategies", baseWithSlash);
    if (startDate) {
      url.searchParams.set("start_dt", startDate);
    }
    if (endDate) {
      url.searchParams.set("end_dt", endDate);
    }
    if (appliedMinWeight !== null) {
      url.searchParams.set("min_weight", String(appliedMinWeight));
    }
    if (appliedMaxWeight !== null) {
      url.searchParams.set("max_weight", String(appliedMaxWeight));
    }
    return url.toString();
  };

  const fetchData = async (page: number, size: number = pageSize) => {
    setLoading(true);
    setError("");

    try {
      const response = await fetch(buildRequestUrl(page, size), {
        method: "GET",
        headers: { Accept: "application/json" }
      });
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      const payload = (await response.json()) as QueryResponse;
      setData(payload);
    } catch (queryError) {
      setError(
        queryError instanceof Error ? queryError.message : "查询失败，请重试"
      );
    } finally {
      setLoading(false);
      setHasLoaded(true);
    }
  };

  const fetchStrategies = async () => {
    setLoadingStrategies(true);
    try {
      const response = await fetch(buildStrategyUrl(), {
        method: "GET",
        headers: { Accept: "application/json" }
      });
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      const payload = (await response.json()) as StrategyResponse;
      setStrategyOptions(payload.items);
      const availableSet = new Set(payload.items.map((item) => item.strategy));
      const allStrategies = payload.items.map((item) => item.strategy);
      setStrategies((prev) => {
        if (prev.length === 0) {
          return allStrategies;
        }
        const filtered = prev.filter((value) => availableSet.has(value));
        return filtered.length > 0 ? filtered : allStrategies;
      });
    } catch {
      setStrategyOptions([]);
    } finally {
      setLoadingStrategies(false);
    }
  };

  useEffect(() => {
    void fetchStrategies();
  }, [startDate, endDate, appliedMinWeight, appliedMaxWeight]);

  useEffect(() => {
    if (dateRangeInvalid || weightRangeInvalid || loadingStrategies) {
      return;
    }
    if (strategyOptions.length > 0 && strategies.length === 0) {
      return;
    }
    void fetchData(currentPage, pageSize);
  }, [queryKey, loadingStrategies, dateRangeInvalid, weightRangeInvalid, strategyOptions.length]);

  const handleSubmit = async () => {
    if (dateRangeInvalid || weightRangeInvalid) {
      return;
    }
    setAppliedMinWeight(minWeightInput);
    setAppliedMaxWeight(maxWeightInput);
    setAppliedDemandName(demandNameInput.trim());
    setCurrentPage(1);
    setRefreshTick((value) => value + 1);
  };

  const totalPages = Math.max(1, Math.ceil(data.total / pageSize));

  const handlePageSizeChange = (value: number) => {
    setPageSize(value);
    setCurrentPage(1);
  };

  const resetFilters = () => {
    const today = dayjs();
    setDateRange([today, today]);
    setStrategies(strategyOptions.map((item) => item.strategy));
    setMinWeightInput(null);
    setMaxWeightInput(null);
    setAppliedMinWeight(null);
    setAppliedMaxWeight(null);
    setDemandNameInput("");
    setAppliedDemandName("");
    setSortBy("weight");
    setSortOrder("desc");
    setCurrentPage(1);
  };

  const getSortOrderForColumn = (columnKey: string): SortOrder | null => {
    if (sortBy !== columnKey) {
      return null;
    }
    return sortOrder === "asc" ? "ascend" : "descend";
  };

  const columns: ColumnsType<DemandPoolItem> = useMemo(
    () => [
      { title: "ID", dataIndex: "id", width: 90, sorter: true, sortOrder: getSortOrderForColumn("id") },
      {
        title: "策略名",
        dataIndex: "strategy",
        render: (v) => v ?? "-",
        sorter: true,
        sortOrder: getSortOrderForColumn("strategy"),
      },
      {
        title: "需求名称",
        dataIndex: "demand_name",
        render: (v) => v ?? "-",
        sorter: true,
        sortOrder: getSortOrderForColumn("demand_name"),
      },
      {
        title: "需求类型",
        dataIndex: "type",
        width: 120,
        render: (v) => v ?? "-",
        sorter: true,
        sortOrder: getSortOrderForColumn("type"),
      },
      {
        title: "权重",
        dataIndex: "weight",
        width: 120,
        render: (v) => v ?? "-",
        sorter: true,
        sortOrder: getSortOrderForColumn("weight"),
      },
      {
        title: "视频数量",
        dataIndex: "video_count",
        width: 120,
        render: (v) => v ?? "-",
        sorter: true,
        sortOrder: getSortOrderForColumn("video_count"),
      },
      {
        title: "日期",
        dataIndex: "dt",
        width: 120,
        render: (v) => v ?? "-",
        sorter: true,
        sortOrder: getSortOrderForColumn("dt"),
      },
      {
        title: "原因",
        dataIndex: "reason",
        width: 140,
        ellipsis: true,
        render: (v, record) =>
          (record.strategy ?? "").trim() === HOT_DEMAND_POOL_STRATEGY ? (v ?? "") : "",
      },
      {
        title: "操作",
        key: "actions",
        width: 110,
        fixed: "right",
        render: (_, record) => {
          if ((record.strategy ?? "").trim() !== HOT_DEMAND_POOL_STRATEGY) {
            return "-";
          }
          const demandName = (record.demand_name ?? "").trim();
          const demandType = (record.type ?? "").trim();
          const dt = (record.dt ?? "").trim();
          const reason = (record.reason ?? "").trim();
          if (!demandName || !demandType || !dt || reason) {
            return "-";
          }
          return (
            <Button
              type="link"
              size="small"
              onClick={() =>
                openHotSourceViewInNewTab({
                  demandName,
                  demandType,
                  dt,
                })
              }
            >
              查看来源
            </Button>
          );
        },
      },
    ],
    [sortBy, sortOrder]
  );

  return (
    <div className="panel-sheet">
      <section className="panel-section panel-section--filters">
        <header className="panel-section-head">
          <span className="panel-section-accent" aria-hidden />
          <Typography.Title level={5} className="panel-section-title">
            筛选条件
          </Typography.Title>
        </header>
        <Form layout="vertical" onFinish={() => void handleSubmit()} className="filter-form">
          <div className="filter-row">
            <Form.Item label="策略名" className="strategy-item">
              <Select
                className="strategy-select"
                placeholder="请选择策略（支持多选）"
                value={strategies}
                onChange={setStrategies}
                loading={loadingStrategies}
                mode="multiple"
                allowClear
                maxTagCount="responsive"
                showSearch
                optionFilterProp="label"
                options={strategyOptions.map((item) => ({
                  label: `${item.strategy} (${item.record_count})`,
                  value: item.strategy,
                }))}
              />
            </Form.Item>
          </div>

          <div className="filter-row second-row">
            <Form.Item label="日期区间">
              <DatePicker.RangePicker
                value={dateRange}
                onChange={(values) => setDateRange(values as [Dayjs, Dayjs] | null)}
                allowClear
              />
            </Form.Item>

            <Form.Item label="权重分">
              <Space>
                <InputNumber
                  placeholder="最小值"
                  value={minWeightInput}
                  onChange={(value) => setMinWeightInput(value)}
                />
                <span>-</span>
                <InputNumber
                  placeholder="最大值"
                  value={maxWeightInput}
                  onChange={(value) => setMaxWeightInput(value)}
                />
              </Space>
            </Form.Item>

            <Form.Item label="需求名称">
              <Input
                allowClear
                value={demandNameInput}
                onChange={(e) => setDemandNameInput(e.target.value)}
              />
            </Form.Item>

            <Form.Item label=" ">
              <Button
                type="primary"
                htmlType="submit"
                loading={loading}
                disabled={dateRangeInvalid || weightRangeInvalid}
              >
                查询
              </Button>
            </Form.Item>

            <Form.Item label=" ">
              <Space>
                <Button type="default" onClick={resetFilters}>
                  重置
                </Button>
              </Space>
            </Form.Item>
          </div>
        </Form>

        {dateRangeInvalid ? (
          <Alert
            style={{ marginTop: 12 }}
            type="error"
            showIcon
            message="开始日期不能晚于结束日期"
          />
        ) : null}
        {weightRangeInvalid ? (
          <Alert
            style={{ marginTop: 12 }}
            type="error"
            showIcon
            message="最小权重不能大于最大权重"
          />
        ) : null}
        {error ? (
          <Alert style={{ marginTop: 12 }} type="error" showIcon message={`请求失败: ${error}`} />
        ) : null}
      </section>

      <section className="panel-section panel-section--table">
        <div className="table-toolbar">
          <Typography.Title level={5} className="panel-section-title panel-section-title--inline">
            需求明细
          </Typography.Title>
          <Space size={8} wrap className="table-toolbar-meta">
            <span className="meta-chip">已选策略 {strategies.length}</span>
            <span className="meta-chip">共 {data.total} 条</span>
            <span className="meta-chip">
              第 {currentPage} / {totalPages} 页
            </span>
            <Button
              type="default"
              loading={exporting}
              disabled={dateRangeInvalid || weightRangeInvalid || loading}
              onClick={() => void handleExport()}
            >
              导出 Excel
            </Button>
          </Space>
        </div>
        {loading && !hasLoaded ? (
          <Skeleton active paragraph={{ rows: 10 }} />
        ) : (
          <div className="table-wrap">
            <Table
              rowKey="id"
              loading={loading}
              columns={columns}
              dataSource={data.items}
              pagination={false}
              scroll={{ x: 1320 }}
              rowClassName={(_, index) => (index % 2 === 0 ? "row-even" : "row-odd")}
              onChange={(_, __, sorter) => {
                if (Array.isArray(sorter)) {
                  return;
                }
                const nextField = typeof sorter.field === "string" ? sorter.field : null;
                const nextOrder = sorter.order;
                if (!nextField || !nextOrder) {
                  setSortBy("weight");
                  setSortOrder("desc");
                  setCurrentPage(1);
                  return;
                }
                setSortBy(nextField);
                setSortOrder(nextOrder === "ascend" ? "asc" : "desc");
                setCurrentPage(1);
              }}
            />
          </div>
        )}
        <div className="panel-footer">
          <Pagination
            current={currentPage}
            total={data.total}
            pageSize={pageSize}
            showSizeChanger
            pageSizeOptions={["10", "20", "50", "100"]}
            showQuickJumper
            showTotal={(total) => `共 ${total} 条`}
            onChange={(page, size) => {
              const nextSize = size ?? pageSize;
              setCurrentPage(page);
              if (nextSize !== pageSize) {
                setPageSize(nextSize);
                return;
              }
            }}
            onShowSizeChange={(_, size) => {
              handlePageSizeChange(size);
            }}
          />
        </div>
      </section>
    </div>
  );
}

function ElementDemandQueryPanel({
  active,
  apiPath,
  periodDaysLabel,
  tableDetailTitle,
  mode = "period",
}: {
  active: boolean;
  apiPath: string;
  periodDaysLabel: string;
  tableDetailTitle: string;
  /** period：阳历/阴历同期；monthly：逐月回溯窗口 */
  mode?: "period" | "monthly";
}) {
  const [periodDays, setPeriodDays] = useState(7);
  const [monthTotalPvThreshold, setMonthTotalPvThreshold] = useState(20000);
  const [minFrequency, setMinFrequency] = useState(4);
  const [viewPvCount, setViewPvCount] = useState(2000);
  const [minContributionScore, setMinContributionScore] = useState(0.8);
  const [rovAvg, setRovAvg] = useState(0.04);

  const [items, setItems] = useState<ElementDemandItem[]>([]);
  const [loading, setLoading] = useState(false);
  const [exporting, setExporting] = useState(false);
  const [hasLoaded, setHasLoaded] = useState(false);
  const [error, setError] = useState("");

  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(20);

  const [videoModalOpen, setVideoModalOpen] = useState(false);
  const [videoModalTitleName, setVideoModalTitleName] = useState("");
  const [videoModalIds, setVideoModalIds] = useState<string[]>([]);
  const [videoModalRaw, setVideoModalRaw] = useState("");
  const [monthModalOpen, setMonthModalOpen] = useState(false);
  const [monthModalTitleName, setMonthModalTitleName] = useState("");
  const [monthModalMonths, setMonthModalMonths] = useState<string[]>([]);
  const [monthModalRaw, setMonthModalRaw] = useState("");
  const [decodeLoadingVid, setDecodeLoadingVid] = useState<string | null>(null);

  const openVideoCmsDetail = useCallback((vid: string) => {
    const url = `${CMS_VIDEO_POST_DETAIL_BASE}${encodeURIComponent(vid)}/detail`;
    window.open(url, "_blank", "noopener,noreferrer");
  }, []);

  const openVideoDecodePage = useCallback(async (vid: string) => {
    setDecodeLoadingVid(vid);
    try {
      const resolvedBase = getResolvedApiBaseUrl();
      const baseWithSlash = resolvedBase.endsWith("/")
        ? resolvedBase
        : `${resolvedBase}/`;
      const url = new URL("videos/decode-url", baseWithSlash);
      url.searchParams.set("vid", vid);
      const response = await fetch(url.toString(), {
        method: "GET",
        headers: { Accept: "application/json" },
      });
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      const payload = (await response.json()) as { url2?: string | null };
      const raw = payload.url2;
      const trimmed =
        typeof raw === "string" ? raw.trim() : raw != null ? String(raw).trim() : "";
      if (!trimmed) {
        message.warning("不存在解构页面");
        return;
      }
      window.open(trimmed, "_blank", "noopener,noreferrer");
    } catch {
      message.error("解构页面地址查询失败");
    } finally {
      setDecodeLoadingVid(null);
    }
  }, []);

  const buildQueryUrl = useCallback(() => {
    const resolvedBase = getResolvedApiBaseUrl();
    const baseWithSlash = resolvedBase.endsWith("/")
      ? resolvedBase
      : `${resolvedBase}/`;
    const url = new URL(apiPath, baseWithSlash);
    if (mode === "period") {
      url.searchParams.set("period_days", String(periodDays));
    } else {
      url.searchParams.set(
        "month_total_pv_threshold",
        String(monthTotalPvThreshold)
      );
      url.searchParams.set("min_frequency", String(minFrequency));
    }
    url.searchParams.set("view_pv_count", String(viewPvCount));
    url.searchParams.set("min_contribution_score", String(minContributionScore));
    url.searchParams.set("rov_avg", String(rovAvg));
    return url.toString();
  }, [
    apiPath,
    mode,
    periodDays,
    monthTotalPvThreshold,
    minFrequency,
    viewPvCount,
    minContributionScore,
    rovAvg,
  ]);

  const buildExportUrl = useCallback(() => {
    const resolvedBase = getResolvedApiBaseUrl();
    const baseWithSlash = resolvedBase.endsWith("/")
      ? resolvedBase
      : `${resolvedBase}/`;
    const url = new URL(`${apiPath}/export`, baseWithSlash);
    if (mode === "period") {
      url.searchParams.set("period_days", String(periodDays));
    } else {
      url.searchParams.set(
        "month_total_pv_threshold",
        String(monthTotalPvThreshold)
      );
      url.searchParams.set("min_frequency", String(minFrequency));
    }
    url.searchParams.set("view_pv_count", String(viewPvCount));
    url.searchParams.set("min_contribution_score", String(minContributionScore));
    url.searchParams.set("rov_avg", String(rovAvg));
    return url.toString();
  }, [
    apiPath,
    mode,
    periodDays,
    monthTotalPvThreshold,
    minFrequency,
    viewPvCount,
    minContributionScore,
    rovAvg,
  ]);

  const handleExport = async () => {
    setExporting(true);
    try {
      await downloadExcelExport(buildExportUrl(), "特征点.xlsx");
      message.success("导出成功");
    } catch {
      message.error("导出失败，请重试");
    } finally {
      setExporting(false);
    }
  };

  const fetchAll = useCallback(async () => {
    setLoading(true);
    setError("");
    try {
      const response = await fetch(buildQueryUrl(), {
        method: "GET",
        headers: { Accept: "application/json" },
      });
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      const payload = (await response.json()) as ElementDemandResponse;
      setItems(payload.items ?? []);
      setPage(1);
    } catch (queryError) {
      setError(
        queryError instanceof Error ? queryError.message : "查询失败，请重试"
      );
      setItems([]);
    } finally {
      setLoading(false);
      setHasLoaded(true);
    }
  }, [buildQueryUrl]);

  /** 仅在本会话内首次进入该 Tab 时自动请求一次；数据留在 state 中，切走再回来不重复请求 */
  const autoFetchedOnceRef = useRef(false);
  useEffect(() => {
    if (!active) {
      return;
    }
    if (autoFetchedOnceRef.current) {
      return;
    }
    autoFetchedOnceRef.current = true;
    void fetchAll();
  }, [active, fetchAll]);

  const handleSubmit = () => {
    void fetchAll();
  };

  const resetDefaults = () => {
    setPeriodDays(7);
    setMonthTotalPvThreshold(20000);
    setMinFrequency(4);
    setViewPvCount(2000);
    setMinContributionScore(0.8);
    setRovAvg(0.04);
    setPage(1);
  };

  const total = items.length;
  const totalPages = Math.max(1, Math.ceil(total / pageSize));

  const pagedItems = useMemo(() => {
    const start = (page - 1) * pageSize;
    return items.slice(start, start + pageSize);
  }, [items, page, pageSize]);

  const columns: ColumnsType<ElementDemandItem> = useMemo(
    () => {
      const baseColumns: ColumnsType<ElementDemandItem> = [
      {
        title: "策略",
        dataIndex: "strategy",
        width: 120,
        render: (v) => v ?? "-",
      },
      {
        title: "特征点名称",
        dataIndex: "demand_name",
        ellipsis: true,
        render: (v) => v ?? "-",
      },
      {
        title: "权重",
        dataIndex: "weight",
        width: 110,
        render: (v) => (v === null || v === undefined ? "-" : String(v)),
      },
      ];
      const videoListColumn: ColumnsType<ElementDemandItem>[number] = {
        title: "视频列表",
        dataIndex: "video_list",
        width: 220,
        render: (_, record) => {
          const raw = record.video_list ?? "";
          const trimmed = raw.trim();
          if (!trimmed) {
            return "-";
          }
          const ids = parseVideoIdsFromList(raw);
          const label =
            ids.length > 0
              ? `共 ${ids.length} 条，点击查看`
              : trimmed.length > 36
                ? `${trimmed.slice(0, 36)}…`
                : trimmed;
          return (
            <Typography.Link
              onClick={() => {
                setVideoModalTitleName(record.demand_name ?? "");
                setVideoModalIds(ids);
                setVideoModalRaw(raw);
                setVideoModalOpen(true);
              }}
            >
              {label}
            </Typography.Link>
          );
        },
      };
      const videoCountColumn: ColumnsType<ElementDemandItem>[number] = {
        title: "视频数",
        dataIndex: "video_count",
        width: 100,
        render: (v) => v ?? "-",
      };
      if (mode === "monthly") {
        baseColumns.push({
          title: "频次",
          dataIndex: "frequency",
          width: 90,
          render: (v) => (v === null || v === undefined ? "-" : String(v)),
        });
        baseColumns.push(videoCountColumn);
        baseColumns.push({
          title: "月份列表",
          dataIndex: "month_list",
          width: 180,
          render: (_, record) => {
            const raw = record.month_list ?? "";
            const trimmed = raw.trim();
            if (!trimmed) {
              return "-";
            }
            const months = parseVideoIdsFromList(raw);
            return (
              <Typography.Link
                onClick={() => {
                  setMonthModalTitleName(record.demand_name ?? "");
                  setMonthModalMonths(months);
                  setMonthModalRaw(raw);
                  setMonthModalOpen(true);
                }}
              >
                {formatMonthListPreview(raw)}
              </Typography.Link>
            );
          },
        });
      } else {
        baseColumns.push(videoCountColumn);
      }
      baseColumns.push(videoListColumn);
      return baseColumns;
    },
    [mode]
  );

  return (
    <div className="panel-sheet">
      <section className="panel-section panel-section--filters">
        <header className="panel-section-head">
          <span className="panel-section-accent" aria-hidden />
          <Typography.Title level={5} className="panel-section-title">
            筛选条件
          </Typography.Title>
        </header>
        <Form layout="vertical" onFinish={() => void handleSubmit()} className="filter-form">
          <div className="filter-row second-row element-demand-filter-row">
            {mode === "period" ? (
              <Form.Item label={periodDaysLabel}>
                <InputNumber
                  min={0}
                  precision={0}
                  value={periodDays}
                  onChange={(v) => setPeriodDays(v ?? 7)}
                />
              </Form.Item>
            ) : null}
            <Form.Item label="当日分发曝光PV限制">
              <InputNumber
                min={0}
                precision={0}
                value={viewPvCount}
                onChange={(v) => setViewPvCount(v ?? 0)}
              />
            </Form.Item>
            {mode === "monthly" ? (
              <Form.Item label="月累计分发曝光PV阈值">
                <InputNumber
                  min={0}
                  precision={0}
                  value={monthTotalPvThreshold}
                  onChange={(v) => setMonthTotalPvThreshold(v ?? 0)}
                />
              </Form.Item>
            ) : null}
            <Form.Item label="贡献分限制">
              <InputNumber
                min={0}
                step={0.01}
                value={minContributionScore}
                onChange={(v) => setMinContributionScore(v ?? 0)}
              />
            </Form.Item>
            <Form.Item
              label={
                mode === "monthly" ? "累加平均ROV限制" : "平均ROV限制"
              }
            >
              <InputNumber
                min={0}
                step={0.001}
                value={rovAvg}
                onChange={(v) => setRovAvg(v ?? 0)}
              />
            </Form.Item>
            {mode === "monthly" ? (
              <Form.Item label="元素频次限制（有效月份数）">
                <InputNumber
                  min={0}
                  precision={0}
                  value={minFrequency}
                  onChange={(v) => setMinFrequency(v ?? 0)}
                />
              </Form.Item>
            ) : null}
            <Form.Item label=" ">
              <Button type="primary" htmlType="submit" loading={loading}>
                查询
              </Button>
            </Form.Item>
            <Form.Item label=" ">
              <Button type="default" onClick={resetDefaults}>
                恢复默认
              </Button>
            </Form.Item>
          </div>
        </Form>
        {error ? (
          <Alert style={{ marginTop: 12 }} type="error" showIcon message={`请求失败: ${error}`} />
        ) : null}
      </section>

      <section className="panel-section panel-section--table">
        <div className="table-toolbar">
          <Typography.Title level={5} className="panel-section-title panel-section-title--inline">
            {tableDetailTitle}
          </Typography.Title>
          <Space size={8} wrap className="table-toolbar-meta">
            <span className="meta-chip">本地 {total} 条</span>
            <span className="meta-chip">
              第 {page} / {totalPages} 页
            </span>
            <Button
              type="default"
              loading={exporting}
              disabled={loading}
              onClick={() => void handleExport()}
            >
              导出 Excel
            </Button>
          </Space>
        </div>
        {loading && !hasLoaded ? (
          <Skeleton active paragraph={{ rows: 10 }} />
        ) : (
          <div className="table-wrap">
            <Table
              rowKey={(record) =>
                String(record.demand_id ?? "").trim() ||
                `${record.strategy ?? ""}|${record.demand_name ?? ""}`
              }
              loading={loading}
              columns={columns}
              dataSource={pagedItems}
              pagination={false}
              scroll={{ x: mode === "monthly" ? 1150 : 880 }}
              rowClassName={(_, index) => (index % 2 === 0 ? "row-even" : "row-odd")}
            />
          </div>
        )}
        <Modal
          title={`视频列表${videoModalTitleName ? ` — ${videoModalTitleName}` : ""}`}
          open={videoModalOpen}
          onCancel={() => setVideoModalOpen(false)}
          footer={
            <Button type="primary" onClick={() => setVideoModalOpen(false)}>
              关闭
            </Button>
          }
          width={900}
          destroyOnHidden
        >
          {videoModalIds.length > 0 ? (
            <List
              size="small"
              bordered
              dataSource={videoModalIds}
              style={{ maxHeight: 480, overflow: "auto" }}
              renderItem={(vid, idx) => (
                <List.Item style={{ paddingBlock: 10, paddingInline: 16 }}>
                  <div
                    style={{
                      display: "flex",
                      alignItems: "center",
                      width: "100%",
                      flexWrap: "nowrap",
                      gap: 12,
                    }}
                  >
                    <span
                      style={{
                        display: "inline-flex",
                        alignItems: "baseline",
                        gap: "0.25em",
                        whiteSpace: "nowrap",
                        minWidth: 0,
                      }}
                    >
                      <Typography.Text type="secondary" style={{ margin: 0 }}>
                        {idx + 1}.
                      </Typography.Text>
                      <Typography.Text copyable={{ text: vid }} style={{ margin: 0 }}>
                        {vid}
                      </Typography.Text>
                    </span>
                    <Space size={4} style={{ marginLeft: "auto", flexShrink: 0 }}>
                      <Button
                        type="link"
                        size="small"
                        onClick={() => openVideoCmsDetail(vid)}
                      >
                        查看视频详情
                      </Button>
                      <Button
                        type="link"
                        size="small"
                        loading={decodeLoadingVid === vid}
                        onClick={() => void openVideoDecodePage(vid)}
                      >
                        查看视频解构
                      </Button>
                    </Space>
                  </div>
                </List.Item>
              )}
            />
          ) : (
            <Typography.Paragraph
              copyable={{ text: videoModalRaw }}
              style={{ marginBottom: 0, whiteSpace: "pre-wrap", wordBreak: "break-all" }}
            >
              {videoModalRaw || "(空)"}
            </Typography.Paragraph>
          )}
        </Modal>
        <Modal
          title={`月份列表${monthModalTitleName ? ` — ${monthModalTitleName}` : ""}`}
          open={monthModalOpen}
          onCancel={() => setMonthModalOpen(false)}
          footer={
            <Button type="primary" onClick={() => setMonthModalOpen(false)}>
              关闭
            </Button>
          }
          width={560}
          destroyOnHidden
        >
          {monthModalMonths.length > 0 ? (
            <List
              size="small"
              bordered
              dataSource={monthModalMonths}
              style={{ maxHeight: 480, overflow: "auto" }}
              renderItem={(ym, idx) => (
                <List.Item style={{ paddingBlock: 10, paddingInline: 16 }}>
                  <Typography.Text copyable={{ text: ym }}>
                    {idx + 1}. {formatYmDisplay(ym)}
                  </Typography.Text>
                </List.Item>
              )}
            />
          ) : (
            <Typography.Paragraph
              copyable={{ text: monthModalRaw }}
              style={{ marginBottom: 0, whiteSpace: "pre-wrap", wordBreak: "break-all" }}
            >
              {monthModalRaw || "(空)"}
            </Typography.Paragraph>
          )}
        </Modal>
        <div className="panel-footer">
          <Pagination
            current={page}
            total={total}
            pageSize={pageSize}
            showSizeChanger
            pageSizeOptions={["10", "20", "50", "100"]}
            showQuickJumper
            showTotal={(t) => `共 ${t} 条`}
            onChange={(nextPage, size) => {
              const nextSize = size ?? pageSize;
              if (nextSize !== pageSize) {
                setPageSize(nextSize);
                setPage(1);
                return;
              }
              setPage(nextPage);
            }}
            onShowSizeChange={(_, size) => {
              setPageSize(size);
              setPage(1);
            }}
          />
        </div>
      </section>
    </div>
  );
}

type HotContentDemandExportItem = {
  id: number;
  source: string;
  hot_title: string;
  item_text: string;
  point_category: string;
  item_type: string;
  item_type_label: string;
  matched_demand: string;
  is_as_demand: number;
  is_as_demand_label: string;
  contribution_score: number | null;
  wxindex_keyword: string;
  all_hot_keywords: string;
  wxindex_latest_score: number;
  wxindex_trend: string;
  event_sense_score: number | null;
  senior_fit_score: number | null;
  record_created_at: string;
};

type HotContentDemandExportResponse = {
  total: number;
  page: number;
  page_size: number;
  items: HotContentDemandExportItem[];
};

type IsAsDemandFilter = "all" | "yes" | "no";
type MatchedDemandFilter = "all" | "yes" | "no";
type ItemTypeFilter = "all" | "word" | "point";

function HotContentDemandExportPanel({ active }: { active: boolean }) {
  const [dateRange, setDateRange] = useState<[Dayjs, Dayjs] | null>(() => {
    const today = dayjs();
    return [today, today];
  });
  const [isAsDemandFilter, setIsAsDemandFilter] = useState<IsAsDemandFilter>("all");
  const [matchedDemandFilter, setMatchedDemandFilter] = useState<MatchedDemandFilter>("all");
  const [itemTypeFilter, setItemTypeFilter] = useState<ItemTypeFilter>("all");
  const [appliedIsAsDemand, setAppliedIsAsDemand] = useState<IsAsDemandFilter>("all");
  const [appliedMatchedDemand, setAppliedMatchedDemand] = useState<MatchedDemandFilter>("all");
  const [appliedItemType, setAppliedItemType] = useState<ItemTypeFilter>("all");
  const [minWxindexInput, setMinWxindexInput] = useState<number | null>(null);
  const [appliedMinWxindex, setAppliedMinWxindex] = useState<number | null>(null);
  const [minEventSenseInput, setMinEventSenseInput] = useState<number | null>(null);
  const [appliedMinEventSense, setAppliedMinEventSense] = useState<number | null>(null);
  const [minSeniorFitInput, setMinSeniorFitInput] = useState<number | null>(null);
  const [appliedMinSeniorFit, setAppliedMinSeniorFit] = useState<number | null>(null);
  const [currentPage, setCurrentPage] = useState(1);
  const [pageSize, setPageSize] = useState(20);
  const [refreshTick, setRefreshTick] = useState(0);
  const [loading, setLoading] = useState(false);
  const [exporting, setExporting] = useState(false);
  const [hasLoaded, setHasLoaded] = useState(false);
  const [error, setError] = useState("");
  const [data, setData] = useState<HotContentDemandExportResponse>({
    total: 0,
    page: 1,
    page_size: pageSize,
    items: [],
  });

  const startDate = dateRange?.[0]?.format("YYYYMMDD") ?? "";
  const endDate = dateRange?.[1]?.format("YYYYMMDD") ?? "";
  const dateRangeInvalid =
    Boolean(startDate) && Boolean(endDate) && startDate > endDate;

  const appendFiltersToUrl = (url: URL) => {
    if (startDate) {
      url.searchParams.set("start_dt", startDate);
    }
    if (endDate) {
      url.searchParams.set("end_dt", endDate);
    }
    if (appliedIsAsDemand === "yes") {
      url.searchParams.set("is_as_demand", "1");
    } else if (appliedIsAsDemand === "no") {
      url.searchParams.set("is_as_demand", "0");
    }
    if (appliedMatchedDemand === "yes") {
      url.searchParams.set("has_matched_demand", "1");
    } else if (appliedMatchedDemand === "no") {
      url.searchParams.set("has_matched_demand", "0");
    }
    if (appliedItemType === "word") {
      url.searchParams.set("item_type", "词");
    } else if (appliedItemType === "point") {
      url.searchParams.set("item_type", "点");
    }
    if (appliedMinWxindex !== null) {
      url.searchParams.set("min_wxindex_latest_score", String(appliedMinWxindex));
    }
    if (appliedMinEventSense !== null) {
      url.searchParams.set("min_event_sense_score", String(appliedMinEventSense));
    }
    if (appliedMinSeniorFit !== null) {
      url.searchParams.set("min_senior_fit_score", String(appliedMinSeniorFit));
    }
  };

  const buildRequestUrl = (page: number, size: number = pageSize) => {
    const resolvedBase = getResolvedApiBaseUrl();
    const baseWithSlash = resolvedBase.endsWith("/")
      ? resolvedBase
      : `${resolvedBase}/`;
    const url = new URL("hot-content/demand-exports", baseWithSlash);
    appendFiltersToUrl(url);
    url.searchParams.set("page", String(page));
    url.searchParams.set("page_size", String(size));
    return url.toString();
  };

  const buildExportUrl = () => {
    const resolvedBase = getResolvedApiBaseUrl();
    const baseWithSlash = resolvedBase.endsWith("/")
      ? resolvedBase
      : `${resolvedBase}/`;
    const url = new URL("hot-content/demand-exports/export", baseWithSlash);
    appendFiltersToUrl(url);
    return url.toString();
  };

  const queryKey = JSON.stringify({
    startDate,
    endDate,
    appliedIsAsDemand,
    appliedMatchedDemand,
    appliedItemType,
    appliedMinWxindex,
    appliedMinEventSense,
    appliedMinSeniorFit,
    currentPage,
    pageSize,
    refreshTick,
    active,
  });

  const fetchData = async (page: number, size: number = pageSize) => {
    setLoading(true);
    setError("");
    try {
      const response = await fetch(buildRequestUrl(page, size), {
        method: "GET",
        headers: { Accept: "application/json" },
      });
      if (!response.ok) {
        const detail = await response.text();
        throw new Error(detail || `HTTP ${response.status}`);
      }
      const payload = (await response.json()) as HotContentDemandExportResponse;
      setData(payload);
    } catch (queryError) {
      setError(
        queryError instanceof Error ? queryError.message : "查询失败，请重试",
      );
    } finally {
      setLoading(false);
      setHasLoaded(true);
    }
  };

  useEffect(() => {
    if (!active || dateRangeInvalid) {
      return;
    }
    void fetchData(currentPage, pageSize);
  }, [queryKey, dateRangeInvalid]);

  const handleSubmit = () => {
    if (dateRangeInvalid) {
      return;
    }
    setAppliedIsAsDemand(isAsDemandFilter);
    setAppliedMatchedDemand(matchedDemandFilter);
    setAppliedItemType(itemTypeFilter);
    setAppliedMinWxindex(minWxindexInput);
    setAppliedMinEventSense(minEventSenseInput);
    setAppliedMinSeniorFit(minSeniorFitInput);
    setCurrentPage(1);
    setRefreshTick((value) => value + 1);
  };

  const handleExport = async () => {
    if (dateRangeInvalid) {
      return;
    }
    setExporting(true);
    try {
      await downloadExcelExport(buildExportUrl(), "新热事件查询.xlsx");
      message.success("导出成功");
    } catch {
      message.error("导出失败，请重试");
    } finally {
      setExporting(false);
    }
  };

  const resetFilters = () => {
    const today = dayjs();
    setDateRange([today, today]);
    setIsAsDemandFilter("all");
    setMatchedDemandFilter("all");
    setItemTypeFilter("all");
    setAppliedIsAsDemand("all");
    setAppliedMatchedDemand("all");
    setAppliedItemType("all");
    setMinWxindexInput(null);
    setAppliedMinWxindex(null);
    setMinEventSenseInput(null);
    setAppliedMinEventSense(null);
    setMinSeniorFitInput(null);
    setAppliedMinSeniorFit(null);
    setCurrentPage(1);
    setRefreshTick((value) => value + 1);
  };

  const totalPages = Math.max(1, Math.ceil(data.total / pageSize));

  const columns: ColumnsType<HotContentDemandExportItem> = useMemo(
    () => [
      {
        title: "来源",
        dataIndex: "source",
        width: 100,
        ellipsis: true,
        fixed: "left",
        render: (v: string) => <EllipsisCell value={v} />,
      },
      {
        title: "热点标题",
        dataIndex: "hot_title",
        width: 220,
        ellipsis: true,
        fixed: "left",
        render: (v: string) => <EllipsisCell value={v} />,
      },
      {
        title: "词条",
        dataIndex: "item_text",
        width: 160,
        ellipsis: true,
        fixed: "left",
        render: (v: string) => <EllipsisCell value={v} />,
      },
      {
        title: "类型",
        dataIndex: "point_category",
        width: 100,
        fixed: "left",
        render: (v: string) => <EllipsisCell value={v} />,
      },
      {
        title: "需求类型",
        dataIndex: "item_type_label",
        width: 90,
        align: "center",
        fixed: "left",
      },
      {
        title: "是否成为需求",
        dataIndex: "is_as_demand_label",
        width: 120,
        align: "center",
        fixed: "left",
      },
      {
        title: "匹配需求",
        dataIndex: "matched_demand",
        width: 200,
        ellipsis: true,
        render: (v: string) => <EllipsisCell value={v} />,
      },
      {
        title: "创建时间",
        dataIndex: "record_created_at",
        width: 170,
      },
      {
        title: "贡献分",
        dataIndex: "contribution_score",
        width: 100,
        align: "right",
        render: (v: number | null) =>
          v === null || v === undefined ? "-" : Number(v).toFixed(2),
      },
      {
        title: "最高微信指数词",
        dataIndex: "wxindex_keyword",
        width: 160,
        ellipsis: true,
        render: (v: string) => <EllipsisCell value={v} />,
      },
      {
        title: "待选微信指数词",
        dataIndex: "all_hot_keywords",
        width: 200,
        ellipsis: true,
        render: (v: string) => <EllipsisCell value={v} />,
      },
      {
        title: "微信指数热度",
        dataIndex: "wxindex_latest_score",
        width: 120,
        align: "right",
        render: (v: number) => Number(v ?? 0).toLocaleString(),
      },
      { title: "微信指数趋势", dataIndex: "wxindex_trend", width: 110, render: (v: string) => <EllipsisCell value={v} /> },
      {
        title: "事件性得分",
        dataIndex: "event_sense_score",
        width: 110,
        align: "right",
        render: (v: number | null) =>
          v === null || v === undefined ? "-" : Number(v).toFixed(2),
      },
      {
        title: "老年性得分",
        dataIndex: "senior_fit_score",
        width: 110,
        align: "right",
        render: (v: number | null) =>
          v === null || v === undefined ? "-" : Number(v).toFixed(2),
      },
    ],
    [],
  );

  return (
    <div className="panel-sheet">
      <section className="panel-section panel-section--filters">
        <header className="panel-section-head">
          <span className="panel-section-accent" aria-hidden />
          <Typography.Title level={5} className="panel-section-title">
            筛选条件
          </Typography.Title>
        </header>
        <Form layout="vertical" onFinish={handleSubmit} className="filter-form">
          <div className="filter-row second-row hot-content-demand-filter-row">
            <Form.Item label="日期区间（热点记录创建时间）">
              <DatePicker.RangePicker
                value={dateRange}
                onChange={(values) => setDateRange(values as [Dayjs, Dayjs] | null)}
                allowClear={false}
              />
            </Form.Item>
            <Form.Item label="是否是需求">
              <Select<IsAsDemandFilter>
                className="hot-content-filter-select"
                value={isAsDemandFilter}
                onChange={setIsAsDemandFilter}
                options={[
                  { label: "全部", value: "all" },
                  { label: "是", value: "yes" },
                  { label: "否", value: "no" },
                ]}
              />
            </Form.Item>
            <Form.Item label="需求类型">
              <Select<ItemTypeFilter>
                className="hot-content-filter-select"
                value={itemTypeFilter}
                onChange={setItemTypeFilter}
                options={[
                  { label: "全部", value: "all" },
                  { label: "特征点", value: "word" },
                  { label: "短语", value: "point" },
                ]}
              />
            </Form.Item>
            <Form.Item label="是否匹配需求">
              <Select<MatchedDemandFilter>
                className="hot-content-filter-select"
                value={matchedDemandFilter}
                onChange={setMatchedDemandFilter}
                options={[
                  { label: "全部", value: "all" },
                  { label: "是", value: "yes" },
                  { label: "否", value: "no" },
                ]}
              />
            </Form.Item>
            <Form.Item label="微信指数热度 ≥">
              <InputNumber
                className="hot-content-wxindex-input"
                min={0}
                step={10000}
                placeholder="不限制"
                value={minWxindexInput}
                onChange={(v) => setMinWxindexInput(v ?? null)}
              />
            </Form.Item>
            <Form.Item label="事件性得分 ≥">
              <InputNumber
                className="hot-content-wxindex-input"
                min={0}
                max={10}
                step={0.1}
                placeholder="不限制"
                value={minEventSenseInput}
                onChange={(v) => setMinEventSenseInput(v ?? null)}
              />
            </Form.Item>
            <Form.Item label="老年性得分 ≥">
              <InputNumber
                className="hot-content-wxindex-input"
                min={0}
                max={10}
                step={0.1}
                placeholder="不限制"
                value={minSeniorFitInput}
                onChange={(v) => setMinSeniorFitInput(v ?? null)}
              />
            </Form.Item>
            <Form.Item label=" ">
              <Button
                type="primary"
                htmlType="submit"
                loading={loading}
                disabled={dateRangeInvalid}
              >
                查询
              </Button>
            </Form.Item>
            <Form.Item label=" ">
              <Button type="default" onClick={resetFilters}>
                重置
              </Button>
            </Form.Item>
          </div>
        </Form>
        {dateRangeInvalid ? (
          <Alert
            style={{ marginTop: 12 }}
            type="error"
            showIcon
            message="开始日期不能晚于结束日期"
          />
        ) : null}
        {error ? (
          <Alert style={{ marginTop: 12 }} type="error" showIcon message={`请求失败: ${error}`} />
        ) : null}
      </section>

      <section className="panel-section panel-section--table">
        <div className="table-toolbar">
          <Typography.Title level={5} className="panel-section-title panel-section-title--inline">
            新热事件明细
          </Typography.Title>
          <Space size={8} wrap className="table-toolbar-meta">
            <span className="meta-chip">共 {data.total} 条</span>
            <span className="meta-chip">
              第 {currentPage} / {totalPages} 页
            </span>
            <Button
              type="default"
              loading={exporting}
              disabled={dateRangeInvalid || loading}
              onClick={() => void handleExport()}
            >
              导出 Excel
            </Button>
          </Space>
        </div>
        {loading && !hasLoaded ? (
          <Skeleton active paragraph={{ rows: 10 }} />
        ) : (
          <div className="table-wrap">
            <Table
              rowKey="id"
              loading={loading}
              columns={columns}
              dataSource={data.items}
              pagination={false}
              scroll={{ x: 2100 }}
              rowClassName={(_, index) => (index % 2 === 0 ? "row-even" : "row-odd")}
            />
          </div>
        )}
        <div className="panel-footer">
          <Pagination
            current={currentPage}
            total={data.total}
            pageSize={pageSize}
            showSizeChanger
            pageSizeOptions={["10", "20", "50", "100"]}
            showQuickJumper
            showTotal={(total) => `共 ${total} 条`}
            onChange={(page, size) => {
              const nextSize = size ?? pageSize;
              setCurrentPage(page);
              if (nextSize !== pageSize) {
                setPageSize(nextSize);
              }
            }}
            onShowSizeChange={(_, size) => {
              setPageSize(size);
              setCurrentPage(1);
            }}
          />
        </div>
      </section>
    </div>
  );
}

function SolarCalendarPanel({ active }: { active: boolean }) {
  return (
    <ElementDemandQueryPanel
      active={active}
      apiPath="element-demands/solar-calendar"
      periodDaysLabel="区间天数（含去年阳历今日）"
      tableDetailTitle="去年同期阳历特征点明细"
    />
  );
}

function LunarCalendarPanel({ active }: { active: boolean }) {
  return (
    <ElementDemandQueryPanel
      active={active}
      apiPath="element-demands/lunar-calendar"
      periodDaysLabel="区间天数（含去年阴历今日）"
      tableDetailTitle="去年同期阴历特征点明细"
    />
  );
}

function MonthlyDemandPanel({ active }: { active: boolean }) {
  return (
    <ElementDemandQueryPanel
      active={active}
      apiPath="element-demands/monthly"
      periodDaysLabel=""
      tableDetailTitle="逐月特征点明细"
      mode="monthly"
    />
  );
}

function App() {
  const [activeTab, setActiveTab] = useState("demand-pool");
  const [hotSourceView, setHotSourceView] = useState<HotSourceViewParams | null>(() =>
    readHotSourceViewFromUrl(),
  );

  useEffect(() => {
    const onPopState = () => {
      setHotSourceView(readHotSourceViewFromUrl());
    };
    window.addEventListener("popstate", onPopState);
    return () => window.removeEventListener("popstate", onPopState);
  }, []);

  const tabItems = useMemo(
    () => [
      {
        key: "demand-pool",
        label: "需求池",
        children: <DemandPoolPanel />,
      },
      {
        key: "solar-calendar",
        label: "去年同期阳历特征点查询",
        children: (
          <SolarCalendarPanel active={activeTab === "solar-calendar"} />
        ),
      },
      {
        key: "lunar-calendar",
        label: "去年同期阴历特征点查询",
        children: (
          <LunarCalendarPanel active={activeTab === "lunar-calendar"} />
        ),
      },
      {
        key: "monthly-demand",
        label: "逐月特征点查询",
        children: (
          <MonthlyDemandPanel active={activeTab === "monthly-demand"} />
        ),
      },
      {
        key: "hot-content-demand",
        label: "新热事件查询",
        children: (
          <HotContentDemandExportPanel active={activeTab === "hot-content-demand"} />
        ),
      },
    ],
    [activeTab],
  );

  if (hotSourceView) {
    return (
      <HotContentSourcePage
        demandName={hotSourceView.demandName}
        demandType={hotSourceView.demandType}
        dt={hotSourceView.dt}
        onBack={closeHotSourceView}
      />
    );
  }

  return (
    <>
      <DemandNavBar active="dashboard" />
      <div className="page">
        <div className="hero">
          <Typography.Title level={2} className="hero-title">
            需求池数据看板
          </Typography.Title>
          <div className="hero-subtitle">
            <Tag color="blue">数据检索</Tag>
            <Tag color="cyan">策略筛选</Tag>
            <Tag color="geekblue">日期范围分析</Tag>
            <Tag color="purple">需求筛选</Tag>
          </div>
        </div>

        <div className="dashboard-shell">
          <Tabs
            className="main-tabs demand-nav-tabs"
            activeKey={activeTab}
            onChange={setActiveTab}
            tabBarGutter={16}
            items={tabItems}
          />
        </div>
      </div>
    </>
  );
}

export default App;
