import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import ApartmentOutlined from "@mui/icons-material/ApartmentOutlined";
import AppsOutlined from "@mui/icons-material/AppsOutlined";
import GroupsOutlined from "@mui/icons-material/GroupsOutlined";
import PublicOutlined from "@mui/icons-material/PublicOutlined";
import QueryStatsOutlined from "@mui/icons-material/QueryStatsOutlined";
import RepeatOutlined from "@mui/icons-material/RepeatOutlined";
import Button from "@mui/material/Button";
import Checkbox from "@mui/material/Checkbox";
import { fetchSocialBiFilterOptions, fetchStatisticsOverview } from "../api.js";
import {
  ALL_VALUE,
  socialBiAppOptions,
  socialBiDepartmentOptions,
  socialBiFilterDefinitions,
  socialBiOperatorOptions,
  socialBiRegionOptions,
  socialBiSections
} from "../data/socialBiDashboard.js";
import { DEFAULT_TIME_ZONE, rangeEndMs, rangeStartMs, thisMonthRange, todayRange } from "../utils/time.js";

const collator = new Intl.Collator("zh-Hans", { numeric: true, sensitivity: "base" });
const SOCIAL_BI_STAT_TZ = DEFAULT_TIME_ZONE;
const dateRangeOptions = [
  { label: "昨日", value: "yesterday" },
  { label: "近 7 日", value: "last7" },
  { label: "近 30 日", value: "last30" },
  { label: "本月", value: "month" }
];

const sectionIcons = {
  appData: AppsOutlined,
  department: ApartmentOutlined,
  personnel: GroupsOutlined,
  regional: PublicOutlined,
  retention: RepeatOutlined
};

export function SocialBiDashboard() {
  const [activeSectionKey, setActiveSectionKey] = useState(socialBiSections[0].key);
  const [filters, setFilters] = useState(() => createInitialFilters());
  const [filterOptions, setFilterOptions] = useState(() => ({
    apps: socialBiAppOptions,
    operators: socialBiOperatorOptions
  }));
  const [loadingToken, setLoadingToken] = useState(0);
  const [isLoading, setIsLoading] = useState(true);
  const [loadError, setLoadError] = useState("");
  const [realRows, setRealRows] = useState(() => createEmptyRealRows());
  const [openFilterKey, setOpenFilterKey] = useState(null);
  const [sortBySection, setSortBySection] = useState(() => createInitialSortState());
  const filterSectionRef = useRef(null);
  const filterDefinitions = useMemo(() => createRuntimeFilterDefinitions(filterOptions), [filterOptions]);
  const runtimeSections = useMemo(() => applyRealRowsToSections(realRows), [realRows]);
  const activeSection = useMemo(
    () => runtimeSections.find((section) => section.key === activeSectionKey) || runtimeSections[0],
    [activeSectionKey, runtimeSections]
  );
  const filteredRows = useMemo(() => applySectionFilters(activeSection, filters, filterDefinitions), [activeSection, filterDefinitions, filters]);
  const sortState = sortBySection[activeSection.key] || defaultSortState(activeSection);
  const sortedRows = useMemo(() => sortRows(filteredRows, activeSection.columns, sortState), [activeSection.columns, filteredRows, sortState]);
  const totalRow = useMemo(() => createTotalRow(activeSection, filteredRows), [activeSection, filteredRows]);
  const summaryCards = useMemo(() => createSummaryCards(activeSection, filteredRows, totalRow), [activeSection, filteredRows, totalRow]);

  useEffect(() => {
    let ignore = false;
    const range = dateRangeToCalendarRange(filters.dateRange);
    const appCodes = selectedSocialBiAppCodes(filters.apps, filterOptions.apps);
    setIsLoading(true);
    setLoadError("");
    fetchSocialBiOverviewRows({ appCodes, range })
      .then((rows) => {
        if (!ignore) {
          setRealRows(rows);
        }
      })
      .catch((err) => {
        if (!ignore) {
          setRealRows(createEmptyRealRows());
          setLoadError(err.message || "数据加载失败");
        }
      })
      .finally(() => {
        if (!ignore) {
          setIsLoading(false);
        }
      });
    return () => {
      ignore = true;
    };
  }, [filterOptions.apps, filters.apps, filters.dateRange, loadingToken]);

  useEffect(() => {
    setOpenFilterKey(null);
  }, [activeSectionKey]);

  useEffect(() => {
    const handlePointerDown = (event) => {
      if (!filterSectionRef.current?.contains(event.target)) {
        setOpenFilterKey(null);
      }
    };

    document.addEventListener("pointerdown", handlePointerDown);
    return () => document.removeEventListener("pointerdown", handlePointerDown);
  }, []);

  useEffect(() => {
    let ignore = false;
    fetchSocialBiFilterOptions()
      .then((options) => {
        if (ignore) {
          return;
        }
        const nextOptions = {
          apps: options.appOptions?.length ? options.appOptions : socialBiAppOptions,
          operators: options.operatorOptions?.length ? options.operatorOptions : socialBiOperatorOptions
        };
        setFilterOptions((current) => {
          if (sameOptionValues(current.apps, nextOptions.apps) && sameOptionValues(current.operators, nextOptions.operators)) {
            return current;
          }
          return nextOptions;
        });
      })
      .catch(() => {
        if (!ignore) {
          setFilterOptions({ apps: socialBiAppOptions, operators: socialBiOperatorOptions });
        }
      });
    return () => {
      ignore = true;
    };
  }, []);

  const updateFilter = useCallback((key, value) => {
    setFilters((current) => ({ ...current, [key]: value }));
  }, []);

  const handleQuery = useCallback(() => {
    setOpenFilterKey(null);
    setLoadingToken((current) => current + 1);
  }, []);

  const toggleFilterPopup = useCallback((key) => {
    setOpenFilterKey((current) => (current === key ? null : key));
  }, []);

  const handleSort = useCallback((column) => {
    setSortBySection((current) => {
      const previous = current[activeSection.key] || defaultSortState(activeSection);
      const direction = previous.key === column.key && previous.direction === "desc" ? "asc" : "desc";
      return {
        ...current,
        [activeSection.key]: { direction, key: column.key }
      };
    });
  }, [activeSection]);

  return (
    <main className="social-bi-shell">
      <SocialBiSidebar activeKey={activeSection.key} onSectionChange={setActiveSectionKey} />
      <section className="social-bi-main" aria-label="Social BI 数据中心">
        <FilterSection
          activeSection={activeSection}
          filterRef={filterSectionRef}
          filters={filters}
          isLoading={isLoading}
          loadError={loadError}
          filterDefinitions={filterDefinitions}
          onCloseDropdowns={() => setOpenFilterKey(null)}
          onFilterChange={updateFilter}
          onQuery={handleQuery}
          onToggleDropdown={toggleFilterPopup}
          openFilterKey={openFilterKey}
        />

        <SummarySection cards={summaryCards} isLoading={isLoading} />

        <WideDataTable
          columns={activeSection.columns}
          isLoading={isLoading}
          onSort={handleSort}
          rows={sortedRows}
          sortState={sortState}
          totalRow={totalRow}
        />
      </section>
    </main>
  );
}

