// OverviewView：经营概览首屏 —— Hero KPI、充值趋势、渠道构成、App 对比、区域 Top5。
// 数据契约见 SocialBiApp.jsx 的 useSocialBi()；本视图只做展示与聚合，不直接调 API。

import { useMemo, useState } from "react";
import Button from "@mui/material/Button";
import { EChart } from "../../charts/EChart.jsx";
import { deltaDirection, formatCount, formatDeltaRate, formatMoney, formatPercentValue, isBlank } from "../format.js";
import { aggregateMetricMaps, appColor, formatMetric, metricChartValue, metricLabel, metricTooltip } from "../metrics.js";
import { SocialAppIdentity, useSocialBi } from "../SocialBiApp.jsx";
import { bucketDay } from "../state.js";
import "./overview-view.css";

const HERO_METRICS = [
  { deltaKey: "recharge_delta_rate", key: "recharge_usd_minor" },
  { deltaKey: "new_users_delta_rate", key: "new_users" },
  { deltaKey: "active_users_delta_rate", key: "active_users" },
  { deltaKey: "paid_users_delta_rate", key: "paid_users" },
  { deltaKey: "arppu_delta_rate", key: "arppu_usd_minor" },
  { deltaKey: "", key: "paid_conversion_rate" }
];

const TREND_METRICS = [
  { key: "recharge_usd_minor", label: "充值", requirementSection: "revenue" },
  { key: "new_users", label: "新增", requirementSection: "new_users" },
  { key: "active_users", label: "日活", requirementSection: "active" },
  { key: "gift_coin_spent", label: "礼物", requirementSection: "gift" },
  { key: "game_turnover", label: "游戏", requirementSection: "game" },
  { key: "d1_retention_rate", label: "留存", requirementSection: "retention" }
];

const COMPARE_METRICS = [
  { key: "recharge_usd_minor", label: "充值" },
  { key: "active_users", label: "日活" },
  { key: "new_users", label: "新增" }
];

const CHANNEL_METRICS = ["google_recharge_usd_minor", "mifapay_recharge_usd_minor", "coin_seller_recharge_usd_minor"];
const CHANNEL_COLORS = ["#2557d6", "#0e7490", "#d97706"];

const MONEY_METRIC_KEYS = new Set([
  "recharge_usd_minor",
  "google_recharge_usd_minor",
  "mifapay_recharge_usd_minor",
  "coin_seller_recharge_usd_minor"
]);
const RATIO_METRIC_KEYS = new Set(["d1_retention_rate"]);

// 图表值已由 metricChartValue 换算（金额→美元），tooltip 里按指标类型补格式。
function formatChartAmount(metricKey, value) {
  if (isBlank(value)) {
    return "--";
  }
  if (RATIO_METRIC_KEYS.has(metricKey)) {
    return formatPercentValue(value);
  }
  return MONEY_METRIC_KEYS.has(metricKey) ? formatMoney(value) : formatCount(value);
}

// 日行按 bucketDay 分桶，桶 key 升序；每桶保留原始行，由调用方再聚合。
function buildBuckets(rows, granularity) {
  const map = new Map();
  rows.forEach((row) => {
    const key = bucketDay(row.stat_day, granularity);
    if (!map.has(key)) {
      map.set(key, []);
    }
    map.get(key).push(row);
  });
  return [...map.keys()].sort().map((key) => ({ key, rows: map.get(key) }));
}

