"use client";

import Link from "next/link";
import type { ReactElement, ReactNode } from "react";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import {
  AlertTriangle,
  ArrowRightIcon,
  CheckCircle2,
  ChevronRightIcon,
  type LucideIcon,
} from "lucide-react";
import {
  Bar,
  BarChart,
  Cell,
  Label,
  Pie,
  PieChart,
  PolarAngleAxis,
  RadialBar,
  RadialBarChart,
  XAxis,
  YAxis,
} from "recharts";

import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { AdminNoResourceState } from "@/components/admin/admin-no-resource-state";
import { AdminMoneyDisplay } from "@/components/admin/admin-money-display";
import { Skeleton } from "@/components/ui/skeleton";
import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart";
import {
  coerceAdminMinor,
  formatAdminMinorDecimal,
  getAdminCurrencyDecimalPlaces,
} from "@/lib/money";
import { cn } from "@/lib/utils";
import { SignedMoney, signedMoneyClass } from "@/lib/admin-signed-money";
import { adminMoneyDisplayClass } from "@/lib/admin-money-display";
import {
  buildBatchProgressConfig,
  buildFinanceStructureConfig,
  buildPayoutPieConfig,
  buildSoldOutPieConfig,
  buildSettlementBarConfig,
  buildUsageBarConfig,
  DASHBOARD_CHART_COLORS,
} from "@/modules/dashboard/dashboard-chart-config";
import { DashboardChartEmpty } from "@/modules/dashboard/dashboard-chart-empty";
import { formatDashboardSignedMoneyMinor } from "@/modules/dashboard/use-dashboard-analytics";
import type { AdminDrawFinanceSummaryData } from "@/types/api/admin-draw-finance";
import type { AdminDashboardLifetimeFinance } from "@/types/api/admin-dashboard";
import type { AdminRiskPoolRow } from "@/types/api/admin-risk";
import type {
  AdminDashboardDrawPanel,
  AdminDashboardResultBatchQueue,
  AdminDashboardSoldOutBuckets,
} from "@/types/api/admin-dashboard";

export type SoldOutBuckets = AdminDashboardSoldOutBuckets;

type MoneyFormatter = (minor: number, currency: string | null) => string;

type DashboardFinanceMetricCell = {
  key: string;
  label: string;
  amount: number;
  emphasize: boolean;
};

/** KPI 卡片底部三列：仅数字（币种见卡片主值），过长时缩小字号 + 换行 */
function formatDashboardMetricAmount(
  minor: number,
  currencyCode: string | null,
  formatMoney: MoneyFormatter,
): { display: string; title: string } {
  const safeMinor = coerceAdminMinor(minor);
  const code = (currencyCode ?? "NPR").toUpperCase();
  const decimals = getAdminCurrencyDecimalPlaces(code);
  return {
    display: formatAdminMinorDecimal(safeMinor, code, decimals),
    title: formatMoney(safeMinor, currencyCode),
  };
}

function DashboardFinanceMetricCells({
  cells,
  currency,
  formatMoney,
}: {
  cells: readonly DashboardFinanceMetricCell[];
  currency: string | null;
  formatMoney: MoneyFormatter;
}): ReactElement {
  return (
    <div className="grid grid-cols-3 gap-1.5">
      {cells.map((cell) => {
        const { display, title } = formatDashboardMetricAmount(
          cell.amount,
          currency,
          formatMoney,
        );
        return (
          <div
            key={cell.key}
            className={cn(
              "min-w-0 rounded-lg px-1 py-2 ring-1",
              cell.emphasize
                ? "bg-primary/6 ring-primary/15"
                : "bg-muted/30 ring-border/50",
            )}
          >
            <p className="line-clamp-2 text-center text-[10px] leading-tight text-muted-foreground">
              {cell.label}
            </p>
            <p
              className={cn(
                "mt-1 text-center font-bold tabular-nums leading-tight break-all",
                adminMoneyDisplayClass(display, { size: "sm", emphasize: true }),
                cell.emphasize ? "text-foreground" : "text-muted-foreground",
              )}
              title={title}
            >
              {display}
            </p>
          </div>
        );
      })}
    </div>
  );
}

function usageBarFill(pct: number): string {
  if (pct >= 95) {
    return DASHBOARD_CHART_COLORS.rose;
  }
  if (pct >= 80) {
    return DASHBOARD_CHART_COLORS.warning;
  }
  return DASHBOARD_CHART_COLORS.primary;
}

function capUsageFill(pct: number): string {
  if (pct >= 90) {
    return "var(--destructive)";
  }
  if (pct >= 70) {
    return DASHBOARD_CHART_COLORS.warning;
  }
  return DASHBOARD_CHART_COLORS.primary;
}

function settlementBarColor(status: string): string {
  switch (status) {
    case "pending_review":
      return DASHBOARD_CHART_COLORS.warning;
    case "approved":
      return "oklch(0.62 0.14 230)";
    case "paid":
    case "completed":
      return DASHBOARD_CHART_COLORS.success;
    case "running":
      return DASHBOARD_CHART_COLORS.primary;
    case "rejected":
    case "failed":
      return DASHBOARD_CHART_COLORS.rose;
    default:
      return DASHBOARD_CHART_COLORS.violet;
  }
}

type DashboardKpiAccent = "primary" | "destructive" | "muted";

function kpiAccentClass(accent: DashboardKpiAccent): string {
  switch (accent) {
    case "destructive":
      return "bg-destructive/10 text-destructive";
    case "muted":
      return "bg-muted text-muted-foreground";
    default:
      return "bg-primary/10 text-primary";
  }
}