function SocialBiSidebar({ activeKey, onSectionChange }) {
  return (
    <aside className="social-bi-sidebar" aria-label="Social BI 栏目">
      <div className="social-bi-sidebar-brand">
        <span>HY</span>
        <strong>BI 后台</strong>
      </div>
      <nav className="social-bi-sidebar-nav" role="tablist" aria-label="数据看板栏目">
        {socialBiSections.map((section) => {
          const Icon = sectionIcons[section.key] || QueryStatsOutlined;
          const isActive = activeKey === section.key;
          return (
            <button
              aria-label={section.label}
              aria-selected={isActive}
              className={isActive ? "social-bi-nav-item is-active" : "social-bi-nav-item"}
              key={section.key}
              onClick={() => onSectionChange(section.key)}
              role="tab"
              title={section.label}
              type="button"
            >
              <Icon fontSize="small" />
              <span aria-hidden="true">{section.label}</span>
            </button>
          );
        })}
      </nav>
    </aside>
  );
}

function FilterSection({
  activeSection,
  filterDefinitions,
  filterRef,
  filters,
  isLoading,
  loadError,
  onCloseDropdowns,
  onFilterChange,
  onQuery,
  onToggleDropdown,
  openFilterKey
}) {
  return (
    <section className="social-bi-filter-section" aria-label="查询筛选区" ref={filterRef}>
      <div className="social-bi-filter-grid">
        {activeSection.filters.map((filterKey) => {
          const definition = filterDefinitions[filterKey];
          if (definition.type === "dateRange") {
            return (
              <DateRangeControl
                key={filterKey}
                label={definition.label}
                onChange={(value) => {
                  onCloseDropdowns();
                  onFilterChange(filterKey, value);
                }}
                value={filters[filterKey]}
              />
            );
          }
          if (definition.type === "searchMulti") {
            return (
              <SearchableMultiSelectControl
                isOpen={openFilterKey === filterKey}
                key={filterKey}
                label={definition.label}
                onChange={(value) => onFilterChange(filterKey, value)}
                onToggle={() => onToggleDropdown(filterKey)}
                options={definition.options}
                placeholder={definition.placeholder}
                value={filters[filterKey]}
              />
            );
          }
          if (definition.type === "cascade") {
            return (
              <CascadeMultiSelectControl
                isOpen={openFilterKey === filterKey}
                key={filterKey}
                label={definition.label}
                onChange={(value) => onFilterChange(filterKey, value)}
                onToggle={() => onToggleDropdown(filterKey)}
                options={definition.options}
                value={filters[filterKey]}
              />
            );
          }
          return (
            <MultiSelectControl
              isOpen={openFilterKey === filterKey}
              key={filterKey}
              label={definition.label}
              onChange={(value) => onFilterChange(filterKey, value)}
              onToggle={() => onToggleDropdown(filterKey)}
              options={definition.options}
              value={filters[filterKey]}
            />
          );
        })}
      </div>
      <Button
        className="social-bi-query-button"
        disabled={isLoading}
        disableElevation
        onClick={onQuery}
        type="button"
        variant="contained"
      >
        {isLoading ? "查询中" : "查询"}
      </Button>
      {loadError ? <span className="social-bi-load-error">{loadError}</span> : null}
    </section>
  );
}

function DateRangeControl({ label, onChange, value }) {
  const activeOption = dateRangeOptions.find((item) => item.value === value) || dateRangeOptions[0];
  return (
    <div className="social-bi-field social-bi-date-field">
      <div className="social-bi-date-options" role="radiogroup" aria-label={label}>
        {dateRangeOptions.map((option) => (
          <button
            aria-checked={option.value === activeOption.value}
            className={option.value === activeOption.value ? "is-active" : ""}
            key={option.value}
            onClick={() => onChange(option.value)}
            role="radio"
            type="button"
          >
            <span>{option.label}</span>
          </button>
        ))}
      </div>
    </div>
  );
}