function buildStackedBarOption({ bucketKeys, series, valueMetric }) {
  const isRatioMetric = RATIO_METRIC_KEYS.has(valueMetric);
  return {
    grid: { bottom: 4, containLabel: true, left: 8, right: 8, top: 28 },
    legend: { itemHeight: 10, itemWidth: 10, left: 0, textStyle: { color: "#5b7089", fontSize: 12 }, top: 0 },
    series,
    tooltip: {
      axisPointer: { type: "shadow" },
      backgroundColor: "#ffffff",
      borderColor: "#e3eaf3",
      formatter: (params) => {
        const list = Array.isArray(params) ? params : [params];
        let total = null;
        const lines = list.map((item) => {
          const numeric = isBlank(item.value) || !Number.isFinite(Number(item.value)) ? null : Number(item.value);
          if (numeric !== null) {
            total = (total ?? 0) + numeric;
          }
          return `${item.marker}${item.seriesName}&nbsp;&nbsp;<b>${formatChartAmount(valueMetric, numeric)}</b>`;
        });
        const header = list[0]?.axisValueLabel || list[0]?.name || "";
        if (list.length > 1 && !isRatioMetric) {
          lines.push(`合计&nbsp;&nbsp;<b>${formatChartAmount(valueMetric, total)}</b>`);
        }
        return [header, ...lines].filter(Boolean).join("<br/>");
      },
      textStyle: { color: "#14263c", fontSize: 12 },
      trigger: "axis"
    },
    xAxis: {
      axisLabel: { color: "#5b7089", fontSize: 11 },
      axisLine: { lineStyle: { color: "#eef3f9" } },
      axisTick: { show: false },
      data: bucketKeys,
      type: "category"
    },
    yAxis: {
      axisLabel: { color: "#5b7089", fontSize: 11, ...(isRatioMetric ? { formatter: "{value}%" } : {}) },
      splitLine: { lineStyle: { color: "#eef3f9" } },
      type: "value"
    }
  };
}

function buildSparkOption(spark) {
  return {
    grid: { bottom: 2, left: 0, right: 0, top: 2 },
    series: [
      {
        areaStyle: { color: "rgba(37, 87, 214, 0.12)" },
        data: spark.map((point) => point.value),
        lineStyle: { color: "#2557d6", width: 2 },
        showSymbol: false,
        smooth: true,
        symbol: "none",
        type: "line"
      }
    ],
    xAxis: { boundaryGap: false, data: spark.map((point) => point.key), show: false, type: "category" },
    yAxis: { show: false, type: "value" }
  };
}

function HeroCard({ deltaValue, metricKey, spark, value }) {
  const sparkOption = useMemo(() => buildSparkOption(spark), [spark]);
  const hasSpark = spark.length > 1 && spark.some((point) => point.value !== null);
  return (
    <article className="sbi-card sbi-ov-hero-card">
      <header className="sbi-ov-hero-top">
        <span className="sbi-metric-hint sbi-ov-hero-label" title={metricTooltip(metricKey)}>
          {metricLabel(metricKey)}
        </span>
        {!isBlank(deltaValue) ? (
          <span className={`sbi-delta is-${deltaDirection(deltaValue)}`}>{formatDeltaRate(deltaValue)}</span>
        ) : null}
      </header>
      <strong className="sbi-ov-hero-value">{formatMetric(metricKey, value)}</strong>
      <div className="sbi-ov-hero-spark">{hasSpark ? <EChart className="sbi-ov-spark-chart" option={sparkOption} /> : null}</div>
    </article>
  );
}

function OverviewSkeleton() {
  return (
    <section className="sbi-ov">
      <div className="sbi-ov-hero">
        {HERO_METRICS.map((metric) => (
          <article className="sbi-card sbi-ov-hero-card" key={metric.key}>
            <span className="sbi-skeleton" style={{ height: 14, width: 72 }} />
            <span className="sbi-skeleton" style={{ height: 26, width: 110 }} />
            <span className="sbi-skeleton" style={{ height: 38, width: "100%" }} />
          </article>
        ))}
      </div>
      <section className="sbi-card sbi-ov-skeleton-card">
        <span className="sbi-skeleton" style={{ height: 300, width: "100%" }} />
      </section>
      <div className="sbi-ov-duo">
        <section className="sbi-card sbi-ov-skeleton-card">
          <span className="sbi-skeleton" style={{ height: 220, width: "100%" }} />
        </section>
        <section className="sbi-card sbi-ov-skeleton-card">
          <span className="sbi-skeleton" style={{ height: 220, width: "100%" }} />
        </section>
      </div>
      <section className="sbi-card sbi-ov-skeleton-card">
        <span className="sbi-skeleton" style={{ height: 180, width: "100%" }} />
      </section>
    </section>
  );
}