/** 站点/代理卡片内 label | amount 行，金额过长时可换行 */
export function DashboardStatRow({
  label,
  value,
  valueClassName,
}: {
  label: string;
  value: string;
  valueClassName?: string;
}): ReactElement {
  return (
    <div className="flex items-start justify-between gap-3 text-sm">
      <span className="shrink-0 text-muted-foreground">{label}</span>
      <span
        className={cn(
          "min-w-0 break-all text-right font-semibold tabular-nums leading-tight",
          valueClassName ?? "text-foreground",
        )}
      >
        {value}
      </span>
    </div>
  );
}

/** 盈亏行：正绿、负红、零灰（与 {@link signedMoneyClass} 一致） */
export function DashboardSignedStatRow({
  label,
  amountMinor,
  currencyCode,
}: {
  label: string;
  amountMinor: number;
  currencyCode: string | null;
}): ReactElement {
  return (
    <div className="flex items-start justify-between gap-3 text-sm">
      <span className="shrink-0 text-muted-foreground">{label}</span>
      <SignedMoney
        amount={amountMinor}
        emphasize
        className="min-w-0 break-all text-right leading-tight"
      >
        {formatDashboardSignedMoneyMinor(amountMinor, currencyCode)}
      </SignedMoney>
    </div>
  );
}

/** 规模/授信等栅格指标，金额可换行 */
export function DashboardScopeMetric({
  label,
  value,
}: {
  label: string;
  value: string;
}): ReactElement {
  return (
    <div className="rounded-lg border bg-muted/30 px-3 py-2.5">
      <p className="text-xs text-muted-foreground">{label}</p>
      <AdminMoneyDisplay
        as="p"
        value={value}
        size="md"
        className="mt-1 text-foreground"
      >
        {value}
      </AdminMoneyDisplay>
    </div>
  );
}

/** 财务概览区紧凑 KPI，避免 StatCard 在窄栅格内撑破布局 */
export function DashboardKpiCard({
  label,
  value,
  hint,
  icon,
  accent = "primary",
  valueClassName,
  signedAmountMinor,
  currencyCode,
  sparklineValues,
  deltaLabel,
  href,
}: {
  label: string;
  value?: ReactNode;
  hint?: ReactNode;
  icon: ReactNode;
  accent?: DashboardKpiAccent;
  /** 覆盖主数值颜色（如盈亏红绿） */
  valueClassName?: string;
  /** 盈亏类 KPI：自动带 +/- 与红绿 */
  signedAmountMinor?: number;
  currencyCode?: string | null;
  sparklineValues?: number[];
  deltaLabel?: ReactNode;
  /** 整张卡片可点击跳转 */
  href?: string;
}): ReactElement {
  const resolvedValue =
    typeof signedAmountMinor === "number" && currencyCode !== undefined
      ? formatDashboardSignedMoneyMinor(signedAmountMinor, currencyCode)
      : value;
  const resolvedValueClassName =
    typeof signedAmountMinor === "number"
      ? signedMoneyClass(signedAmountMinor, true)
      : valueClassName;
  const valueTitle =
    typeof resolvedValue === "string" || typeof resolvedValue === "number"
      ? String(resolvedValue)
      : undefined;

  const card = (
    <div
      className={cn(
        "flex h-full min-w-0 flex-col rounded-xl border border-border/60 bg-card p-4",
        href && "transition-colors group-hover/kpi:border-primary/30 group-hover/kpi:shadow-sm",
      )}
    >
      <div className="flex items-start justify-between gap-2">
        <p className="min-w-0 flex-1 text-xs font-medium leading-snug text-muted-foreground">{label}</p>
        <div className="flex shrink-0 items-center gap-1">
          <div
            className={cn(
              "flex size-9 items-center justify-center rounded-lg [&_svg]:size-4",
              kpiAccentClass(accent),
            )}
          >
            {icon}
          </div>
          {href ? (
            <ChevronRightIcon
              className="size-4 text-muted-foreground/50 transition group-hover/kpi:text-primary"
              aria-hidden
            />
          ) : null}
        </div>
      </div>
      <p
        title={valueTitle}
        className={cn(
          "mt-2 text-foreground",
          resolvedValueClassName,
          typeof resolvedValue === "string"
            ? adminMoneyDisplayClass(resolvedValue, { size: "lg", emphasize: true })
            : "min-w-0 break-all text-base font-bold tabular-nums leading-tight tracking-tight sm:text-lg",
        )}
      >
        {resolvedValue}
      </p>
      {deltaLabel ? <div className="mt-1 text-xs font-medium tabular-nums">{deltaLabel}</div> : null}
      {sparklineValues && sparklineValues.length >= 2 ? (
        <div className="mt-3 flex justify-end">
          <MiniSparkline
            values={sparklineValues}
            strokeClass={
              accent === "destructive"
                ? "stroke-destructive"
                : accent === "muted"
                  ? "stroke-muted-foreground"
                  : "stroke-primary"
            }
          />
        </div>
      ) : null}
      {hint ? (
        <p
          className={cn(
            "mt-2 text-[11px] leading-snug",
            href ? "text-primary/80 group-hover/kpi:underline" : "text-muted-foreground",
          )}
        >
          {hint}
        </p>
      ) : null}
    </div>
  );

  if (!href) {
    return card;
  }

  return (
    <Link
      href={href}
      className="group/kpi block h-full min-h-0 rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring"
    >
      {card}
    </Link>
  );
}

export type DashboardQuickLink = {
  href: string;
  label: string;
  icon?: LucideIcon;
};