function MultiSelectControl({ isOpen, label, onChange, onToggle, options, value }) {
  return (
    <details className="social-bi-field social-bi-dropdown" open={isOpen}>
      <summary
        onClick={(event) => {
          event.preventDefault();
          onToggle();
        }}
      >
        <span className="social-bi-field-label">{label}</span>
        <strong>{selectedOptionsLabel(options, value)}</strong>
      </summary>
      <div className="social-bi-dropdown-menu" role="listbox" aria-label={label}>
        {options.map((option) => (
          <OptionToggle
            checked={value.includes(option.value)}
            key={option.value}
            label={option.label}
            onClick={() => onChange(toggleFlatSelection(value, option.value, options))}
          />
        ))}
      </div>
    </details>
  );
}

function SearchableMultiSelectControl({ isOpen, label, onChange, onToggle, options, placeholder, value }) {
  const [query, setQuery] = useState("");
  const filteredOptions = useMemo(() => {
    const keyword = query.trim().toLowerCase();
    if (!keyword) {
      return options;
    }
    return options.filter((option) => `${option.label} ${option.searchText || ""}`.toLowerCase().includes(keyword));
  }, [options, query]);

  return (
    <details className="social-bi-field social-bi-dropdown" open={isOpen}>
      <summary
        onClick={(event) => {
          event.preventDefault();
          onToggle();
        }}
      >
        <span className="social-bi-field-label">{label}</span>
        <strong>{selectedOptionsLabel(options, value)}</strong>
      </summary>
      <div className="social-bi-dropdown-menu social-bi-dropdown-menu--search" role="listbox" aria-label={label}>
        <input
          aria-label={placeholder}
          onChange={(event) => setQuery(event.target.value)}
          placeholder={placeholder}
          type="text"
          value={query}
        />
        {filteredOptions.map((option) => (
          <OptionToggle
            checked={value.includes(option.value)}
            key={option.value}
            label={option.label}
            onClick={() => onChange(toggleFlatSelection(value, option.value, options))}
          />
        ))}
        {!filteredOptions.length ? <span className="social-bi-dropdown-empty">当前无数据</span> : null}
      </div>
    </details>
  );
}

function CascadeMultiSelectControl({ isOpen, label, onChange, onToggle, options, value }) {
  return (
    <details className="social-bi-field social-bi-dropdown social-bi-dropdown--wide" open={isOpen}>
      <summary
        onClick={(event) => {
          event.preventDefault();
          onToggle();
        }}
      >
        <span className="social-bi-field-label">{label}</span>
        <strong>{selectedCascadeLabel(options, value)}</strong>
      </summary>
      <div className="social-bi-dropdown-menu social-bi-cascade-menu" role="tree" aria-label={label}>
        <OptionToggle
          checked={value.includes(ALL_VALUE)}
          label="全部地区"
          onClick={() => onChange([ALL_VALUE])}
        />
        {options.map((group) => (
          <div className="social-bi-cascade-group" key={group.value}>
            <OptionToggle
              checked={value.includes(group.value)}
              label={group.label}
              onClick={() => onChange(toggleCascadeGroup(value, group))}
            />
            <div className="social-bi-cascade-children">
              {group.children.map((child) => (
                <OptionToggle
                  checked={value.includes(child.value)}
                  key={child.value}
                  label={child.label}
                  onClick={() => onChange(toggleCascadeChild(value, child.value))}
                />
              ))}
            </div>
          </div>
        ))}
      </div>
    </details>
  );
}

function OptionToggle({ checked, label, onClick }) {
  return (
    <button className={checked ? "social-bi-option is-selected" : "social-bi-option"} onClick={onClick} role="option" type="button">
      <Checkbox checked={checked} disableRipple size="small" tabIndex={-1} />
      <span>{label}</span>
    </button>
  );
}

function SummarySection({ cards, isLoading }) {
  return (
    <section className="social-bi-summary-section" aria-label="汇总指标卡片">
      {cards.map((card) => (
        <article className={isLoading ? "social-bi-summary-card is-loading" : "social-bi-summary-card"} key={card.label}>
          {isLoading ? (
            <>
              <span className="social-bi-skeleton social-bi-skeleton--title" />
              <span className="social-bi-skeleton social-bi-skeleton--value" />
              <span className="social-bi-skeleton social-bi-skeleton--line" />
            </>
          ) : (
            <>
              <span>{card.label}</span>
              <strong>{formatTypedValue(card.value, card.type)}</strong>
              <DeltaBadge value={card.delta} />
            </>
          )}
        </article>
      ))}
    </section>
  );
}

