// RetentionView（留存质量）：整体 D1/D7/D30 概要卡、新增×次日留存组合图、
// 留存趋势（汇总/单 App）、App/区域留存热力对比表。数据契约见 SocialBiApp.jsx 的 useSocialBi()。

import { useMemo, useState } from "react";
import { 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 { 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_SERIES = [
  { color: CHART_ACCENT, key: "d1_retention_rate" },
  { color: CHART_ACCENT_2, key: "d7_retention_rate" },
  { color: "#7c3aed", key: "d30_retention_rate" }
];

const HEAT_KEYS = new Set(["d1_retention_rate", "d7_retention_rate", "d30_retention_rate"]);
const TABLE_METRIC_KEYS = [
  "new_users",
  "active_users",
  "d1_retention_rate",
  "d7_retention_rate",
  "d30_retention_rate",
  "paid_conversion_rate"
];

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

// 把日粒度行按 bucketDay 分桶后逐桶聚合（比例按分子分母重算），桶 key 升序。
function bucketAggregates(rows, granularity) {
  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) => aggregateMetricMaps(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"
  };
}

// 留存热力单元格：值越高蓝底越深（上限 0.85），深度超过 0.35 换白字保证对比度；null 无底色显示 "--"。
function renderMetricCell(key, value) {
  if (!HEAT_KEYS.has(key)) {
    return <td key={key}>{formatMetric(key, value)}</td>;
  }
  if (isBlank(value)) {
    return <td key={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={key} style={style}>
      {formatRatioPercent(value)}
    </td>
  );
}

export function RetentionView() {
  const { derived, filters, isLoading } = useSocialBi();
  const { appDaily, appTotals, regionDaily, regionScoped, regionTotals } = derived;
  const [trendApp, setTrendApp] = useState(ALL);
  const [tableDim, setTableDim] = useState("app");

  const granularityLabel = GRANULARITY_LABELS[filters.granularity] || "日";
  // 筛选变化后所选 App 可能不在结果里，回退到汇总（不直接改 state，渲染期兜底即可）。
  const activeTrendApp = trendApp !== ALL && appTotals.some((row) => row.app_code === trendApp) ? trendApp : ALL;
  // 选了具体区域时，全视图（概要/图表/对比表）统一切到区域行口径，避免同屏两套数字。
  const dailyRows = regionScoped ? regionDaily : appDaily;

  // 1) 概要卡：选了具体区域时 App 级合计改用区域行聚合，与顶栏语义一致。
  const summary = useMemo(() => {
    const rows = regionScoped ? regionTotals : appTotals;
    return rows.length ? aggregateMetricMaps(rows) : null;
  }, [appTotals, regionScoped, regionTotals]);

  // 2) 新增（柱/左轴）× 次日留存率（线/右轴），按全局粒度分桶；null 让线断开。
  const comboOption = useMemo(() => {
    const { aggregates, buckets } = bucketAggregates(dailyRows, filters.granularity);
    if (!buckets.length) {
      return null;
    }
    return {
      grid: { ...CHART_GRID },
      legend: { ...CHART_LEGEND },
      series: [
        {
          barMaxWidth: 26,
          data: aggregates.map((row) => metricChartValue("new_users", row.new_users)),
          itemStyle: { borderRadius: [3, 3, 0, 0], color: CHART_ACCENT },
          name: metricLabel("new_users"),
          tooltip: { valueFormatter: (value) => formatCount(value) },
          type: "bar"
        },
        {
          connectNulls: false,
          data: aggregates.map((row) => metricChartValue("d1_retention_rate", row.d1_retention_rate)),
          itemStyle: { color: CHART_ACCENT_2 },
          lineStyle: { color: CHART_ACCENT_2, width: 2.5 },
          name: metricLabel("d1_retention_rate"),
          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]);

  // 3) D1/D7/D30 留存趋势：汇总或单 App 的日行分桶聚合（区域筛选时用区域日行）。
  const trendOption = useMemo(() => {
    const rows = activeTrendApp === ALL ? dailyRows : dailyRows.filter((row) => row.app_code === activeTrendApp);
    const { aggregates, buckets } = bucketAggregates(rows, filters.granularity);
    if (!buckets.length) {
      return null;
    }
    return {
      grid: { ...CHART_GRID },
      legend: { ...CHART_LEGEND },
      series: RETENTION_SERIES.map(({ color, key }) => ({
        connectNulls: false,
        data: aggregates.map((row) => metricChartValue(key, row[key])),
        itemStyle: { color },
        lineStyle: { color, width: 2.5 },
        name: metricLabel(key),
        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]);

  // 4) 热力对比表：按 App / 按区域；区域筛选时"按 App"改为区域行按 App 聚合，保持口径一致。
  const tableSource = useMemo(() => {
    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)
    }));
  }, [appTotals, regionScoped, regionTotals, tableDim]);

  const tableRows = useMemo(() => {
    return tableSource
      .map((row) => ({
        key: tableDim === "region" ? `${row.app_code}:${row.region_id}` : row.app_code,
        label: tableDim === "region" ? `${row.app_name}·${row.region_name}` : row.app_name,
        row
      }))
      .sort((a, b) => (Number(b.row.new_users) || 0) - (Number(a.row.new_users) || 0));
  }, [tableDim, tableSource]);

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

  const hasRows = appTotals.length > 0 || appDaily.length > 0 || regionTotals.length > 0;

  if (isLoading && !hasRows) {
    return (
      <div className="sbi-rt-view">
        <div className="sbi-rt-summary">
          {RETENTION_SERIES.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 (!hasRows) {
    return (
      <div className="sbi-rt-view">
        <section className="sbi-card">
          <div className="sbi-empty">
            <strong>暂无留存数据</strong>
            <span>当前筛选没有返回任何 App 数据，试试调整日期区间或放宽 App/区域筛选。</span>
          </div>
        </section>
      </div>
    );
  }

  return (
    <div className="sbi-rt-view">
      <div className="sbi-rt-summary">
        {RETENTION_SERIES.map(({ key }) => {
          const baseUsers = summary?.[key.replace("_retention_rate", "_retention_base_users")];
          return (
            <section className="sbi-card sbi-rt-summary-card" key={key}>
              <span className="sbi-rt-summary-label sbi-metric-hint" title={metricTooltip(key)}>
                整体{metricLabel(key)}
              </span>
              <span className="sbi-rt-summary-value">{formatRatioPercent(summary?.[key])}</span>
              <span className="sbi-rt-summary-note">
                {regionScoped ? "所选区域汇总" : "全部所选 App 汇总"}
                {isBlank(baseUsers) ? "" : ` · cohort 样本 ${formatCount(baseUsers)} 人`}
              </span>
            </section>
          );
        })}
      </div>

      <section className="sbi-card">
        <div className="sbi-card-header">
          <strong>新增与次日留存</strong>
          <small>柱=新增（左轴）· 线=次日留存率（右轴）· 按{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>留存趋势</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>
              {appTotals.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"
                >
                  {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>留存对比</strong>
          <small>D1/D7/D30 底色越深留存越高</small>
          <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>
        </div>
        {tableRows.length ? (
          <div className="sbi-table-scroll">
            <table className="sbi-table">
              <thead>
                <tr>
                  <th className="is-left">对象</th>
                  {TABLE_METRIC_KEYS.map((key) => (
                    <th key={key}>
                      <span className="sbi-metric-hint" title={metricTooltip(key)}>
                        {metricLabel(key)}
                      </span>
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                <tr className="is-total">
                  <td className="is-left">合计</td>
                  {TABLE_METRIC_KEYS.map((key) => renderMetricCell(key, tableTotal?.[key]))}
                </tr>
                {tableRows.map((item) => (
                  <tr key={item.key}>
                    <td className="is-left">{item.label}</td>
                    {TABLE_METRIC_KEYS.map((key) => renderMetricCell(key, item.row[key]))}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        ) : (
          <div className="sbi-empty">
            <strong>暂无对比数据</strong>
            <span>{tableDim === "region" ? "当前筛选下没有区域行，试试放宽顶栏的区域选择。" : "当前筛选下没有 App 合计行。"}</span>
          </div>
        )}
      </section>

      <p className="sbi-rt-footnote">
        留存按注册日 cohort 计算；观察期未到的日期显示 "--"。hyapp App 的按日留存暂不提供（仅整段区间口径）。
      </p>
    </div>
  );
}