/** 仪表盘快捷入口：按钮或图标卡片两种布局 */
export function DashboardQuickLinksCard({
  title,
  links,
  variant = "buttons",
}: {
  title: string;
  links: readonly DashboardQuickLink[];
  variant?: "buttons" | "tiles";
}): ReactElement {
  const { t } = useTranslation("dashboard");

  if (links.length === 0) {
    return <></>;
  }

  return (
    <Card>
      <CardHeader className="pb-2">
        <CardTitle className="text-sm font-semibold">{title}</CardTitle>
      </CardHeader>
      <CardContent
        className={cn(
          variant === "buttons" ? "flex flex-wrap gap-2" : "grid gap-3 sm:grid-cols-3",
        )}
      >
        {links.map((link) => {
          if (variant === "buttons") {
            return (
              <Link
                key={link.href}
                href={link.href}
                className={cn(
                  "inline-flex items-center gap-1.5 rounded-md border border-input bg-background px-3 py-1.5 text-sm font-medium shadow-xs transition-colors hover:bg-accent hover:text-accent-foreground",
                )}
              >
                {link.label}
              </Link>
            );
          }

          const Icon = link.icon;
          return (
            <Link
              key={link.href}
              href={link.href}
              className="flex flex-col gap-2 rounded-xl border bg-muted/20 px-4 py-4 transition-colors hover:bg-muted/40"
            >
              {Icon ? <Icon className="size-5 text-primary" aria-hidden /> : null}
              <span className="text-sm font-medium">{link.label}</span>
              <span className="text-xs text-muted-foreground">{t("cs.openModule")}</span>
            </Link>
          );
        })}
      </CardContent>
    </Card>
  );
}

/** 轻量报表入口：替代站点/代理仪表盘内嵌完整 analytics 面板 */
export function DashboardReportsTeaser({
  title,
  description,
  href = "/admin/reports",
}: {
  title: string;
  description: string;
  href?: string;
}): ReactElement {
  const { t } = useTranslation("dashboard");

  return (
    <Card>
      <CardHeader className="flex flex-row flex-wrap items-center justify-between gap-2 space-y-0 pb-2">
        <CardTitle className="text-sm font-semibold">{title}</CardTitle>
        <Link
          href={href}
          className="inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline"
        >
          {t("viewReports")}
          <ArrowRightIcon className="size-3.5" aria-hidden />
        </Link>
      </CardHeader>
      <CardContent>
        <p className="text-xs leading-relaxed text-muted-foreground">{description}</p>
      </CardContent>
    </Card>
  );
}

function MiniSparkline({
  values,
  strokeClass,
}: {
  values: number[];
  strokeClass: string;
}): ReactElement | null {
  if (values.length < 2) {
    return null;
  }
  const width = 88;
  const height = 32;
  const max = Math.max(...values, 1);
  const min = Math.min(...values, 0);
  const range = Math.max(max - min, 1);
  const points = values
    .map((v, i) => {
      const x = (i / (values.length - 1)) * width;
      const y = height - ((v - min) / range) * (height - 4) - 2;
      return `${x},${y}`;
    })
    .join(" ");

  return (
    <svg
      viewBox={`0 0 ${width} ${height}`}
      className="h-8 w-[5.5rem] shrink-0"
      aria-hidden
    >
      <polyline
        fill="none"
        strokeWidth="2"
        strokeLinecap="round"
        strokeLinejoin="round"
        points={points}
        className={strokeClass}
      />
    </svg>
  );
}

export function StatCard({
  label,
  value,
  hint,
  icon,
  accent = "primary",
  href,
  sparklineValues,
  deltaLabel,
}: {
  label: string;
  value: ReactNode;
  hint?: ReactNode;
  icon: ReactNode;
  accent?: "primary" | "destructive" | "muted";
  /** 整张卡片可点击跳转 */
  href?: string;
  sparklineValues?: number[];
  deltaLabel?: ReactNode;
}): ReactElement {
  const accentClass =
    accent === "destructive"
      ? "bg-destructive text-destructive-foreground"
      : accent === "muted"
        ? "bg-muted text-foreground"
        : "bg-primary text-primary-foreground";

  const card = (
    <Card
      className={cn(
        "flex h-full flex-col border-border/80 py-0 shadow-sm transition-colors",
        href &&
          "group-hover/stat border-primary/30 bg-muted/15 shadow-md",
      )}
    >
      <CardContent className="flex flex-1 items-start gap-4 p-5">
        <div
          className={cn(
            "flex size-11 shrink-0 items-center justify-center rounded-lg shadow-sm",
            accentClass,
          )}
        >
          {icon}
        </div>
        <div className="flex min-h-0 min-w-0 flex-1 flex-col">
          <p className="text-sm font-medium text-muted-foreground">{label}</p>
          {typeof value === "string" || typeof value === "number" ? (
            <AdminMoneyDisplay
              as="p"
              value={String(value)}
              size="xl"
              emphasize
              className="mt-1 text-foreground"
            >
              {value}
            </AdminMoneyDisplay>
          ) : (
            <p className="mt-1 text-2xl font-bold tabular-nums tracking-tight text-foreground">
              {value}
            </p>
          )}
          {deltaLabel ? (
            <p className="mt-1 text-xs font-medium tabular-nums">{deltaLabel}</p>
          ) : null}
          <div
            className={cn(
              "mt-auto min-h-10 pt-2 text-xs leading-snug",
              hint
                ? href
                  ? "font-medium text-primary group-hover/stat:underline"
                  : "text-muted-foreground"
                : "text-transparent",
            )}
          >
            {hint ?? "\u00a0"}
          </div>
        </div>
        {sparklineValues ? (
          <MiniSparkline
            values={sparklineValues}
            strokeClass={
              accent === "destructive"
                ? "stroke-destructive"
                : accent === "muted"
                  ? "stroke-muted-foreground"
                  : "stroke-primary"
            }
          />
        ) : href ? (
          <ChevronRightIcon
            className="mt-0.5 size-4 shrink-0 text-muted-foreground/60 transition group-hover/stat:text-primary"
            aria-hidden
          />
        ) : null}
      </CardContent>
    </Card>
  );

  const shellClass = "flex h-full min-h-0 rounded-2xl";

  if (!href) {
    return <div className={shellClass}>{card}</div>;
  }

  return (
    <Link
      href={href}
      className={cn(
        shellClass,
        "group/stat outline-none focus-visible:ring-2 focus-visible:ring-ring",
      )}
    >
      {card}
    </Link>
  );
}