export function OverviewView() {
  const { appCodes, derived, filters, isLoading, openRequirementSection, setViewKey, updateFilter } = useSocialBi();
  const [trendMetric, setTrendMetric] = useState("recharge_usd_minor");
  const [compareMetric, setCompareMetric] = useState("recharge_usd_minor");

  // regionScoped 时 App 级视角统一切到区域行聚合，保证与顶栏区域选择一致。
  const dailyRows = derived.regionScoped ? derived.regionDaily : derived.appDaily;

  const totals = useMemo(
    () => aggregateMetricMaps(derived.regionScoped ? derived.regionTotals : derived.appTotals),
    [derived.appTotals, derived.regionScoped, derived.regionTotals]
  );

  const buckets = useMemo(() => buildBuckets(dailyRows, filters.granularity), [dailyRows, filters.granularity]);

  const bucketAggregates = useMemo(
    () => buckets.map((bucket) => ({ key: bucket.key, metrics: aggregateMetricMaps(bucket.rows) })),
    [buckets]
  );

  const heroSparks = useMemo(() => {
    const map = {};
    HERO_METRICS.forEach((metric) => {
      map[metric.key] = bucketAggregates.map((bucket) => ({
        key: bucket.key,
        value: metricChartValue(metric.key, bucket.metrics[metric.key])
      }));
    });
    return map;
  }, [bucketAggregates]);

  // 每 App × 每桶的聚合单元，供主趋势堆叠柱使用；App 顺序跟随顶栏 appCodes。
  const appBuckets = useMemo(() => {
    const keys = buckets.map((bucket) => bucket.key);
    const appIndex = new Map();
    buckets.forEach((bucket, bucketIdx) => {
      const byApp = new Map();
      bucket.rows.forEach((row) => {
        if (!byApp.has(row.app_code)) {
          byApp.set(row.app_code, []);
        }
        byApp.get(row.app_code).push(row);
      });
      byApp.forEach((rows, code) => {
        if (!appIndex.has(code)) {
          appIndex.set(code, { app_code: code, app_name: rows[0].app_name, cells: new Array(buckets.length).fill(null) });
        }
        appIndex.get(code).cells[bucketIdx] = aggregateMetricMaps(rows);
      });
    });
    const apps = [...appIndex.values()].sort((a, b) => appCodes.indexOf(a.app_code) - appCodes.indexOf(b.app_code));
    return { apps, keys };
  }, [appCodes, buckets]);

  const trendOption = useMemo(() => {
    const shouldStack = !RATIO_METRIC_KEYS.has(trendMetric);
    const series = appBuckets.apps.map((app) => ({
      barMaxWidth: 26,
      data: app.cells.map((cell) => (cell ? metricChartValue(trendMetric, cell[trendMetric]) : null)),
      itemStyle: { color: appColor(app.app_code, appCodes) },
      name: app.app_name,
      ...(shouldStack ? { stack: "total" } : {}),
      type: "bar"
    }));
    return buildStackedBarOption({ bucketKeys: appBuckets.keys, series, valueMetric: trendMetric });
  }, [appBuckets, appCodes, trendMetric]);

  const channelOption = useMemo(() => {
    const series = CHANNEL_METRICS.map((key, index) => ({
      barMaxWidth: 26,
      data: bucketAggregates.map((bucket) => metricChartValue(key, bucket.metrics[key])),
      itemStyle: { color: CHANNEL_COLORS[index] },
      name: metricLabel(key),
      stack: "channel",
      type: "bar"
    }));
    return buildStackedBarOption({ bucketKeys: bucketAggregates.map((bucket) => bucket.key), series, valueMetric: CHANNEL_METRICS[0] });
  }, [bucketAggregates]);

  const hasChannelData = useMemo(
    () => bucketAggregates.some((bucket) => CHANNEL_METRICS.some((key) => Number(bucket.metrics[key] ?? 0) > 0)),
    [bucketAggregates]
  );

  const compareRows = useMemo(() => {
    let rows;
    if (derived.regionScoped) {
      const byApp = new Map();
      derived.regionTotals.forEach((row) => {
        if (!byApp.has(row.app_code)) {
          byApp.set(row.app_code, { app_code: row.app_code, app_name: row.app_name, rows: [] });
        }
        byApp.get(row.app_code).rows.push(row);
      });
      rows = [...byApp.values()].map((entry) => ({
        app_code: entry.app_code,
        app_name: entry.app_name,
        metrics: aggregateMetricMaps(entry.rows)
      }));
    } else {
      rows = derived.appTotals.map((row) => ({ app_code: row.app_code, app_name: row.app_name, metrics: row }));
    }
    return rows.sort((a, b) => {
      const left = Number(a.metrics[compareMetric]);
      const right = Number(b.metrics[compareMetric]);
      return (Number.isFinite(right) ? right : 0) - (Number.isFinite(left) ? left : 0);
    });
  }, [compareMetric, derived.appTotals, derived.regionScoped, derived.regionTotals]);

  const compareTotal = compareRows.reduce((sum, row) => {
    const value = Number(row.metrics[compareMetric]);
    return Number.isFinite(value) && value > 0 ? sum + value : sum;
  }, 0);

  const topRegions = useMemo(() => {
    return [...derived.regionTotals]
      .sort((a, b) => (Number(b.recharge_usd_minor) || 0) - (Number(a.recharge_usd_minor) || 0))
      .slice(0, 5);
  }, [derived.regionTotals]);
  const topRegionMax = topRegions.reduce((max, row) => Math.max(max, Number(row.recharge_usd_minor) || 0), 0);

  const soloApp = !derived.regionScoped && derived.appTotals.length === 1 ? derived.appTotals[0] : null;
  const hasRestricted = derived.appTotals.some((row) => row.restricted) || derived.appErrors.some((row) => row.restricted);
  const hasTrend = appBuckets.keys.length > 0 && appBuckets.apps.length > 0;
  const trendConfig = TREND_METRICS.find((item) => item.key === trendMetric) || TREND_METRICS[0];

  if (isLoading && !derived.appTotals.length && !derived.regionTotals.length) {
    return <OverviewSkeleton />;
  }

  return (
    <section className="sbi-ov">
      {derived.appErrors.length || hasRestricted ? (
        <div className="sbi-ov-info">
          {derived.appErrors.map((item) => (
            <span className="sbi-badge is-warn" key={item.app_code}>
              {item.app_name}: {item.error}
            </span>
          ))}
          {hasRestricted ? <span className="sbi-badge">已按你的数据范围过滤</span> : null}
        </div>
      ) : null}

      <div className="sbi-ov-hero">
        {HERO_METRICS.map((metric) => (
          <HeroCard
            deltaValue={soloApp && metric.deltaKey ? soloApp[metric.deltaKey] : null}
            key={metric.key}
            metricKey={metric.key}
            spark={heroSparks[metric.key]}
            value={totals[metric.key]}
          />
        ))}
      </div>

      <section className="sbi-card">
        <header className="sbi-card-header">
          <strong>{trendConfig.label}趋势</strong>
          <small>{RATIO_METRIC_KEYS.has(trendMetric) ? "按 App 对比" : "按 App 堆叠"}</small>
          <div className="sbi-card-toolbar">
            {/* 趋势 Tab 与数据需求分组一一映射，按钮始终跟随当前指标进入对应明细。 */}
            <Button
              onClick={() => openRequirementSection(trendConfig.requirementSection)}
              size="small"
              title={`进入数据需求中的${trendConfig.label}明细`}
              variant="outlined"
            >
              查看明细
            </Button>
            <div className="sbi-seg" role="radiogroup" aria-label="趋势指标">
              {TREND_METRICS.map((item) => (
                <button
                  aria-checked={trendMetric === item.key}
                  className={trendMetric === item.key ? "is-active" : ""}
                  key={item.key}
                  onClick={() => setTrendMetric(item.key)}
                  role="radio"
                  title={metricTooltip(item.key)}
                  type="button"
                >
                  {item.label}
                </button>
              ))}
            </div>
          </div>
        </header>
        <div className="sbi-ov-card-body">
          {hasTrend ? (
            <EChart className="sbi-ov-trend-chart" option={trendOption} />
          ) : (
            <div className="sbi-empty">
              <strong>暂无趋势数据</strong>
              <span>当前筛选范围内没有日级数据，试试放宽日期或 App 筛选</span>
            </div>
          )}
        </div>
      </section>

      <div className="sbi-ov-duo">
        <section className="sbi-card">
          <header className="sbi-card-header">
            <strong>渠道构成</strong>
            <small>谷歌 / 三方 / 币商充值</small>
          </header>
          <div className="sbi-ov-card-body">
            {hasChannelData ? (
              <EChart className="sbi-ov-channel-chart" option={channelOption} />
            ) : (
              <div className="sbi-empty">
                <strong>暂无渠道拆分数据</strong>
                <span>所选范围内各渠道充值均为空或 0</span>
              </div>
            )}
          </div>
        </section>

        <section className="sbi-card">
          <header className="sbi-card-header">
            <strong>App 对比</strong>
            <small title={metricTooltip(compareMetric)}>按{COMPARE_METRICS.find((item) => item.key === compareMetric)?.label}占比</small>
            <div className="sbi-card-toolbar">
              <div className="sbi-seg" role="radiogroup" aria-label="对比指标">
                {COMPARE_METRICS.map((item) => (
                  <button
                    aria-checked={compareMetric === item.key}
                    className={compareMetric === item.key ? "is-active" : ""}
                    key={item.key}
                    onClick={() => setCompareMetric(item.key)}
                    role="radio"
                    title={metricTooltip(item.key)}
                    type="button"
                  >
                    {item.label}
                  </button>
                ))}
              </div>
            </div>
          </header>
          <div className="sbi-ov-card-body">
            {compareRows.length ? (
              <div className="sbi-ov-compare">
                {compareRows.map((row) => {
                  const raw = Number(row.metrics[compareMetric]);
                  const share = compareTotal > 0 && Number.isFinite(raw) && raw > 0 ? (raw / compareTotal) * 100 : 0;
                  const color = appColor(row.app_code, appCodes);
                  return (
                    <div className="sbi-ov-compare-row" key={row.app_code}>
                      <span className="sbi-ov-compare-name">
                        <SocialAppIdentity appCode={row.app_code} appName={row.app_name} />
                      </span>
                      <div className="sbi-ov-bar-track">
                        <div className="sbi-ov-bar-fill" style={{ background: color, width: `${share}%` }} />
                      </div>
                      <span className="sbi-ov-num">{formatMetric(compareMetric, row.metrics[compareMetric])}</span>
                      <button
                        className="sbi-ov-link"
                        onClick={() => {
                          updateFilter("apps", [row.app_code]);
                          setViewKey("regions");
                        }}
                        type="button"
                      >
                        地区 →
                      </button>
                    </div>
                  );
                })}
              </div>
            ) : (
              <div className="sbi-empty">
                <strong>暂无 App 数据</strong>
                <span>调整顶部 App / 日期筛选后重试</span>
              </div>
            )}
          </div>
        </section>
      </div>

      <section className="sbi-card">
        <header className="sbi-card-header">
          <strong>区域 Top5</strong>
          <small title={metricTooltip("recharge_usd_minor")}>按充值降序</small>
          <div className="sbi-card-toolbar">
            <button className="sbi-ov-link" onClick={() => setViewKey("regions")} type="button">
              查看全部 →
            </button>
          </div>
        </header>
        <div className="sbi-ov-card-body">
          {topRegions.length ? (
            <div className="sbi-ov-top">
              {topRegions.map((row) => {
                const raw = Number(row.recharge_usd_minor);
                const share = topRegionMax > 0 && Number.isFinite(raw) && raw > 0 ? (raw / topRegionMax) * 100 : 0;
                const color = appColor(row.app_code, appCodes);
                return (
                  <div className="sbi-ov-top-row" key={`${row.app_code}:${row.region_id}`}>
                    <span className="sbi-ov-top-name">
                      <strong>{row.region_name}</strong>
                      <span className="sbi-badge">
                        <SocialAppIdentity appCode={row.app_code} appName={row.app_name} />
                      </span>
                    </span>
                    <div className="sbi-ov-bar-track">
                      <div className="sbi-ov-bar-fill" style={{ background: color, width: `${share}%` }} />
                    </div>
                    <span className="sbi-ov-num">{formatMetric("recharge_usd_minor", row.recharge_usd_minor)}</span>
                  </div>
                );
              })}
            </div>
          ) : (
            <div className="sbi-empty">
              <strong>暂无区域数据</strong>
              <span>该范围未配置区域目录，或你没有对应区域的数据权限</span>
            </div>
          )}
        </div>
      </section>
    </section>
  );
}
