// RetentionView（留存质量）：支持“全体活跃 cohort”与“新增注册 cohort”两套明确分离的留存口径。
// 整体次留只消费 SocialRequirements retention；新增次留继续消费 Overview 的注册 cohort。

import { useEffect, useMemo, useState } from "react";
import { SocialAppIdentity, useSocialBi } from "../SocialBiApp.jsx";
import { EChart } from "../../charts/EChart.jsx";
import { aggregateMetricMaps, formatMetric, metricChartValue, metricLabel, metricTooltip } from "../metrics.js";
import { formatCount, formatPercentValue, formatRatioPercent, isBlank } from "../format.js";
import { ACTIVE_RETENTION_WINDOWS, aggregateActiveRetentionRows, normalizeActiveRetentionRequirements } from "../activeRetention.js";
import { ALL, bucketDay } from "../state.js";
import "./retention-view.css";

// 图表 canvas 内无法读 CSS 变量，这里用与 social-v2.css token 一致的十六进制值。
const CHART_ACCENT = "#2557d6"; // var(--sbi-accent)
const CHART_ACCENT_2 = "#0e7490"; // var(--sbi-accent-2)
const CHART_GRID_LINE = "#eef3f9";
const CHART_TEXT = "#5b7089";
const CHART_GRID = { bottom: 4, containLabel: true, left: 8, right: 8, top: 28 };
const CHART_LEGEND = { itemHeight: 9, itemWidth: 14, textStyle: { color: CHART_TEXT, fontSize: 12 }, top: 0 };
const CHART_TOOLTIP = { backgroundColor: "#ffffff", borderColor: CHART_GRID_LINE, textStyle: { color: CHART_TEXT }, trigger: "axis" };
const RETENTION_COLORS = [CHART_ACCENT, CHART_ACCENT_2, "#7c3aed"];

const RETENTION_MODES = [
  { key: "active", label: "整体次留" },
  { key: "new", label: "新增次留" }
];

const ACTIVE_RETENTION_SERIES = ACTIVE_RETENTION_WINDOWS.map((item, index) => ({
  ...item,
  color: RETENTION_COLORS[index]
}));

const NEW_RETENTION_SERIES = [
  {
    baseKey: "d1_retention_base_users",
    color: RETENTION_COLORS[0],
    key: "d1_retention_rate",
    label: "次日留存",
    shortLabel: "D1",
    usersKey: "d1_retention_users"
  },
  {
    baseKey: "d7_retention_base_users",
    color: RETENTION_COLORS[1],
    key: "d7_retention_rate",
    label: "7日留存",
    shortLabel: "D7",
    usersKey: "d7_retention_users"
  },
  {
    baseKey: "d30_retention_base_users",
    color: RETENTION_COLORS[2],
    key: "d30_retention_rate",
    label: "30日留存",
    shortLabel: "D30",
    usersKey: "d30_retention_users"
  }
];

const ACTIVE_TABLE_COLUMNS = [
  {
    key: "active_d1_active_base_users",
    label: "活跃用户基数",
    tooltip: "基准日进入 App 的全体 canonical 活跃用户",
    type: "count"
  },
  {
    key: "active_d1_active_users",
    label: "次留人数",
    tooltip: "基准日活跃且次日继续活跃的 canonical 用户",
    type: "count"
  },
  {
    heat: true,
    key: "active_d1_active_rate",
    label: "整体次留",
    tooltip: "次留人数 / 活跃用户基数",
    type: "ratio"
  },
  { heat: true, key: "active_d7_active_rate", label: "整体7日留存", tooltip: "基准日活跃用户在 D+7 仍活跃的比例", type: "ratio" },
  { heat: true, key: "active_d30_active_rate", label: "整体30日留存", tooltip: "基准日活跃用户在 D+30 仍活跃的比例", type: "ratio" }
];