type DashboardPanelAccent = "primary" | "destructive" | "warning" | "muted";

function panelAccentClass(accent: DashboardPanelAccent): string {
  switch (accent) {
    case "destructive":
      return "bg-destructive/10 text-destructive";
    case "warning":
      return "bg-amber-500/15 text-amber-700 dark:text-amber-400";
    case "muted":
      return "bg-muted text-muted-foreground";
    default:
      return "bg-primary/10 text-primary";
  }
}

/** 仪表盘 KPI：整卡可点，主指标 + 可选底部可视化 */
export function DashboardPanelCard({
  href,
  icon,
  title,
  value,
  subtitle,
  actionLabel,
  accent = "primary",
  loading = false,
  highlight = false,
  children,
}: {
  href: string;
  title: string;
  value: ReactNode;
  subtitle?: ReactNode;
  actionLabel: string;
  icon: ReactNode;
  accent?: DashboardPanelAccent;
  loading?: boolean;
  /** 有异常/待办时强调边框 */
  highlight?: boolean;
  children?: ReactNode;
}): ReactElement {
  const hasFooter = children != null;

  return (
    <Link
      href={href}
      aria-label={`${title}，${actionLabel}`}
      className="group/panel flex h-full min-w-0 w-full rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring"
    >
      <Card
        className={cn(
          "admin-list-card flex h-full min-w-0 w-full flex-col py-0 transition-all duration-200",
          "hover:border-primary/30 hover:shadow-md",
          highlight && "border-amber-400/50 ring-1 ring-amber-400/25 dark:border-amber-500/40",
        )}
      >
        <div className="flex flex-1 flex-col p-4">
          <div className="flex items-start justify-between gap-3">
            <div
              className={cn(
                "flex size-10 shrink-0 items-center justify-center rounded-xl [&_svg]:size-[1.125rem]",
                panelAccentClass(accent),
              )}
            >
              {icon}
            </div>
            <span
              className="flex size-8 shrink-0 items-center justify-center rounded-full text-muted-foreground/70 transition-colors group-hover/panel:bg-primary/10 group-hover/panel:text-primary"
              title={actionLabel}
            >
              <ArrowRightIcon
                className="size-4 transition-transform group-hover/panel:translate-x-0.5"
                aria-hidden
              />
            </span>
          </div>

          <div className="mt-3 min-w-0">
            <p className="text-xs font-medium text-muted-foreground">{title}</p>
            {loading ? (
              <Skeleton className="mt-2 h-8 w-24 rounded-md" />
            ) : typeof value === "string" || typeof value === "number" ? (
              <AdminMoneyDisplay
                as="p"
                value={String(value)}
                size="xl"
                emphasize
                className="mt-1 text-foreground"
              >
                {value}
              </AdminMoneyDisplay>
            ) : (
              <p className="mt-1 text-2xl font-bold tabular-nums leading-none tracking-tight text-foreground">
                {value}
              </p>
            )}
            {subtitle && !loading ? (
              <div className="mt-2 text-xs leading-snug text-muted-foreground">{subtitle}</div>
            ) : null}
          </div>

          {hasFooter ? (
            <div
              className={cn(
                "mt-4 flex min-h-[3.25rem] items-center justify-center",
                loading && "items-stretch",
              )}
            >
              {loading ? (
                <Skeleton className="h-[3.25rem] w-full rounded-lg" />
              ) : (
                <div className="w-full border-t border-dashed border-border/60 pt-3">
                  {children}
                </div>
              )}
            </div>
          ) : null}
        </div>
      </Card>
    </Link>
  );
}

/** 异常转账 KPI 底部：待办提示或正常态 */
export function AbnormalTransferPanelFooter({
  total,
  walletPermission = true,
}: {
  total: number | null;
  walletPermission?: boolean;
}): ReactElement {
  const { t } = useTranslation("dashboard");

  if (!walletPermission) {
    return (
      <p className="rounded-lg bg-muted/40 px-3 py-2.5 text-center text-[11px] leading-snug text-muted-foreground ring-1 ring-border/50">
        {t("warnings.walletPermission")}
      </p>
    );
  }

  if (total == null) {
    return (
      <AdminNoResourceState className="rounded-lg bg-muted/40 px-3 py-2.5 text-center text-[11px] text-muted-foreground ring-1 ring-border/50" />
    );
  }

  if (total > 0) {
    return (
      <div className="flex items-start gap-2.5 rounded-lg bg-amber-500/10 px-3 py-2.5 ring-1 ring-amber-500/20">
        <AlertTriangle className="mt-0.5 size-4 shrink-0 text-amber-600 dark:text-amber-400" aria-hidden />
        <div className="min-w-0 text-left">
          <p className="text-xs font-semibold text-amber-900 dark:text-amber-200">
            {t("abnormalTransferPending", { count: total })}
          </p>
          <p className="mt-1 text-[11px] leading-snug text-amber-800/90 dark:text-amber-300/90">
            {t("abnormalTransferAction")}
          </p>
        </div>
      </div>
    );
  }

  return (
    <div className="flex items-center gap-2 rounded-lg bg-emerald-500/8 px-3 py-2.5 ring-1 ring-emerald-500/15">
      <CheckCircle2 className="size-4 shrink-0 text-emerald-600 dark:text-emerald-400" aria-hidden />
      <p className="text-xs font-medium text-emerald-800 dark:text-emerald-300">
        {t("abnormalTransferAllClear")}
      </p>
    </div>
  );
}