function WideDataTable({ columns, isLoading, onSort, rows, sortState, totalRow }) {
  return (
    <section className="social-bi-table-section" aria-label="数据明细宽表">
      <div className="social-bi-table-head">
        <span>首行汇总 Total</span>
      </div>
      <div className="social-bi-table-scroll">
        <table className="social-bi-table">
          <thead>
            <tr>
              {columns.map((column) => (
                <th className={`is-${column.align}`} key={column.key} scope="col">
                  <button
                    className={sortState.key === column.key ? `is-active is-${sortState.direction}` : ""}
                    onClick={() => onSort(column)}
                    type="button"
                  >
                    <span>{column.label}</span>
                    <span className="social-bi-sort-arrow">{sortState.key === column.key ? (sortState.direction === "desc" ? "↓" : "↑") : "↕"}</span>
                  </button>
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {isLoading ? <SkeletonRows columns={columns} /> : null}
            {!isLoading && rows.length ? (
              <>
                <DataRow columns={columns} row={totalRow} total />
                {rows.map((row) => (
                  <DataRow columns={columns} key={row.id} row={row} />
                ))}
              </>
            ) : null}
            {!isLoading && !rows.length ? (
              <tr className="social-bi-empty-row">
                <td colSpan={columns.length}>当前无数据</td>
              </tr>
            ) : null}
          </tbody>
        </table>
      </div>
    </section>
  );
}

function SkeletonRows({ columns }) {
  return Array.from({ length: 5 }, (_, rowIndex) => (
    <tr className="social-bi-skeleton-row" key={rowIndex}>
      {columns.map((column) => (
        <td key={column.key}>
          <span className="social-bi-skeleton social-bi-skeleton--cell" />
        </td>
      ))}
    </tr>
  ));
}

function DataRow({ columns, row, total = false }) {
  return (
    <tr className={total ? "is-total" : ""}>
      {columns.map((column) => (
        <td className={`is-${column.align}`} key={column.key}>
          {renderCell(row[column.key], column)}
        </td>
      ))}
    </tr>
  );
}

function DeltaBadge({ value }) {
  if (value === null || value === undefined || value === "" || !Number.isFinite(Number(value))) {
    return null;
  }
  const isUp = Number(value) >= 0;
  return (
    <small className={isUp ? "social-bi-delta is-up" : "social-bi-delta is-down"}>
      较昨日 {isUp ? "+" : ""}
      {Number(value).toFixed(1)}%
    </small>
  );
}

function ProgressCell({ value }) {
  if (!value || value.noGoal || !value.target) {
    return <span className="social-bi-goal-empty">无目标</span>;
  }
  const rate = Math.max(0, Math.min(100, (Number(value.actual || 0) / Number(value.target || 1)) * 100));
  return (
    <div className="social-bi-progress-cell">
      <div className="social-bi-progress-copy">
        <strong>
          {formatMoney(value.actual)} / {formatMoney(value.target)}
        </strong>
        <span>{rate.toFixed(1)}%</span>
      </div>
      <div className="social-bi-progress-track" aria-hidden="true">
        <span style={{ width: `${rate}%` }} />
      </div>
    </div>
  );
}

function renderCell(value, column) {
  if (column.type === "progress") {
    return <ProgressCell value={value} />;
  }
  if (column.type === "ratio") {
    if (!value) {
      return "--";
    }
    return `${formatNumber(value?.active || 0)} / ${formatNumber(value?.total || 0)}`;
  }
  if (column.type === "flowRate") {
    return <MetricPairCell primary={formatNumber(value?.value)} secondary={formatPercentValue(value?.rate)} />;
  }
  if (column.type === "consumeOutput") {
    const secondary = value?.delta === null || value?.delta === undefined ? "" : `差额 ${formatNumber(value.delta)}`;
    return <MetricPairCell primary={formatPercentValue(value?.rate)} secondary={secondary} />;
  }
  if (column.type === "duration") {
    return formatDuration(value);
  }
  return formatTypedValue(value, column.type);
}

function MetricPairCell({ primary, secondary }) {
  const hasPrimary = primary && primary !== "--";
  const hasSecondary = secondary && secondary !== "--";
  if (!hasPrimary && !hasSecondary) {
    return "--";
  }
  return (
    <span className="social-bi-pair-cell">
      <strong>{hasPrimary ? primary : "--"}</strong>
      {hasSecondary ? <small>{secondary}</small> : null}
    </span>
  );
}

function createEmptyRealRows() {
  return {
    appData: [],
    department: [],
    personnel: [],
    regional: [],
    retention: []
  };
}

function applyRealRowsToSections(realRows) {
  return socialBiSections.map((section) => ({
    ...section,
    rows: realRows[section.key] || []
  }));
}

async function fetchSocialBiOverviewRows({ appCodes, range }) {
  if (!appCodes.length) {
    return createEmptyRealRows();
  }
  const startMs = rangeStartMs(range, SOCIAL_BI_STAT_TZ);
  const endMs = rangeEndMs(range, SOCIAL_BI_STAT_TZ);
  const results = await Promise.allSettled(
    appCodes.map(async (appCode) => {
      const overview = await fetchStatisticsOverview({
        appCode,
        countryId: 0,
        endMs,
        regionId: "all",
        seriesEndMs: endMs,
        seriesStartMs: startMs,
        startMs,
        statTz: SOCIAL_BI_STAT_TZ
      });
      return { appCode, overview: overview || {} };
    })
  );
  const fulfilled = results.filter((item) => item.status === "fulfilled").map((item) => item.value);
  if (!fulfilled.length && results.length) {
    throw results[0].reason || new Error("数据加载失败");
  }
  const rows = createEmptyRealRows();
  fulfilled.forEach(({ appCode, overview }) => {
    rows.appData.push(createAppDataRow(appCode, overview, range));
    const countryRows = normalizeSocialCountries(appCode, overview, range);
    rows.regional.push(...countryRows.map(createRegionalRow));
    rows.retention.push(...countryRows.map(createRetentionRow));
  });
  return rows;
}

function createAppDataRow(appCode, overview, range) {
  return {
    appName: appLabel(appCode, overview),
    date: rangeLabel(range),
    id: `app-${appCode}`,
    ...createSocialMetricFields(overview)
  };
}

function normalizeSocialCountries(appCode, overview, range) {
  const countries = Array.isArray(overview?.country_breakdown) ? overview.country_breakdown : [];
  return countries.map((country, index) => ({
    appCode,
    appName: appLabel(appCode, overview),
    country,
    date: rangeLabel(range),
    id: `${appCode}-country-${country.country_code || country.countryCode || country.country_id || country.countryId || index}`,
    region: countryLabel(country),
    ...createSocialMetricFields(country)
  }));
}

function createSocialMetricFields(source) {
  return {
    arpu: moneyMajor(readOptionalNumber(source, "arpu_usd_minor", "arpuUsdMinor")),
    arppu: moneyMajor(readOptionalNumber(source, "arppu_usd_minor", "arppuUsdMinor", "up_value_usd_minor", "upValueUsdMinor")),
    coinSellerRecharge: moneyMajor(readOptionalNumber(source, "coin_seller_recharge_usd_minor", "coinSellerRechargeUsdMinor")),
    coinSellerStockCoin: readOptionalNumber(source, "coin_seller_stock_coin", "coinSellerStockCoin"),
    coinSellerTransferCoin: readOptionalNumber(source, "coin_seller_transfer_coin", "coinSellerTransferCoin"),
    coins: readOptionalNumber(source, "coin_total", "coinTotal"),
    consumedCoin: readOptionalNumber(source, "consumed_coin", "consumedCoin"),
    consumeOutputRatio: consumeOutputValue(source),
    dau: readOptionalNumber(source, "active_users", "activeUsers"),
    gameFlowProfit: metricFlowRateValue(source, ["game_turnover", "gameTurnover"], ["game_profit_rate", "gameProfitRate"], ["game_profit", "gameProfit"]),
    giftCoinSpent: readOptionalNumber(source, "gift_coin_spent", "giftCoinSpent"),
    googleRecharge: moneyMajor(readOptionalNumber(source, "google_recharge_usd_minor", "googleRechargeUsdMinor")),
    luckyGiftFlowProfit: metricFlowRateValue(
      source,
      ["lucky_gift_turnover", "luckyGiftTurnover", "room_lucky_gift_turnover", "roomLuckyGiftTurnover"],
      ["lucky_gift_profit_rate", "luckyGiftProfitRate"],
      ["lucky_gift_profit", "luckyGiftProfit"]
    ),
    manualGrantCoin: readOptionalNumber(source, "manual_grant_coin", "manualGrantCoin"),
    avgMicOnlineMs: readOptionalNumber(source, "avg_mic_online_ms", "avgMicOnlineMs"),
    micOnlineMs: readOptionalNumber(source, "mic_online_ms", "micOnlineMs"),
    newUserRecharge: moneyMajor(readOptionalNumber(source, "new_user_recharge_usd_minor", "newUserRechargeUsdMinor")),
    newUsers: readOptionalNumber(source, "new_users", "newUsers", "registrations", "registered_users", "registeredUsers"),
    outputCoin: readOptionalNumber(source, "output_coin", "outputCoin"),
    paidConversionRate: percentageValue(readOptionalNumber(source, "paid_conversion_rate", "paidConversionRate", "payer_rate", "payerRate")),
    paidUsers: readOptionalNumber(source, "paid_users", "paidUsers"),
    platformGrantCoin: readOptionalNumber(source, "platform_grant_coin", "platformGrantCoin"),
    recharge: moneyMajor(readOptionalNumber(source, "recharge_usd_minor", "rechargeUsdMinor", "total_recharge_usd_minor", "totalRechargeUsdMinor")),
    rechargeConversionRate: percentageValue(readOptionalNumber(source, "recharge_conversion_rate", "rechargeConversionRate")),
    rechargeUsers: readOptionalNumber(source, "recharge_users", "rechargeUsers"),
    retentionD1: retentionPercent(source, "d1_retention_rate", "day1_rate"),
    retentionD7: retentionPercent(source, "d7_retention_rate", "day7_rate"),
    retentionD30: retentionPercent(source, "d30_retention_rate", "day30_rate"),
    salary: moneyMajor(readOptionalNumber(source, "salary_usd_minor", "salaryUsdMinor")),
    salaryTransferCoin: readOptionalNumber(source, "salary_transfer_coin", "salaryTransferCoin"),
    superLuckyGiftFlowProfit: metricFlowRateValue(
      source,
      ["super_lucky_gift_turnover", "superLuckyGiftTurnover"],
      ["super_lucky_gift_profit_rate", "superLuckyGiftProfitRate"],
      ["super_lucky_gift_profit", "superLuckyGiftProfit"]
    ),
    thirdPartyRecharge: moneyMajor(readOptionalNumber(source, "mifapay_recharge_usd_minor", "mifapayRechargeUsdMinor", "mifa_pay_recharge_usd_minor", "mifaPayRechargeUsdMinor")),
    turnover: appTurnoverValue(source)
  };
}

function createRegionalRow(row) {
  return {
    ...row,
    agencyCount: null,
    bdCount: null,
    coinSellerCoins: readOptionalNumber(row.country, "coin_seller_stock_coin", "coinSellerStockCoin"),
    coinSellerCount: null,
    hostCount: null
  };
}

function createRetentionRow(row) {
  return {
    ...row,
    agencyActive: null,
    bdActive: null,
    coinSellerActive: null,
    ecosystemActive: null,
    hostActive: null
  };
}

function selectedSocialBiAppCodes(selectedValues, appOptions) {
  const options = appOptions?.length ? appOptions : socialBiAppOptions;
  const selected = selectedValues?.length ? selectedValues : [ALL_VALUE];
  const values = selected.includes(ALL_VALUE)
    ? options.map((option) => option.value)
    : selected;
  return [...new Set(values.filter((value) => value && value !== ALL_VALUE).map((value) => String(value).toLowerCase()))];
}

function dateRangeToCalendarRange(value) {
  if (value === "last7") {
    return lastDaysRangeValue(7);
  }
  if (value === "last30") {
    return lastDaysRangeValue(30);
  }
  if (value === "month") {
    return thisMonthRange(SOCIAL_BI_STAT_TZ);
  }
  const today = todayRange(SOCIAL_BI_STAT_TZ).start;
  const yesterday = shiftDateValue(today, -1);
  return { end: yesterday, endTime: "23:59:59", start: yesterday, startTime: "00:00:00" };
}

function lastDaysRangeValue(days) {
  const today = todayRange(SOCIAL_BI_STAT_TZ).start;
  return {
    end: today,
    endTime: "23:59:59",
    start: shiftDateValue(today, 1 - Number(days || 1)),
    startTime: "00:00:00"
  };
}

function shiftDateValue(value, days) {
  const [year, month, day] = String(value).split("-").map(Number);
  const date = new Date(Date.UTC(year || 1970, (month || 1) - 1, (day || 1) + days));
  return `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())}`;
}

function rangeLabel(range) {
  return range?.start === range?.end ? range.start : `${range.start} ~ ${range.end}`;
}

function appLabel(appCode, overview) {
  return overview?.app_name || overview?.appName || appCodeLabel(appCode);
}

function appCodeLabel(appCode) {
  const normalized = String(appCode || "").toLowerCase();
  return socialBiAppOptions.find((option) => option.value === normalized)?.label || normalized || "-";
}

function countryLabel(country) {
  return country?.country || country?.country_name || country?.countryName || country?.country_code || country?.countryCode || "未知地区";
}

function retentionPercent(source, topLevelKey, retentionKey) {
  const value = readOptionalNumber(source, topLevelKey, retentionKey) ?? readOptionalNumber(source?.retention, retentionKey, topLevelKey);
  return value === null ? null : value * 100;
}

function percentageValue(value) {
  return value === null || value === undefined ? null : Number(value) * 100;
}

function metricFlowRateValue(source, valueKeys, rateKeys, profitKeys) {
  const value = readFirstOptionalNumber(source, valueKeys);
  const profit = readFirstOptionalNumber(source, profitKeys);
  const explicitRate = readFirstOptionalNumber(source, rateKeys);
  const rate = explicitRate === null && value && profit !== null ? profit / value : explicitRate;
  if (value === null && rate === null && profit === null) {
    return null;
  }
  return { profit, rate: percentageValue(rate), value };
}

function consumeOutputValue(source) {
  const consumed = readOptionalNumber(source, "consumed_coin", "consumedCoin");
  const output = readOptionalNumber(source, "output_coin", "outputCoin");
  const explicitRate = readOptionalNumber(source, "consume_output_ratio", "consumeOutputRatio");
  const rate = explicitRate === null && consumed && output !== null ? output / consumed : explicitRate;
  const delta = readOptionalNumber(source, "consume_output_delta", "consumeOutputDelta");
  if (consumed === null && output === null && rate === null && delta === null) {
    return null;
  }
  return { consumed, delta, output, rate: percentageValue(rate) };
}

function appTurnoverValue(source) {
  return sumOptionalNumbers(
    readOptionalNumber(source, "gift_coin_spent", "giftCoinSpent"),
    readOptionalNumber(source, "lucky_gift_turnover", "luckyGiftTurnover", "room_lucky_gift_turnover", "roomLuckyGiftTurnover"),
    readOptionalNumber(source, "super_lucky_gift_turnover", "superLuckyGiftTurnover"),
    readOptionalNumber(source, "game_turnover", "gameTurnover")
  );
}

function readFirstOptionalNumber(source, keys) {
  return readOptionalNumber(source, ...(keys || []));
}

function readOptionalNumber(source, ...keys) {
  for (const key of keys) {
    if (source?.[key] !== undefined && source[key] !== null && source[key] !== "") {
      const value = Number(source[key]);
      return Number.isFinite(value) ? value : null;
    }
  }
  return null;
}

function moneyMajor(value) {
  return value === null || value === undefined ? null : value / 100;
}

function sumOptionalNumbers(...values) {
  const validValues = values.filter((value) => value !== null && value !== undefined && Number.isFinite(Number(value)));
  if (!validValues.length) {
    return null;
  }
  return validValues.reduce((sum, value) => sum + Number(value), 0);
}

function pad2(value) {
  return String(value).padStart(2, "0");
}

function createInitialFilters() {
  return {
    apps: [ALL_VALUE],
    dateRange: "yesterday",
    departments: [ALL_VALUE],
    operators: [ALL_VALUE],
    regions: [ALL_VALUE]
  };
}

function createInitialSortState() {
  return Object.fromEntries(socialBiSections.map((section) => [section.key, defaultSortState(section)]));
}

function defaultSortState(section) {
  const revenueColumn = section.columns.find((column) => column.key === "recharge");
  return { direction: "desc", key: revenueColumn?.key || section.columns[0].key };
}

function createRuntimeFilterDefinitions(filterOptions) {
  return {
    ...socialBiFilterDefinitions,
    apps: { ...socialBiFilterDefinitions.apps, options: filterOptions.apps },
    operators: { ...socialBiFilterDefinitions.operators, options: filterOptions.operators }
  };
}

function applySectionFilters(section, filters, filterDefinitions) {
  // 前端布局阶段只做可验证的本地过滤：按当前行里已有的 App、部门、人员、地区字段筛选，不推断后端不存在的维度。
  return section.rows.filter((row) => {
    if (!matchesFlatSelection(row.appName, filters.apps, filterDefinitions.apps.options)) {
      return false;
    }
    if (section.filters.includes("departments") && !matchesFlatSelection(row.department, filters.departments, socialBiDepartmentOptions)) {
      return false;
    }
    if (section.filters.includes("operators") && row.operator && !matchesFlatSelection(row.operator, filters.operators, filterDefinitions.operators.options)) {
      return false;
    }
    if (section.filters.includes("regions") && !matchesRegionSelection(row.region, filters.regions)) {
      return false;
    }
    return true;
  });
}

function matchesFlatSelection(rowLabel, values, options) {
  if (!values?.length || values.includes(ALL_VALUE) || !rowLabel) {
    return true;
  }
  const selectedLabels = options.filter((option) => values.includes(option.value)).map((option) => normalizeLabel(option.label));
  const normalizedRowLabel = normalizeLabel(rowLabel);
  return selectedLabels.some((label) => normalizedRowLabel.includes(label) || label.includes(normalizedRowLabel));
}

function matchesRegionSelection(rowLabel, values) {
  if (!values?.length || values.includes(ALL_VALUE) || !rowLabel) {
    return true;
  }
  const selectedLabels = selectedRegionLabels(values).map(normalizeLabel);
  const normalizedRowLabel = normalizeLabel(rowLabel);
  return selectedLabels.some((label) => normalizedRowLabel.includes(label) || label.includes(normalizedRowLabel));
}

function selectedRegionLabels(values) {
  return socialBiRegionOptions.flatMap((group) => {
    const labels = [];
    if (values.includes(group.value)) {
      labels.push(group.label, ...group.children.map((child) => child.label));
    }
    group.children.forEach((child) => {
      if (values.includes(child.value)) {
        labels.push(child.label);
      }
    });
    return labels;
  });
}

function createSummaryCards(section, rows, totalRow) {
  return section.cards.map((card) => ({
    ...card,
    delta: null,
    value: totalRow[card.key] ?? aggregateLooseValue(rows, card)
  }));
}

function createTotalRow(section, rows) {
  const totalRow = { id: `${section.key}-total`, isTotal: true };
  section.columns.forEach((column, index) => {
    if (index === 0) {
      totalRow[column.key] = "汇总 Total";
      return;
    }
    if (column.aggregate === "text") {
      totalRow[column.key] = "全部";
      return;
    }
    totalRow[column.key] = aggregateColumn(rows, column);
  });
  return totalRow;
}

function aggregateLooseValue(rows, card) {
  const values = rows.map((row) => row[card.key]).filter((value) => value !== null && value !== undefined && Number.isFinite(Number(value)));
  if (!values.length) {
    return null;
  }
  if (card.type === "percent") {
    return values.reduce((sum, value) => sum + Number(value), 0) / values.length;
  }
  return values.reduce((sum, value) => sum + Number(value), 0);
}

function aggregateColumn(rows, column) {
  if (column.aggregate === "progress") {
    const actual = rows.reduce((sum, row) => sum + Number(row[column.key]?.actual || 0), 0);
    const target = rows.reduce((sum, row) => sum + Number(row[column.key]?.target || 0), 0);
    return target ? { actual, target } : { actual, noGoal: true, target: 0 };
  }
  if (column.aggregate === "ratio") {
    return rows.reduce((total, row) => ({
      active: total.active + Number(row[column.key]?.active || 0),
      total: total.total + Number(row[column.key]?.total || 0)
    }), { active: 0, total: 0 });
  }
  if (column.aggregate === "average") {
    const values = rows.map((row) => Number(row[column.key])).filter(Number.isFinite);
    return values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
  }
  if (column.aggregate === "flowRate") {
    return aggregateFlowRate(rows, column.key);
  }
  if (column.aggregate === "consumeOutput") {
    return aggregateConsumeOutput(rows, column.key);
  }
  if (column.aggregate === "durationAverage") {
    const values = rows.map((row) => Number(row[column.key])).filter(Number.isFinite);
    return values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
  }
  if (column.aggregate === "duration") {
    return sumOptionalNumbers(...rows.map((row) => row[column.key]));
  }
  return sumOptionalNumbers(...rows.map((row) => row[column.key]));
}

function aggregateFlowRate(rows, key) {
  const values = rows.map((row) => row[key]).filter(Boolean);
  if (!values.length) {
    return null;
  }
  const totalValue = sumOptionalNumbers(...values.map((value) => value.value));
  const totalProfit = sumOptionalNumbers(...values.map((value) => value.profit));
  const weightedBase = values
    .filter((value) => Number.isFinite(Number(value.value)) && Number.isFinite(Number(value.rate)) && Number(value.value) > 0)
    .reduce((total, value) => ({
      rateValue: total.rateValue + Number(value.value) * Number(value.rate),
      value: total.value + Number(value.value)
    }), { rateValue: 0, value: 0 });
  const rateValues = values.map((value) => Number(value.rate)).filter(Number.isFinite);
  const rate = weightedBase.value
    ? weightedBase.rateValue / weightedBase.value
    : (rateValues.length ? rateValues.reduce((sum, value) => sum + value, 0) / rateValues.length : null);
  if (totalValue === null && totalProfit === null && rate === null) {
    return null;
  }
  return { profit: totalProfit, rate, value: totalValue };
}

function aggregateConsumeOutput(rows, key) {
  const values = rows.map((row) => row[key]).filter(Boolean);
  if (!values.length) {
    return null;
  }
  const consumed = sumOptionalNumbers(...values.map((value) => value.consumed));
  const output = sumOptionalNumbers(...values.map((value) => value.output));
  const delta = sumOptionalNumbers(...values.map((value) => value.delta));
  const rateValues = values.map((value) => Number(value.rate)).filter(Number.isFinite);
  const rate = consumed && output !== null
    ? (Number(output) / Number(consumed)) * 100
    : (rateValues.length ? rateValues.reduce((sum, value) => sum + value, 0) / rateValues.length : null);
  if (consumed === null && output === null && delta === null && rate === null) {
    return null;
  }
  return { consumed, delta, output, rate };
}

function sortRows(rows, columns, sortState) {
  const column = columns.find((item) => item.key === sortState.key) || columns[0];
  const direction = sortState.direction === "asc" ? 1 : -1;
  return [...rows].sort((left, right) => {
    const leftValue = sortComparableValue(left[column.key], column);
    const rightValue = sortComparableValue(right[column.key], column);
    if (typeof leftValue === "string" || typeof rightValue === "string") {
      return collator.compare(String(leftValue), String(rightValue)) * direction;
    }
    return (Number(leftValue || 0) - Number(rightValue || 0)) * direction;
  });
}

function sortComparableValue(value, column) {
  if (column.type === "progress") {
    return value?.target ? Number(value.actual || 0) / Number(value.target) : -1;
  }
  if (column.type === "ratio") {
    return value?.active || 0;
  }
  if (column.type === "flowRate") {
    return value?.value ?? value?.rate ?? -1;
  }
  if (column.type === "consumeOutput") {
    return value?.rate ?? -1;
  }
  return value;
}

function selectedOptionsLabel(options, values) {
  if (!values?.length || values.includes(ALL_VALUE)) {
    return options.find((option) => option.value === ALL_VALUE)?.label || "全部";
  }
  const selected = options.filter((option) => values.includes(option.value));
  if (selected.length <= 2) {
    return selected.map((option) => option.label).join("、");
  }
  return `${selected[0].label} +${selected.length - 1}`;
}

function sameOptionValues(left = [], right = []) {
  if (left.length !== right.length) {
    return false;
  }
  return left.every((item, index) => item.value === right[index]?.value && item.label === right[index]?.label);
}

function selectedCascadeLabel(options, values) {
  if (!values?.length || values.includes(ALL_VALUE)) {
    return "全部地区";
  }
  const flatOptions = options.flatMap((group) => [group, ...group.children]);
  const selected = flatOptions.filter((option) => values.includes(option.value));
  if (selected.length <= 2) {
    return selected.map((option) => option.label).join("、");
  }
  return `${selected[0].label} +${selected.length - 1}`;
}

function toggleFlatSelection(currentValues, nextValue, options) {
  if (nextValue === ALL_VALUE) {
    return [ALL_VALUE];
  }
  const withoutAll = currentValues.filter((value) => value !== ALL_VALUE);
  const next = withoutAll.includes(nextValue) ? withoutAll.filter((value) => value !== nextValue) : [...withoutAll, nextValue];
  const availableValues = options.filter((option) => option.value !== ALL_VALUE).map((option) => option.value);
  if (!next.length || availableValues.every((value) => next.includes(value))) {
    return [ALL_VALUE];
  }
  return next;
}

function toggleCascadeGroup(currentValues, group) {
  const groupValues = [group.value, ...group.children.map((child) => child.value)];
  const withoutAll = currentValues.filter((value) => value !== ALL_VALUE);
  const hasGroup = groupValues.every((value) => withoutAll.includes(value));
  const next = hasGroup ? withoutAll.filter((value) => !groupValues.includes(value)) : [...new Set([...withoutAll, ...groupValues])];
  return next.length ? next : [ALL_VALUE];
}

function toggleCascadeChild(currentValues, childValue) {
  const withoutAll = currentValues.filter((value) => value !== ALL_VALUE);
  const next = withoutAll.includes(childValue) ? withoutAll.filter((value) => value !== childValue) : [...withoutAll, childValue];
  return next.length ? next : [ALL_VALUE];
}

function normalizeLabel(value) {
  return String(value || "").trim().toLowerCase();
}

function formatTypedValue(value, type) {
  if (value === null || value === undefined || value === "" || (typeof value === "number" && !Number.isFinite(value))) {
    return "--";
  }
  if (type === "money") {
    return formatMoney(value);
  }
  if (type === "number") {
    return formatNumber(value);
  }
  if (type === "percent") {
    return `${formatDecimal(value)}%`;
  }
  return String(value ?? "-");
}

function formatPercentValue(value) {
  if (value === null || value === undefined || value === "" || !Number.isFinite(Number(value))) {
    return "--";
  }
  return `${formatDecimal(value)}%`;
}

function formatDuration(value) {
  const numeric = Number(value);
  if (!Number.isFinite(numeric)) {
    return "--";
  }
  const totalSeconds = Math.max(0, Math.round(numeric / 1000));
  if (totalSeconds < 60) {
    return `${totalSeconds}秒`;
  }
  const totalMinutes = Math.round(totalSeconds / 60);
  if (totalMinutes < 60) {
    return `${totalMinutes}分`;
  }
  const hours = Math.floor(totalMinutes / 60);
  const minutes = totalMinutes % 60;
  return minutes ? `${hours}小时${minutes}分` : `${hours}小时`;
}

function formatMoney(value) {
  const numeric = Number(value);
  if (!Number.isFinite(numeric)) {
    return "--";
  }
  const maximumFractionDigits = Math.abs(numeric) < 1000 && !Number.isInteger(numeric) ? 1 : 0;
  return `$${numeric.toLocaleString("en-US", { maximumFractionDigits, minimumFractionDigits: maximumFractionDigits })}`;
}

function formatNumber(value) {
  const numeric = Number(value);
  if (!Number.isFinite(numeric)) {
    return "--";
  }
  return numeric.toLocaleString("en-US", { maximumFractionDigits: 0 });
}

function formatDecimal(value) {
  const numeric = Number(value);
  if (!Number.isFinite(numeric)) {
    return "--";
  }
  return numeric.toLocaleString("en-US", { maximumFractionDigits: 1, minimumFractionDigits: 1 });
}