const NEW_TABLE_COLUMNS = ["new_users", "active_users", "d1_retention_rate", "d7_retention_rate", "d30_retention_rate", "paid_conversion_rate"].map(
  (key) => ({
    heat: key === "d1_retention_rate" || key === "d7_retention_rate" || key === "d30_retention_rate",
    key,
    label: metricLabel(key),
    tooltip: metricTooltip(key),
    type: "metric"
  })
);

const GRANULARITY_LABELS = { day: "日", month: "月", week: "周" };

// 把日粒度行按 bucketDay 分桶后逐桶聚合（比例按分子分母重算），桶 key 升序。
function bucketAggregates(rows, granularity, aggregateRows) {
  const map = new Map();
  rows.forEach((row) => {
    const bucket = bucketDay(row.stat_day, granularity);
    const list = map.get(bucket);
    if (list) {
      list.push(row);
    } else {
      map.set(bucket, [row]);
    }
  });
  const buckets = [...map.keys()].sort();
  return { aggregates: buckets.map((key) => aggregateRows(map.get(key))), buckets };
}

function categoryAxis(buckets) {
  return {
    axisLabel: { color: CHART_TEXT },
    axisLine: { lineStyle: { color: CHART_GRID_LINE } },
    axisTick: { show: false },
    data: buckets,
    type: "category"
  };
}

function chartRatio(value) {
  return isBlank(value) ? null : Number(value) * 100;
}

function formatTableMetric(column, value) {
  if (column.type === "ratio") {
    return formatRatioPercent(value);
  }
  if (column.type === "count") {
    return formatCount(value);
  }
  return formatMetric(column.key, value);
}

// 留存热力单元格：值越高蓝底越深（上限 0.85），深度超过 0.35 换白字保证对比度；null 无底色显示 "--"。
function renderMetricCell(column, value) {
  if (!column.heat) {
    return <td key={column.key}>{formatTableMetric(column, value)}</td>;
  }
  if (isBlank(value)) {
    return <td key={column.key}>--</td>;
  }
  const depth = Math.min(0.85, Number(value) * 2);
  const style = { backgroundColor: `rgba(37, 87, 214, ${Number(depth.toFixed(3))})` };
  if (depth > 0.35) {
    style.color = "#ffffff";
  }
  return (
    <td key={column.key} style={style}>
      {formatRatioPercent(value)}
    </td>
  );
}

function RetentionModeSwitch({ mode, onChange }) {
  return (
    <div className="sbi-rt-modebar">
      <span className="sbi-rt-mode-label">留存口径</span>
      <div className="sbi-seg sbi-rt-mode-seg" role="radiogroup" aria-label="留存口径">
        {RETENTION_MODES.map((item) => (
          <button
            aria-checked={mode === item.key}
            className={mode === item.key ? "is-active" : ""}
            key={item.key}
            onClick={() => onChange(item.key)}
            role="radio"
            type="button"
          >
            {item.label}
          </button>
        ))}
      </div>
    </div>
  );
}

function summaryNote({ base, isActiveMode, partial, regionScoped, users }) {
  const parts = [
    partial
      ? "成功返回的 Social App 子集"
      : isActiveMode
      ? regionScoped
        ? "所选区域 Social App 汇总"
        : "已接入 Social App 汇总"
      : regionScoped
        ? "所选区域汇总"
        : "全部所选 App 汇总"
  ];
  if (!isBlank(base)) {
    parts.push(`${isActiveMode ? "活跃" : "注册"}基数 ${formatCount(base)} 人`);
  }
  if (!isBlank(users)) {
    parts.push(`留存 ${formatCount(users)} 人`);
  }
  return parts.join(" · ");
}