/** 派彩 KPI 底部：投注/中奖/奖池拆分，有派彩时再附饼图 */
export function PayoutPanelSnapshot({
  finance,
  formatMoney,
}: {
  finance: AdminDrawFinanceSummaryData;
  formatMoney: MoneyFormatter;
}): ReactElement {
  const { t } = useTranslation("dashboard");
  const currency = finance.currency_code;
  const bet = coerceAdminMinor(finance.total_bet_minor);
  const win = coerceAdminMinor(finance.total_win_payout_minor);
  const jackpot = coerceAdminMinor(finance.total_jackpot_win_minor);
  const payout = coerceAdminMinor(finance.total_payout_minor);
  const hasPayout = payout > 0 || win + jackpot > 0;

  if (bet <= 0 && !hasPayout) {
    return <DashboardChartEmpty message={t("noFinanceActivity")} compact />;
  }

  const cells = [
    { key: "bet", label: t("currentDrawBetTotal"), amount: bet, emphasize: bet > 0 },
    { key: "win", label: t("winPayout"), amount: win, emphasize: win > 0 },
    { key: "jackpot", label: t("jackpotPayout"), amount: jackpot, emphasize: jackpot > 0 },
  ] as const;

  return (
    <div className="space-y-3">
      <DashboardFinanceMetricCells cells={cells} currency={currency} formatMoney={formatMoney} />
      {hasPayout ? (
        <PayoutCompositionChart finance={finance} formatMoney={formatMoney} compact />
      ) : (
        <p className="rounded-lg bg-muted/25 px-2 py-2 text-center text-[11px] text-muted-foreground ring-1 ring-border/40">
          {t("noPayoutYet")}
        </p>
      )}
    </div>
  );
}

export function CapUsageBar({
  locked,
  cap,
  usagePct,
  formatMoney,
  currency,
  compact = false,
}: {
  locked: number;
  cap: number;
  usagePct: number;
  formatMoney: MoneyFormatter;
  currency: string | null;
  /** 嵌入 DashboardPanelCard 时隐藏底部说明、缩小图表 */
  compact?: boolean;
}): ReactElement {
  const { t } = useTranslation("dashboard");
  const pct = Math.min(100, Math.max(0, usagePct));
  const fill = capUsageFill(pct);
  const chartConfig = useMemo(
    () => buildUsageBarConfig(t("riskCapUsage")),
    [t],
  );
  const radialData = useMemo(() => [{ usage: pct, fill }], [pct, fill]);

  if (compact) {
    const lockedLabel = formatMoney(locked, currency);
    const capLabel = cap > 0 ? formatMoney(cap, currency) : t("platformCapUnset");

    return (
      <div className="space-y-2.5">
        <div className="grid grid-cols-2 gap-2">
          <div className="rounded-lg bg-primary/5 px-2.5 py-2 ring-1 ring-primary/15">
            <p className="text-[10px] font-medium uppercase tracking-wide text-primary/80">
              {t("platformLockedLabel")}
            </p>
            <p
              className="mt-1 break-all text-sm font-semibold tabular-nums leading-tight text-foreground"
              title={lockedLabel}
            >
              {lockedLabel}
            </p>
          </div>
          <div className="rounded-lg bg-muted/50 px-2.5 py-2 ring-1 ring-border/60">
            <p className="text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
              {t("platformCapLabel")}
            </p>
            <p
              className="mt-1 break-all text-sm font-semibold tabular-nums leading-tight text-foreground"
              title={capLabel}
            >
              {capLabel}
            </p>
          </div>
        </div>
        <div
          className="h-2 overflow-hidden rounded-full bg-muted"
          role="progressbar"
          aria-valuenow={pct}
          aria-valuemin={0}
          aria-valuemax={100}
          aria-label={t("riskCapUsage")}
        >
          <div
            className="h-full rounded-full transition-[width] duration-500"
            style={{ width: `${pct}%`, backgroundColor: fill }}
          />
        </div>
      </div>
    );
  }

  return (
    <div className="space-y-4">
      <ChartContainer
        config={chartConfig}
        className="mx-auto aspect-square h-[180px] w-full max-w-[200px]"
      >
        <RadialBarChart
          data={radialData}
          startAngle={90}
          endAngle={-270}
          innerRadius="72%"
          outerRadius="100%"
        >
          <PolarAngleAxis type="number" domain={[0, 100]} tick={false} />
          <RadialBar
            dataKey="usage"
            background={{ fill: "var(--muted)" }}
            cornerRadius={8}
            fill={fill}
          />
          <Label
            content={({ viewBox }) => {
              if (!viewBox || !("cx" in viewBox) || !("cy" in viewBox)) {
                return null;
              }
              const { cx, cy } = viewBox as { cx: number; cy: number };
              return (
                <text x={cx} y={cy} textAnchor="middle" dominantBaseline="middle">
                  <tspan className="fill-foreground text-2xl font-bold">
                    {pct.toFixed(1)}%
                  </tspan>
                </text>
              );
            }}
          />
        </RadialBarChart>
      </ChartContainer>
      <p className="text-center text-xs tabular-nums text-muted-foreground">
        {t("lockedAndCap", {
          locked: formatMoney(locked, currency),
          cap: formatMoney(cap, currency),
        })}
      </p>
    </div>
  );
}

export function FinanceStructureChart({
  finance,
  formatMoney,
}: {
  finance: AdminDrawFinanceSummaryData;
  formatMoney: MoneyFormatter;
}): ReactElement {
  const { t } = useTranslation("dashboard");
  const currency = finance.currency_code;
  const bet = finance.total_bet_minor;
  const win = finance.total_win_payout_minor;
  const jackpot = finance.total_jackpot_win_minor;
  const gross = finance.approx_house_gross_minor;
  const payout = finance.total_payout_minor;

  const chartConfig = useMemo(
    () =>
      buildFinanceStructureConfig({
        win: t("winPayout"),
        jackpot: t("jackpotPayout"),
        gross: t("houseGross"),
      }),
    [t],
  );

  if (bet <= 0) {
    return <DashboardChartEmpty message={t("noFinanceActivity")} />;
  }

  const payoutRate = ((payout / bet) * 100).toFixed(1);
  const chartData = [{ segment: "structure", win, jackpot, gross }];

  return (
    <div className="space-y-4">
      <ChartContainer config={chartConfig} className="aspect-auto h-12 w-full">
        <BarChart
          accessibilityLayer
          layout="vertical"
          data={chartData}
          margin={{ top: 0, right: 0, bottom: 0, left: 0 }}
        >
          <XAxis type="number" hide domain={[0, bet]} />
          <YAxis type="category" dataKey="segment" hide width={0} />
          <ChartTooltip
            content={
              <ChartTooltipContent formatter={(value) => formatMoney(Number(value), currency)} />
            }
          />
          <Bar dataKey="win" stackId="structure" fill="var(--color-win)" radius={4} />
          <Bar dataKey="jackpot" stackId="structure" fill="var(--color-jackpot)" radius={4} />
          <Bar dataKey="gross" stackId="structure" fill="var(--color-gross)" radius={4} />
        </BarChart>
      </ChartContainer>
      <p className="text-center text-xs text-muted-foreground">
        {t("payoutRateOfBet", { rate: payoutRate })}
      </p>
      <p className="text-center text-sm tabular-nums">
        <span className="text-muted-foreground">{t("houseGross")} </span>
        <SignedMoney amount={gross} emphasize>
          {formatDashboardSignedMoneyMinor(gross, currency)}
        </SignedMoney>
      </p>
      <ChartLegend content={<ChartLegendContent />} />
    </div>
  );
}

export function PayoutCompositionChart({
  finance,
  formatMoney,
  compact = false,
}: {
  finance: AdminDrawFinanceSummaryData;
  formatMoney: MoneyFormatter;
  compact?: boolean;
}): ReactElement {
  const { t } = useTranslation("dashboard");
  const currency = finance.currency_code;
  const win = finance.total_win_payout_minor;
  const jackpot = finance.total_jackpot_win_minor;
  const total = win + jackpot;

  const chartConfig = useMemo(
    () =>
      buildPayoutPieConfig({
        win: t("winPayout"),
        jackpot: t("jackpotPayout"),
      }),
    [t],
  );

  if (total <= 0) {
    return <DashboardChartEmpty message={t("noPayoutYet")} compact={compact} />;
  }

  const pieData = [
    { key: "win", value: win, fill: "var(--color-win)" },
    { key: "jackpot", value: jackpot, fill: "var(--color-jackpot)" },
  ];

  return (
    <ChartContainer
      config={chartConfig}
      className={cn(
        "mx-auto aspect-square w-full",
        compact ? "h-[72px] max-w-[88px]" : "h-[220px] max-w-[280px]",
      )}
    >
      <PieChart>
        <ChartTooltip
          content={
            <ChartTooltipContent
              nameKey="key"
              formatter={(value) => formatMoney(Number(value), currency)}
            />
          }
        />
        <Pie
          data={pieData}
          dataKey="value"
          nameKey="key"
          innerRadius="58%"
          outerRadius="82%"
          paddingAngle={2}
        >
          {pieData.map((entry) => (
            <Cell key={entry.key} fill={entry.fill} />
          ))}
        </Pie>
        {compact ? null : (
          <ChartLegend content={<ChartLegendContent nameKey="key" />} />
        )}
      </PieChart>
    </ChartContainer>
  );
}

export function HotUsageBars({
  rows,
  compact = false,
}: {
  rows: AdminRiskPoolRow[];
  compact?: boolean;
}): ReactElement {
  const { t } = useTranslation("dashboard");
  const chartConfig = useMemo(() => buildUsageBarConfig(t("riskCapUsage")), [t]);
  const shortenPoolLabel = (value: string): string => {
    const normalized = value.trim();
    const trimmedMeta = normalized.replace(/\s*\(.+\)$/, "");
    return trimmedMeta.length > 10 ? `${trimmedMeta.slice(0, 10)}…` : trimmedMeta;
  };

  const chartData = useMemo(
    () =>
      rows.map((row) => {
        const pct = Math.min(100, Math.max(0, (row.usage_ratio ?? 0) * 100));
        return {
          number: row.normalized_number.trim(),
          displayNumber: shortenPoolLabel(row.normalized_number),
          usage: pct,
          fill: usageBarFill(pct),
        };
      }),
    [rows],
  );

  if (rows.length === 0) {
    return <DashboardChartEmpty message={t("noPoolData")} />;
  }

  const chartHeight = compact
    ? Math.min(220, Math.max(120, rows.length * 22 + 36))
    : Math.min(420, Math.max(160, rows.length * 32 + 48));

  return (
    <ChartContainer
      config={chartConfig}
      className="aspect-auto w-full"
      style={{ height: chartHeight }}
    >
      <BarChart
        accessibilityLayer
        layout="vertical"
        data={chartData}
        margin={{ top: 4, right: 12, bottom: 4, left: 4 }}
      >
        <XAxis type="number" domain={[0, 100]} hide />
        <YAxis
          type="category"
          dataKey="number"
          width={compact ? 92 : 112}
          tickLine={false}
          axisLine={false}
          tickFormatter={(value) => {
            const row = chartData.find((item) => item.number === value);
            return row?.displayNumber ?? value;
          }}
          tick={{ fontSize: 11, fontFamily: "var(--font-mono)" }}
        />
        <ChartTooltip
          content={
            <ChartTooltipContent
              formatter={(value, _name, item) => {
                const payload = item?.payload as { number?: string } | undefined;
                const number = payload?.number ? `${payload.number} · ` : "";
                return `${number}${Number(value).toFixed(1)}%`;
              }}
            />
          }
        />
        <Bar dataKey="usage" radius={4} barSize={14}>
          {chartData.map((entry) => (
            <Cell key={entry.number} fill={entry.fill} />
          ))}
        </Bar>
      </BarChart>
    </ChartContainer>
  );
}