export function RetentionView() {
  const {
    derived,
    filters,
    isLoading,
    isRequirementsLoading,
    loadRequirements,
    refreshToken,
    requirements,
    requirementsError
  } = useSocialBi();
  const { appDaily, appTotals, regionDaily, regionScoped, regionTotals } = derived;
  const [retentionMode, setRetentionMode] = useState("active");
  const [trendApp, setTrendApp] = useState(ALL);
  const [tableDim, setTableDim] = useState("app");
  const isActiveMode = retentionMode === "active";

  // 整体次留是独立的 canonical 用户日查询，只在该口径可见时加载；切回新增次留不为不可见数据重复请求。
  useEffect(() => {
    if (!isActiveMode) {
      return;
    }
    loadRequirements({ newUserType: "all", payerType: "all", section: "retention", userRole: "all" });
  }, [isActiveMode, loadRequirements, refreshToken]);

  const activeRetention = useMemo(() => normalizeActiveRetentionRequirements(requirements), [requirements]);
  const granularityLabel = GRANULARITY_LABELS[filters.granularity] || "日";
  const newRetentionDailyRows = regionScoped ? regionDaily : appDaily;
  const modeAppTotals = isActiveMode ? activeRetention.appTotals : appTotals;
  const dailyRows = isActiveMode ? activeRetention.appDaily : newRetentionDailyRows;
  const retentionSeries = isActiveMode ? ACTIVE_RETENTION_SERIES : NEW_RETENTION_SERIES;

  // 筛选或口径变化后所选 App 可能不在结果里，回退到汇总（不在渲染期直接改 state）。
  const activeTrendApp = trendApp !== ALL && modeAppTotals.some((row) => row.app_code === trendApp) ? trendApp : ALL;

  const summary = useMemo(() => {
    const rows = isActiveMode ? activeRetention.appTotals : regionScoped ? regionTotals : appTotals;
    if (!rows.length) {
      return null;
    }
    return isActiveMode ? aggregateActiveRetentionRows(rows) : aggregateMetricMaps(rows);
  }, [activeRetention.appTotals, appTotals, isActiveMode, regionScoped, regionTotals]);

  // 组合图在整体模式下是“活跃基数 + 全体用户次留”；新增模式保持“新增人数 + 注册 cohort 次留”。
  const comboOption = useMemo(() => {
    const aggregateRows = isActiveMode ? aggregateActiveRetentionRows : aggregateMetricMaps;
    const { aggregates, buckets } = bucketAggregates(dailyRows, filters.granularity, aggregateRows);
    if (!buckets.length) {
      return null;
    }
    const barKey = isActiveMode ? "active_d1_active_base_users" : "new_users";
    const lineKey = isActiveMode ? "active_d1_active_rate" : "d1_retention_rate";
    return {
      grid: { ...CHART_GRID },
      legend: { ...CHART_LEGEND },
      series: [
        {
          barMaxWidth: 26,
          data: aggregates.map((row) => metricChartValue(barKey, row[barKey])),
          itemStyle: { borderRadius: [3, 3, 0, 0], color: CHART_ACCENT },
          name: isActiveMode ? "活跃用户基数" : metricLabel("new_users"),
          tooltip: { valueFormatter: (value) => formatCount(value) },
          type: "bar"
        },
        {
          connectNulls: false,
          data: aggregates.map((row) => chartRatio(row[lineKey])),
          itemStyle: { color: CHART_ACCENT_2 },
          lineStyle: { color: CHART_ACCENT_2, width: 2.5 },
          name: isActiveMode ? "整体次日留存" : "新增次日留存",
          symbol: "circle",
          symbolSize: 6,
          tooltip: { valueFormatter: (value) => formatPercentValue(value) },
          type: "line",
          yAxisIndex: 1
        }
      ],
      tooltip: { ...CHART_TOOLTIP, axisPointer: { type: "shadow" } },
      xAxis: categoryAxis(buckets),
      yAxis: [
        {
          axisLabel: { color: CHART_TEXT },
          splitLine: { lineStyle: { color: CHART_GRID_LINE } },
          type: "value"
        },
        {
          axisLabel: { color: CHART_TEXT, formatter: "{value}%" },
          splitLine: { show: false },
          type: "value"
        }
      ]
    };
  }, [dailyRows, filters.granularity, isActiveMode]);

  // D1/D7/D30 趋势始终跟随当前口径；周/月桶均先合并人数和基数，再重算比例。
  const trendOption = useMemo(() => {
    const rows = activeTrendApp === ALL ? dailyRows : dailyRows.filter((row) => row.app_code === activeTrendApp);
    const aggregateRows = isActiveMode ? aggregateActiveRetentionRows : aggregateMetricMaps;
    const { aggregates, buckets } = bucketAggregates(rows, filters.granularity, aggregateRows);
    if (!buckets.length) {
      return null;
    }
    return {
      grid: { ...CHART_GRID },
      legend: { ...CHART_LEGEND },
      series: retentionSeries.map(({ color, key, label }) => ({
        connectNulls: false,
        data: aggregates.map((row) => chartRatio(row[key])),
        itemStyle: { color },
        lineStyle: { color, width: 2.5 },
        name: `${isActiveMode ? "整体" : "新增"}${label}`,
        symbol: "circle",
        symbolSize: 5,
        tooltip: { valueFormatter: (value) => formatPercentValue(value) },
        type: "line"
      })),
      tooltip: { ...CHART_TOOLTIP },
      xAxis: categoryAxis(buckets),
      yAxis: {
        axisLabel: { color: CHART_TEXT, formatter: "{value}%" },
        splitLine: { lineStyle: { color: CHART_GRID_LINE } },
        type: "value"
      }
    };
  }, [activeTrendApp, dailyRows, filters.granularity, isActiveMode, retentionSeries]);

  // SocialRequirements 当前提供 App 合计并跟随顶栏区域筛选，不伪造不存在的区域拆分；新增模式保留原区域对比。
  const tableSource = useMemo(() => {
    if (isActiveMode) {
      return activeRetention.appTotals;
    }
    if (tableDim === "region") {
      return regionTotals;
    }
    if (!regionScoped) {
      return appTotals;
    }
    const byApp = new Map();
    regionTotals.forEach((row) => {
      const list = byApp.get(row.app_code) || [];
      list.push(row);
      byApp.set(row.app_code, list);
    });
    return [...byApp.entries()].map(([appCode, rows]) => ({
      app_code: appCode,
      app_name: rows[0].app_name,
      ...aggregateMetricMaps(rows)
    }));
  }, [activeRetention.appTotals, appTotals, isActiveMode, regionScoped, regionTotals, tableDim]);

  const tableColumns = isActiveMode ? ACTIVE_TABLE_COLUMNS : NEW_TABLE_COLUMNS;
  const tableRows = useMemo(() => {
    const sortKey = isActiveMode ? "active_d1_active_base_users" : "new_users";
    return tableSource
      .map((row) => ({
        key: !isActiveMode && tableDim === "region" ? `${row.app_code}:${row.region_id}` : row.app_code,
        row
      }))
      .sort((a, b) => (Number(b.row[sortKey]) || 0) - (Number(a.row[sortKey]) || 0));
  }, [isActiveMode, tableDim, tableSource]);

  const tableTotal = useMemo(() => {
    if (!tableSource.length) {
      return null;
    }
    return isActiveMode ? aggregateActiveRetentionRows(tableSource) : aggregateMetricMaps(tableSource);
  }, [isActiveMode, tableSource]);

  const hasRows = modeAppTotals.length > 0 || dailyRows.length > 0;
  const partialErrors = isActiveMode ? activeRetention.appErrors : [];
  // 首次进入时 master/Overview 仍在加载，requirements 请求尚未启动；此阶段也保持整页骨架，
  // 避免在请求链衔接的一个渲染周期内错误闪出“暂无整体留存数据”。
  const modeLoading = isActiveMode ? isRequirementsLoading || (isLoading && !requirements) : isLoading;
  const modeError = isActiveMode ? requirementsError || (!hasRows ? partialErrors.join("；") : "") : "";
  const modeSwitch = <RetentionModeSwitch mode={retentionMode} onChange={setRetentionMode} />;

  if (modeLoading && !hasRows) {
    return (
      <div className="sbi-rt-view">
        {modeSwitch}
        <div className="sbi-rt-summary">
          {retentionSeries.map(({ key }) => (
            <section className="sbi-card sbi-rt-summary-card" key={key}>
              <span className="sbi-skeleton sbi-rt-skeleton-line" />
              <span className="sbi-skeleton sbi-rt-skeleton-value" />
            </section>
          ))}
        </div>
        <section className="sbi-card">
          <span className="sbi-skeleton sbi-rt-skeleton-chart" />
        </section>
        <section className="sbi-card">
          <span className="sbi-skeleton sbi-rt-skeleton-chart" />
        </section>
        <section className="sbi-card">
          <span className="sbi-skeleton sbi-rt-skeleton-table" />
        </section>
      </div>
    );
  }

  if (modeError && !hasRows) {
    return (
      <div className="sbi-rt-view">
        {modeSwitch}
        <section className="sbi-card">
          <div className="sbi-empty" role="alert">
            <strong>{modeError}</strong>
            <span>可切换到“新增次留”查看 Overview 注册 cohort 数据。</span>
          </div>
        </section>
      </div>
    );
  }

  if (!hasRows) {
    return (
      <div className="sbi-rt-view">
        {modeSwitch}
        <section className="sbi-card">
          <div className="sbi-empty">
            <strong>暂无{isActiveMode ? "整体" : "新增"}留存数据</strong>
            <span>当前筛选没有返回任何 App 数据，试试调整日期区间或放宽 App/区域筛选。</span>
          </div>
        </section>
      </div>
    );
  }

  return (
    <div className="sbi-rt-view">
      {modeSwitch}

      {partialErrors.length ? (
        <div className="sbi-rt-warning" role="alert">
          <strong>部分 App 整体留存加载失败，以下仅展示成功返回的 App，不作为完整汇总。</strong>
          {partialErrors.map((message) => (
            <span key={message}>{message}</span>
          ))}
        </div>
      ) : null}

      <div className="sbi-rt-summary">
        {retentionSeries.map(({ baseKey, key, label, usersKey }) => (
          <section className="sbi-card sbi-rt-summary-card" key={key}>
            <span className="sbi-rt-summary-label sbi-metric-hint" title={isActiveMode ? `${label}：基准日全体活跃用户在对应观察日仍活跃的比例` : metricTooltip(key)}>
              {isActiveMode ? "整体" : "新增"}{label}
            </span>
            <span className="sbi-rt-summary-value">{formatRatioPercent(summary?.[key])}</span>
            <span className="sbi-rt-summary-note">
              {summaryNote({
                base: summary?.[baseKey],
                isActiveMode,
                partial: partialErrors.length > 0,
                regionScoped,
                users: summary?.[usersKey]
              })}
            </span>
          </section>
        ))}
      </div>

      <section className="sbi-card">
        <div className="sbi-card-header">
          <strong>{isActiveMode ? "活跃基数与次日留存" : "新增与次日留存"}</strong>
          <small>
            {isActiveMode
              ? `柱=基准日全体活跃用户（左轴）· 线=该批用户次日继续活跃率（右轴）· 按${granularityLabel}分桶`
              : `柱=当日新增（左轴）· 线=前一日注册用户的当日留存率（右轴）· 按${granularityLabel}分桶`}
          </small>
        </div>
        {comboOption ? (
          <EChart className="sbi-rt-chart-combo" option={comboOption} />
        ) : (
          <div className="sbi-empty">
            <strong>暂无按日数据</strong>
            <span>当前筛选没有返回按日序列，无法绘制留存趋势。</span>
          </div>
        )}
      </section>

      <section className="sbi-card">
        <div className="sbi-card-header">
          <strong>{isActiveMode ? "整体用户留存趋势" : "新增用户留存趋势"}</strong>
          <small>D1 / D7 / D30 · 按{granularityLabel}分桶</small>
          <div className="sbi-card-toolbar">
            <div className="sbi-seg" role="radiogroup" aria-label="留存趋势维度">
              <button
                aria-checked={activeTrendApp === ALL}
                className={activeTrendApp === ALL ? "is-active" : ""}
                onClick={() => setTrendApp(ALL)}
                role="radio"
                type="button"
              >
                汇总
              </button>
              {modeAppTotals.map((row) => (
                <button
                  aria-checked={activeTrendApp === row.app_code}
                  className={activeTrendApp === row.app_code ? "is-active" : ""}
                  key={row.app_code}
                  onClick={() => setTrendApp(row.app_code)}
                  role="radio"
                  type="button"
                >
                  <SocialAppIdentity appCode={row.app_code} appName={row.app_name} />
                </button>
              ))}
            </div>
          </div>
        </div>
        {trendOption ? (
          <EChart className="sbi-rt-chart-trend" option={trendOption} />
        ) : (
          <div className="sbi-empty">
            <strong>暂无留存趋势数据</strong>
            <span>{activeTrendApp === ALL ? "当前筛选没有返回按日序列。" : "该 App 在当前区间没有按日序列。"}</span>
          </div>
        )}
      </section>

      <section className="sbi-card sbi-rt-table-card">
        <div className="sbi-card-header">
          <strong>{isActiveMode ? "整体留存对比" : "新增留存对比"}</strong>
          <small>{isActiveMode ? "全体活跃用户 cohort · 按 App 对比并跟随顶部区域筛选" : "D1/D7/D30 底色越深留存越高"}</small>
          {!isActiveMode ? (
            <div className="sbi-card-toolbar">
              <div className="sbi-seg" role="radiogroup" aria-label="留存对比维度">
                <button
                  aria-checked={tableDim === "app"}
                  className={tableDim === "app" ? "is-active" : ""}
                  onClick={() => setTableDim("app")}
                  role="radio"
                  type="button"
                >
                  按 App
                </button>
                <button
                  aria-checked={tableDim === "region"}
                  className={tableDim === "region" ? "is-active" : ""}
                  onClick={() => setTableDim("region")}
                  role="radio"
                  type="button"
                >
                  按区域
                </button>
              </div>
            </div>
          ) : null}
        </div>
        {tableRows.length ? (
          <div className="sbi-table-scroll">
            <table className="sbi-table">
              <thead>
                <tr>
                  <th className="is-left">对象</th>
                  {tableColumns.map((column) => (
                    <th key={column.key}>
                      <span className="sbi-metric-hint" title={column.tooltip}>
                        {column.label}
                      </span>
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                <tr className="is-total">
                  <td className="is-left">合计</td>
                  {tableColumns.map((column) => renderMetricCell(column, tableTotal?.[column.key]))}
                </tr>
                {tableRows.map((item) => (
                  <tr key={item.key}>
                    <td className="is-left">
                      <SocialAppIdentity appCode={item.row.app_code} appName={item.row.app_name} />
                      {!isActiveMode && tableDim === "region" ? <span> · {item.row.region_name}</span> : null}
                    </td>
                    {tableColumns.map((column) => renderMetricCell(column, item.row[column.key]))}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        ) : (
          <div className="sbi-empty">
            <strong>暂无对比数据</strong>
            <span>{!isActiveMode && tableDim === "region" ? "当前筛选下没有区域行，试试放宽顶栏的区域选择。" : "当前筛选下没有 App 合计行。"}</span>
          </div>
        )}
      </section>

      <p className="sbi-rt-footnote">
        {isActiveMode
          ? "整体留存按 cohort 基准日计算：某日的 N 日留存 = 该日全体 canonical 活跃用户中，在 D+N 仍活跃的人数 / 基准日活跃用户数；未到观察日的窗口显示“--”，不计入合计。"
          : "新增留存按观察日回看计算：某日的 N 日留存 = 该日往前推 N 天注册的用户中当日仍活跃的比例；区间合计 = 各观察日留存人数之和 / 各观察日注册 cohort 基数之和。"}
      </p>
    </div>
  );
}