export function SoldOutRing({ buckets }: { buckets: SoldOutBuckets }): ReactElement {
  const { t } = useTranslation("dashboard");

  const chartConfig = useMemo(
    () =>
      buildSoldOutPieConfig({
        d4: t("soldOutBuckets.d4"),
        d3: t("soldOutBuckets.d3"),
        d2: t("soldOutBuckets.d2"),
        special: t("soldOutBuckets.special"),
        other: t("soldOutBuckets.other"),
      }),
    [t],
  );

  const entries: (keyof SoldOutBuckets)[] = ["d4", "d3", "d2", "special", "other"];
  const total = entries.reduce((s, key) => s + buckets[key], 0);

  if (total === 0) {
    return <DashboardChartEmpty message={t("noSoldOutNumbers")} />;
  }

  const pieData = entries
    .filter((key) => buckets[key] > 0)
    .map((key) => ({
      key,
      value: buckets[key],
      fill: `var(--color-${key})`,
    }));

  return (
    <ChartContainer config={chartConfig} className="mx-auto aspect-square h-[240px] w-full max-w-[320px]">
      <PieChart>
        <ChartTooltip content={<ChartTooltipContent nameKey="key" />} />
        <Pie
          data={pieData}
          dataKey="value"
          nameKey="key"
          innerRadius="58%"
          outerRadius="82%"
          paddingAngle={2}
        >
          {pieData.map((entry) => (
            <Cell key={entry.key} fill={entry.fill} />
          ))}
          <Label
            content={({ viewBox }) => {
              if (!viewBox || !("cx" in viewBox) || !("cy" in viewBox)) {
                return null;
              }
              const { cx, cy } = viewBox as { cx: number; cy: number };
              return (
                <text x={cx} y={cy} textAnchor="middle" dominantBaseline="middle">
                  <tspan className="fill-foreground text-3xl font-bold">{total}</tspan>
                  <tspan x={cx} dy="1.4em" className="fill-muted-foreground text-xs">
                    {t("soldOutTotal")}
                  </tspan>
                </text>
              );
            }}
          />
        </Pie>
        <ChartLegend content={<ChartLegendContent nameKey="key" />} />
      </PieChart>
    </ChartContainer>
  );
}

export function ResultBatchProgress({
  draw,
  compact = false,
}: {
  draw: AdminDashboardDrawPanel;
  compact?: boolean;
}): ReactElement {
  const { t } = useTranslation("dashboard");
  const { total, pending_review, published } = draw.result_batch_counts;
  const other = Math.max(0, total - pending_review - published);

  const chartConfig = useMemo(
    () =>
      buildBatchProgressConfig({
        pending: t("batchPending"),
        published: t("batchPublished"),
        other: t("batchOther"),
      }),
    [t],
  );

  const chartData = [{ row: "batches", pending: pending_review, published, other }];

  const statCells = (
    <div className="grid grid-cols-3 gap-2 text-center">
      <div className="rounded-lg bg-amber-500/8 px-2 py-2 ring-1 ring-amber-500/15">
        <p
          className={cn(
            "font-bold tabular-nums text-amber-700 dark:text-amber-400",
            compact ? "text-lg" : "text-2xl",
          )}
        >
          {pending_review}
        </p>
        <p className="mt-0.5 text-[10px] text-muted-foreground">{t("batchPending")}</p>
      </div>
      <div className="rounded-lg bg-emerald-500/8 px-2 py-2 ring-1 ring-emerald-500/15">
        <p
          className={cn(
            "font-bold tabular-nums text-emerald-700 dark:text-emerald-400",
            compact ? "text-lg" : "text-2xl",
          )}
        >
          {published}
        </p>
        <p className="mt-0.5 text-[10px] text-muted-foreground">{t("batchPublished")}</p>
      </div>
      <div className="rounded-lg bg-muted/50 px-2 py-2 ring-1 ring-border/60">
        <p className={cn("font-bold tabular-nums text-foreground", compact ? "text-lg" : "text-2xl")}>
          {total}
        </p>
        <p className="mt-0.5 text-[10px] text-muted-foreground">{t("batchTotal")}</p>
      </div>
    </div>
  );

  if (compact) {
    return statCells;
  }

  return (
    <div className="space-y-4">
      <ChartContainer config={chartConfig} className="aspect-auto h-10 w-full">
        <BarChart
          accessibilityLayer
          layout="vertical"
          data={chartData}
          margin={{ top: 0, right: 0, bottom: 0, left: 0 }}
        >
          <XAxis type="number" hide domain={[0, Math.max(total, 1)]} />
          <YAxis type="category" dataKey="row" hide width={0} />
          <Bar dataKey="pending" stackId="batch" fill="var(--color-pending)" radius={4} />
          <Bar dataKey="published" stackId="batch" fill="var(--color-published)" radius={4} />
          <Bar dataKey="other" stackId="batch" fill="var(--color-other)" radius={4} />
        </BarChart>
      </ChartContainer>
      {statCells}
    </div>
  );
}

export function ResultBatchQueueSummary({
  queue,
  compact = false,
}: {
  queue: AdminDashboardResultBatchQueue;
  compact?: boolean;
}): ReactElement {
  const { t } = useTranslation("dashboard");
  const pendingReviewTotal = coerceAdminMinor(queue.pending_review_total);
  const pendingDrawCount = coerceAdminMinor(queue.pending_draw_count);
  const publishedTotal = coerceAdminMinor(queue.published_total);
  const batchTotal = coerceAdminMinor(queue.batch_total);

  return (
    <div className="grid grid-cols-3 gap-2 text-center">
      <div className="rounded-lg bg-amber-500/8 px-2 py-2 ring-1 ring-amber-500/15">
        <p
          className={cn(
            "font-bold tabular-nums text-amber-700 dark:text-amber-400",
            compact ? "text-lg" : "text-2xl",
          )}
        >
          {pendingReviewTotal}
        </p>
        <p className="mt-0.5 text-[10px] text-muted-foreground">{t("batchPending")}</p>
      </div>
      <div className="rounded-lg bg-emerald-500/8 px-2 py-2 ring-1 ring-emerald-500/15">
        <p
          className={cn(
            "font-bold tabular-nums text-emerald-700 dark:text-emerald-400",
            compact ? "text-lg" : "text-2xl",
          )}
        >
          {publishedTotal}
        </p>
        <p className="mt-0.5 text-[10px] text-muted-foreground">{t("batchPublished")}</p>
      </div>
      <div className="rounded-lg bg-muted/50 px-2 py-2 ring-1 ring-border/60">
        <p className={cn("font-bold tabular-nums text-foreground", compact ? "text-lg" : "text-2xl")}>
          {pendingDrawCount > 0 ? pendingDrawCount : batchTotal}
        </p>
        <p className="mt-0.5 text-[10px] text-muted-foreground">
          {pendingDrawCount > 0 ? t("batchPendingDraws") : t("batchTotal")}
        </p>
      </div>
    </div>
  );
}

export function PlatformLifetimePayoutSnapshot({
  finance,
  formatMoney,
}: {
  finance: AdminDashboardLifetimeFinance;
  formatMoney: MoneyFormatter;
}): ReactElement {
  const { t } = useTranslation("dashboard");
  const currency = finance.currency_code;
  const bet = coerceAdminMinor(finance.total_bet_minor);
  const payout = coerceAdminMinor(finance.total_payout_minor);
  let win = coerceAdminMinor(finance.total_win_minor);
  const jackpot = coerceAdminMinor(finance.total_jackpot_minor);
  if (payout > 0 && win + jackpot === 0) {
    win = payout;
  }
  const hasPayout = payout > 0 || win + jackpot > 0;

  if (bet <= 0 && !hasPayout) {
    return <DashboardChartEmpty message={t("platformNoFinanceActivity")} compact />;
  }

  const cells = [
    { key: "bet", label: t("platformBetTotal"), amount: bet, emphasize: bet > 0 },
    { key: "win", label: t("winPayout"), amount: win, emphasize: win > 0 },
    { key: "jackpot", label: t("jackpotPayout"), amount: jackpot, emphasize: jackpot > 0 },
  ] as const;

  return (
    <div className="space-y-3">
      <DashboardFinanceMetricCells cells={cells} currency={currency} formatMoney={formatMoney} />
      {!hasPayout ? (
        <p className="rounded-lg bg-muted/25 px-2 py-2 text-center text-[11px] text-muted-foreground ring-1 ring-border/40">
          {t("platformNoPayoutYet")}
        </p>
      ) : null}
    </div>
  );
}

export function SettlementStatusChart({
  finance,
}: {
  finance: AdminDrawFinanceSummaryData;
}): ReactElement {
  const { t } = useTranslation("dashboard");
  const settlementBatches = finance.settlement_batches;

  const entries = useMemo(() => {
    const counts = new Map<string, number>();
    for (const b of settlementBatches ?? []) {
      counts.set(b.status, (counts.get(b.status) ?? 0) + 1);
    }
    return [...counts.entries()].sort((a, b) => b[1] - a[1]);
  }, [settlementBatches]);

  const chartConfig = useMemo(
    (): ChartConfig =>
      buildSettlementBarConfig(
        entries.map(([status]) => ({
          status,
          label: status,
          color: settlementBarColor(status),
        })),
      ),
    [entries],
  );

  if (!settlementBatches || settlementBatches.length === 0) {
    return <DashboardChartEmpty message={t("noSettlementBatches")} />;
  }

  const chartData = entries.map(([status, count]) => ({
    status,
    count,
    fill: settlementBarColor(status),
  }));

  const chartHeight = Math.min(360, Math.max(140, entries.length * 40 + 40));

  return (
    <ChartContainer
      config={chartConfig}
      className="aspect-auto w-full"
      style={{ height: chartHeight }}
    >
      <BarChart
        accessibilityLayer
        layout="vertical"
        data={chartData}
        margin={{ top: 4, right: 12, bottom: 4, left: 8 }}
      >
        <XAxis type="number" hide />
        <YAxis
          type="category"
          dataKey="status"
          width={108}
          tickLine={false}
          axisLine={false}
          tick={{ fontSize: 11 }}
        />
        <ChartTooltip content={<ChartTooltipContent />} />
        <Bar dataKey="count" radius={4} barSize={16}>
          {chartData.map((entry) => (
            <Cell key={entry.status} fill={entry.fill} />
          ))}
        </Bar>
      </BarChart>
    </ChartContainer>
  );
}
