"use client";

import { toastApiError } from "@/lib/toast-api-error";
import * as React from "react";

import { useAnyChanged } from "@/hooks/use-changed";
import { resolveCustomerTenant } from "@/app/customer/_lib/resolve-customer-tenant";
import Link from "next/link";
import { useParams } from "next/navigation";
import { format } from "date-fns";
import { Download, RefreshCw, X } from "lucide-react";
import { toast } from "sonner";
import { isUserDrivenNow, notifyTutorialAction } from "@/components/customer-tutorial/tutorial-actions";

import {
  CustomerFieldsButton,
  CustomerFieldsPanel,
} from "@/app/customer/_components/customer-fields-panel";
import type { CustomerColumnPrefs } from "@/app/customer/_lib/customer-grid-columns";
import { useCustomerTableColumnPrefs } from "@/app/customer/_lib/use-customer-table-column-prefs";
import { Button } from "@/components/ui/button";
import { DatePicker } from "@/components/date-range-picker";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { DEFAULT_REPORT_PAGE_SIZE } from "@/config/pagination";
import { cn } from "@/lib/utils";

import { AsOfSnapshotBanner } from "@/app/customer/[tenant]/reports/_shared/components/as-of-snapshot-banner";
import { ReportLoadingPanel } from "@/app/customer/[tenant]/reports/_shared/components/report-loading-panel";
import { deriveAsOfViewState, formatYmd } from "@/app/customer/[tenant]/reports/_shared/lib/as-of-snapshot-view";
import {
  ReportPageNavigation,
  ReportRecordsPerPage,
  ReportSortableHead,
  ReportTableHead,
} from "@/app/customer/[tenant]/reports/_shared/components/report-table-chrome";
import { nextReportSort } from "@/app/customer/[tenant]/reports/_shared/lib/report-sort";
import { useOverallSummaryList } from "../../_lib/use-overall-summary-list";
import {
  emptyOverallSummaryColumnFilterOptions,
  type OverallSummaryColumnFilterOption,
  type OverallSummaryColumnFilterOptions,
} from "../../_lib/overall-summary-types";
import {
  exportOverallSummary,
  listOverallSummaryBanks,
  saveOverallSummaryColumnPrefsClient,
} from "../../_lib/overall-summary-api";
import { useCustomerReportQuery } from "@/app/customer/_lib/use-customer-report-query";
import { useOverallSummaryAsOf } from "../../_lib/use-overall-summary-asof";
import { useOverallSummaryRegen } from "../../_lib/use-overall-summary-regen";
import {
  OVERALL_SUMMARY_COLUMNS,
  OVERALL_SUMMARY_LOCKED_KEYS,
  renderOverallSummaryCell,
  renderOverallSummaryFooterCell,
  resolveOverallSummaryVisibleColumns,
} from "./overall-summary-columns";
import { OverallSummaryFiltersDialog } from "./overall-summary-filters-dialog";
import { formatPlainAmount } from "@/lib/format/numbers";
import {
  type OverallSummaryColumnFilters,
  type OverallSummaryFilters,
  type OverallSummarySortKey,
  type OverallSummarySortOrder,
  defaultOverallSummaryColumnFilters,
  defaultOverallSummaryFilters,
  hasActiveOverallSummaryFilters,
} from "./schema";
import { usePortfolioScopePageReset } from "@/app/customer/_lib/admin/use-portfolio-scope-page-reset";
import { TransactionFilterSelect } from "@/app/customer/_components/customer-transaction-list-filters";
import { useReportBankMode } from "@/app/customer/_lib/use-report-bank-mode";
import { useReportBankModeInvalidation } from "@/app/customer/_lib/use-report-bank-mode-invalidation";
import { computeTotals } from "./utils";
import { formatNumberFixed } from "@/lib/format/numbers";

const PAGE_SIZE_OPTIONS = [25, 50, 100, 200, 500] as const;

/** Format a backend timestamp as UTC text (stable across SSR/client timezones). */
function formatUtcTimestamp(raw: string): string | null {
  const normalized = raw.includes("T") ? raw : `${raw.replace(" ", "T")}Z`;
  const parsed = new Date(normalized);
  if (Number.isNaN(parsed.getTime())) return null;
  const pad = (n: number) => String(n).padStart(2, "0");
  return `${pad(parsed.getUTCDate())}/${pad(parsed.getUTCMonth() + 1)}/${parsed.getUTCFullYear()} ${pad(parsed.getUTCHours())}:${pad(parsed.getUTCMinutes())}`;
}

function FilterHead({
  value,
  onChange,
  placeholder,
  className,
}: {
  value: string;
  onChange: (v: string) => void;
  placeholder: string;
  className?: string;
}) {
  return (
    <TableHead className={cn("px-2 py-2", className)}>
      <Input
        value={value}
        onChange={(e) => onChange(e.target.value)}
        placeholder={placeholder}
        className="h-8 min-w-28 text-xs"
      />
    </TableHead>
  );
}

function SelectFilterHead({
  value,
  onChange,
  options,
  placeholder,
  className,
}: {
  value: string;
  onChange: (v: string) => void;
  options: OverallSummaryColumnFilterOption[];
  placeholder: string;
  className?: string;
}) {
  const mergedOptions = React.useMemo(() => {
    if (!value || options.some((option) => option.value === value)) return options;
    return [{ value, label: value }, ...options];
  }, [options, value]);

  return (
    <TableHead className={cn("px-2 py-2", className)}>
      <TransactionFilterSelect
        value={value || undefined}
        options={mergedOptions}
        onValueChange={(next) => onChange(next ?? "")}
        placeholder={placeholder || "All"}
        allLabel="All"
        searchPlaceholder="Search..."
        className="h-8 min-w-28 text-xs"
      />
    </TableHead>
  );
}

function columnSelectOptions(
  filterKey: keyof OverallSummaryColumnFilters,
  options: OverallSummaryColumnFilterOptions,
): OverallSummaryColumnFilterOption[] {
  switch (filterKey) {
    case "bank":
      return options.banks;
    case "parentBank":
      return options.parentBanks;
    case "assetClass":
      return options.assetClasses;
    case "assetType":
      return options.assetTypes;
    case "industry":
      return options.industries;
    case "sector":
      return options.sectors;
    default:
      return [];
  }
}

function PortfolioTotalsBar({
  currencyTotals,
  portfolioValue,
  reportingCurrency,
  excludeCount,
  lastUpdated,
  asOfLabel,
}: {
  currencyTotals: { currency: string; total: number }[];
  portfolioValue: number;
  reportingCurrency: string;
  excludeCount: number;
  lastUpdated: string | null;
  /** "As of" date shown next to the total when viewing a historical snapshot. */
  asOfLabel: string | null;
}) {
  // Banner shows the FX-converted AUM total from the backend — never recalculate from rows.
  const currency =
    currencyTotals[0]?.currency || reportingCurrency || "USD";
  const total = Number.isFinite(portfolioValue) ? portfolioValue : 0;

  return (
    <div className="overflow-hidden rounded-xl border border-border/80 bg-card shadow-sm">
      <div className="flex items-center gap-x-4 gap-y-1 overflow-x-auto px-4 py-2.5 sm:px-5">
        <span className="shrink-0 font-semibold text-[11px] text-emerald-700 uppercase tracking-wider dark:text-emerald-400">
          Total market value{asOfLabel ? ` · as of ${asOfLabel}` : ""}
        </span>
        <p className="shrink-0 whitespace-nowrap text-sm tabular-nums">
          <span className="font-bold text-foreground">{currency}</span>
          <span className="text-muted-foreground"> </span>
          <span className="font-semibold text-emerald-700 dark:text-emerald-400">
            {formatNumberFixed(total, 2)}
          </span>
        </p>
        {excludeCount > 0 ? (
          <span className="shrink-0 text-[10px] text-amber-600 dark:text-amber-400">
            *excluding {excludeCount} transactions
          </span>
        ) : null}
        {lastUpdated ? (
          <span className="ml-auto shrink-0 whitespace-nowrap text-muted-foreground text-[11px]">
            {asOfLabel ? "Snapshot built" : "Updated"} {lastUpdated} UTC
          </span>
        ) : null}
      </div>
    </div>
  );
}

export function OverallSummaryView() {
  const params = useParams<{ tenant?: string }>();
  const tenant = typeof params?.tenant === "string" && params.tenant.trim() ? params.tenant.trim() : resolveCustomerTenant();
  const [filters, setFilters] = React.useState<OverallSummaryFilters>(defaultOverallSummaryFilters);
  const [columnFilters, setColumnFilters] = React.useState<OverallSummaryColumnFilters>(
    defaultOverallSummaryColumnFilters,
  );
  const [sortKey, setSortKey] = React.useState<OverallSummarySortKey | null>("refId");
  const [sortOrder, setSortOrder] = React.useState<OverallSummarySortOrder>("desc");
  const [page, setPage] = React.useState(0);
  const [pageSize, setPageSize] = React.useState<number>(DEFAULT_REPORT_PAGE_SIZE);
  const [refreshKey, setRefreshKey] = React.useState(0);
  // Historical "as of" view: a past date shows holdings, prices and FX as they stood then.
  // Today / a future date is the same as live, so it is treated as no date.
  const [asOfDate, setAsOfDate] = React.useState("");
  const todayYmd = format(new Date(), "yyyy-MM-dd");
  const effectiveAsOf = asOfDate && asOfDate < todayYmd ? asOfDate : "";
  const { mode: bankFilterMode } = useReportBankMode(tenant);

  // Portfolio Scope Apply / banner Clear: also drop page search
  // filters via the shared reset hook (same contract as structure/accumulator).
  usePortfolioScopePageReset(
    React.useCallback(() => {
      setFilters(defaultOverallSummaryFilters);
      setColumnFilters(defaultOverallSummaryColumnFilters);
      setPage(0);
      setRefreshKey((value) => value + 1);
    }, []),
  );

  useReportBankModeInvalidation(() => {
    setFilters((current) => ({ ...current, banks: [] }));
    setPage(0);
    setRefreshKey((value) => value + 1);
  });

  const [isExporting, setIsExporting] = React.useState(false);
  const [fieldsOpen, setFieldsOpen] = React.useState(false);

  const handleRegenCompleted = React.useCallback(() => {
    // Rebuild finished — bump refreshKey so the next list fetch also hits /totals.
    setRefreshKey((k) => k + 1);
    toast.success("Overall summary rebuilt");
  }, []);

  const handleRegenFailed = React.useCallback((error: string) => {
    toast.error(error);
  }, []);

  const {
    state: regenState,
    isBusy: isRebuilding,
    isStarting: isStartingRebuild,
    errorMessage: regenErrorMessage,
    startRebuild,
  } = useOverallSummaryRegen({
    onCompleted: handleRegenCompleted,
    onFailed: handleRegenFailed,
  });

  const handleAsOfBuilt = React.useCallback(() => {
    setRefreshKey((k) => k + 1);
    toast.success("Snapshot ready");
  }, []);

  const handleAsOfFailed = React.useCallback((error: string) => {
    toast.error(error);
  }, []);

  const {
    state: asOfState,
    isBuilding: isBuildingAsOf,
    errorMessage: asOfErrorMessage,
    snapshotKey,
    rebuild: rebuildAsOf,
  } = useOverallSummaryAsOf({
    asOf: effectiveAsOf,
    onBuilt: handleAsOfBuilt,
    onFailed: handleAsOfFailed,
  });

  const {
    rows,
    upcomingRows,
    potentialRows,
    totalCount,
    pageCount,
    reportingCurrency,
    totals,
    columnPrefs,
    empty,
    scopeActive,
    asOfInfo,
    isLoading,
    errorMessage,
    columnFilterOptions,
  } = useOverallSummaryList({
    page: page + 1,
    pageSize,
    sortKey,
    sortOrder,
    filters,
    columnFilters,
    refreshKey,
    asOf: effectiveAsOf,
    snapshotKey,
  });

  // Keep the last non-empty option lists so the header selects don't blank out while a reload is in flight.
  const [stableColumnFilterOptions, setStableColumnFilterOptions] = React.useState(
    emptyOverallSummaryColumnFilterOptions,
  );
  if (
    stableColumnFilterOptions !== columnFilterOptions &&
    Object.values(columnFilterOptions).some((list) => list.length > 0)
  ) {
    setStableColumnFilterOptions(columnFilterOptions);
  }

  const saveColumnPrefs = React.useCallback(async (prefs: CustomerColumnPrefs) => {
    await saveOverallSummaryColumnPrefsClient(prefs);
  }, []);

  const {
    columnVisibility,
    columnOrder,
    hideAll,
    setColumnVisible,
    reorderShown,
  } = useCustomerTableColumnPrefs({
    catalog: OVERALL_SUMMARY_COLUMNS,
    initialPrefs: columnPrefs,
    lockedKeys: OVERALL_SUMMARY_LOCKED_KEYS,
    savePrefs: saveColumnPrefs,
  });

  const visibleColumns = React.useMemo(
    () => resolveOverallSummaryVisibleColumns(columnOrder, columnVisibility as Record<string, boolean>),
    [columnOrder, columnVisibility],
  );

  const pageTotals = React.useMemo(() => computeTotals(rows), [rows]);
  const currencyTotals = totals?.byCurrency?.length ? totals.byCurrency : [];

  // Bank filter dropdown: the full tenant bank picker, not just banks seen on
  // the currently loaded page of rows (paginated `rows` would otherwise hide
  // banks until the user pages through the whole report). The endpoint is
  // already mode-aware — Child mode returns tenant bank_id/bank_name rows,
  // Parent mode returns synthetic master mw_parent_banks id/name rows — so
  // the two id spaces are never mixed. Keyed by mode so switching Parent/Child
  // refetches automatically.
  const { data: banksFromApi } = useCustomerReportQuery({
    queryKey: `overall-summary-banks:${bankFilterMode}`,
    queryFn: () => listOverallSummaryBanks(),
    emptyErrorMessage: "Bank options could not be loaded.",
  });

  const bankNameByIdMap = React.useMemo(() => {
    const map = new Map<string, string>();
    for (const bank of banksFromApi ?? []) {
      const id = bank.bank_id.trim();
      if (!id) continue;
      map.set(id, bank.bank_name.trim() || id);
    }
    return map;
  }, [banksFromApi]);

  const bankOptions = React.useMemo(
    () =>
      [...bankNameByIdMap.keys()].sort((left, right) =>
        (bankNameByIdMap.get(left) ?? left).localeCompare(bankNameByIdMap.get(right) ?? right),
      ),
    [bankNameByIdMap],
  );

  const bankNameById = React.useCallback(
    (id: string) => bankNameByIdMap.get(id) ?? id,
    [bankNameByIdMap],
  );

  // Banner AUM from dedicated totals endpoint (`value` = Yii1 summary_data Total Value).
  const portfolioValue =
    totals != null ? Number(totals.value) || Number(totals.marketValue) || 0 : 0;
  const headerReportingCurrency = totals?.code || reportingCurrency;
  const excludeCount = totals?.excludeCount ?? 0;
  const resolvedPageCount = Math.max(pageCount, 1);
  const pageRows = rows;
  const rangeStart = totalCount === 0 ? 0 : page * pageSize + 1;
  const rangeEnd = Math.min((page + 1) * pageSize, totalCount);

  const asOfView = deriveAsOfViewState(effectiveAsOf, asOfState);
  const { waiting: asOfWaiting, failed: asOfFailed } = asOfView;

  const hasActiveFilters = hasActiveOverallSummaryFilters(filters);
  const hasColumnFilters = Object.values(columnFilters).some((v) => v.trim() !== "");
  // Rebuild only when the summary cache is truly empty — not when Scope/filters hide rows.
  // TOTAL VALUE > 0 with 0 positions means data exists; the query is just filtered.
  const portfolioTotalValue = portfolioValue;
  const isCacheEmpty =
    !effectiveAsOf
    && !isLoading && totalCount === 0 && portfolioTotalValue <= 0 && !scopeActive && !hasActiveFilters && !hasColumnFilters;
  const isFilteredEmpty = !isLoading && totalCount === 0 && !isCacheEmpty;
  const lastUpdated = React.useMemo(() => {
    const raw = effectiveAsOf
      ? (asOfInfo?.builtAt ?? asOfState?.builtAt ?? "")
      : regenState?.finishedAt?.trim() || regenState?.startedAt?.trim();
    if (!raw) return null;
    return formatUtcTimestamp(raw);
  }, [effectiveAsOf, asOfInfo?.builtAt, asOfState?.builtAt, regenState?.finishedAt, regenState?.startedAt]);

  const handleRebuild = async () => {
    if (effectiveAsOf) {
      try {
        await rebuildAsOf();
        toast.message("Snapshot rebuild queued", {
          description: `Rebuilding the summary as of ${formatYmd(effectiveAsOf)} in the background.`,
        });
      } catch (error) {
        toastApiError(error, "Could not start snapshot rebuild");
      }
      return;
    }
    try {
      await startRebuild();
      toast.message("Rebuild queued", {
        description: "Summary table is rebuilding in the background.",
      });
    } catch (error) {
      toastApiError(error, "Could not start rebuild");
    }
  };

  const footerLabelSpan = React.useMemo(() => {
    const labelIndex = visibleColumns.findIndex((column) => column.footer === "label");
    const purchaseIndex = visibleColumns.findIndex((column) => column.footer === "purchaseValue");
    if (labelIndex < 0) return 1;
    if (purchaseIndex < 0) return Math.max(1, visibleColumns.length);
    return Math.max(1, purchaseIndex - labelIndex);
  }, [visibleColumns]);

  // Any filter/sort/size/as-of change returns to the first page (adjusted during render, not in an effect).
  if (useAnyChanged([filters, columnFilters, sortKey, sortOrder, pageSize, effectiveAsOf])) {
    setPage(0);
  }

  const handleSort = (key: OverallSummarySortKey) => {
    const next = nextReportSort({ key: sortKey, order: sortOrder }, key);
    setSortKey(next.key);
    setSortOrder(next.order);
  };

  const setCol = (key: keyof OverallSummaryColumnFilters, value: string) => {
    setColumnFilters((p) => ({ ...p, [key]: value }));
  };

  const handleExport = async (formatType: "excel" | "csv") => {
    const userDriven = isUserDrivenNow();
    setIsExporting(true);
    try {
      await exportOverallSummary(formatType, {
        sortKey,
        sortOrder,
        filters,
        columnFilters,
        asOf: effectiveAsOf,
      });
      toast.success(formatType === "excel" ? "Excel download started" : "CSV download started");
      notifyTutorialAction("report-exported", { userDriven });
    } catch (error) {
      toastApiError(error, "Export failed");
    } finally {
      setIsExporting(false);
    }
  };

  const meta = {
    title: "Overall Summary",
    reportingCurrency: headerReportingCurrency,
  };

  const emptyTableMessage = (() => {
    if (isLoading || isRebuilding) return "";
    if (asOfFailed) return "The snapshot for this date could not be built. Use Rebuild snapshot to try again.";
    if (effectiveAsOf && !hasActiveFilters && !hasColumnFilters && !scopeActive) {
      return `No holdings on ${formatYmd(effectiveAsOf)}.`;
    }
    if (isCacheEmpty) {
      return "Summary table is empty. Rebuild to refresh holdings from live data.";
    }
    if (scopeActive) {
      return "No holdings for the current Portfolio Scope. Clear Scope or pick another User/Customer.";
    }
    return "No holdings match your filters.";
  })();
  const busyRebuilding = effectiveAsOf ? isBuildingAsOf : isRebuilding;
  const showLoadingOverlay = isLoading || isRebuilding || asOfWaiting;
  // While a first-time snapshot builds, never show rows left over from another date.
  const displayRows = asOfWaiting ? [] : pageRows;

  return (
    <div className="flex min-w-0 flex-col gap-4">
      <div className="mb-4 flex w-full min-w-0 flex-col gap-4 border-b pb-4 sm:flex-row sm:items-center">
        <div className="shrink-0">
          <h1 className="font-semibold text-2xl tracking-tight">{meta.title}</h1>
          <p className="text-muted-foreground text-sm">
            Displaying {rangeStart}-{rangeEnd} of {formatPlainAmount(totalCount)} results.
            {isLoading ? " Loading…" : ""}
            {isRebuilding
              ? regenState?.regenStatus === "queued"
                ? " Rebuild queued…"
                : " Rebuilding…"
              : ""}
            {effectiveAsOf && isBuildingAsOf ? " Building snapshot…" : ""}
          </p>
          {errorMessage ? <p className="text-destructive text-sm">{errorMessage}</p> : null}
          {regenErrorMessage ? <p className="text-destructive text-sm">{regenErrorMessage}</p> : null}
          {asOfErrorMessage ? <p className="text-destructive text-sm">{asOfErrorMessage}</p> : null}
          {asOfFailed && asOfState?.error ? (
            <p className="text-destructive text-sm">Snapshot build failed: {asOfState.error}</p>
          ) : null}
          {regenState?.regenStatus === "failed" && !isRebuilding ? (
            <p className="text-destructive text-sm">Rebuild failed</p>
          ) : null}
        </div>

        <div className="flex w-full min-w-0 flex-wrap items-center justify-end gap-2 sm:ml-auto sm:w-auto">
          <ReportRecordsPerPage
            id="overall-summary-rows-per-page"
            pageSize={pageSize}
            options={PAGE_SIZE_OPTIONS}
            onPageSizeChange={(size) => {
              setPageSize(size);
              setPage(0);
            }}
          />
          <div className="flex items-center gap-1.5">
            <DatePicker
              value={asOfDate}
              placeholder="As of date (live)"
              align="end"
              className="h-9 w-[10.5rem]"
              onChange={(value) => setAsOfDate(value)}
            />
            {asOfDate ? (
              <Button
                type="button"
                variant="ghost"
                size="icon"
                className="size-9 shrink-0"
                aria-label="Clear as-of date (use live data)"
                onClick={() => setAsOfDate("")}
              >
                <X className="size-4" />
              </Button>
            ) : null}
          </div>
          <OverallSummaryFiltersDialog
            filters={filters}
            onApply={setFilters}
            bankOptions={bankOptions}
            bankLabel={bankNameById}
          />
          <CustomerFieldsButton onClick={() => setFieldsOpen(true)} />
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <Button
                variant="outline"
                size="icon-lg"
                className="h-9 w-9 shrink-0"
                data-tutorial="report-export"
              >
                <Download className="size-4" />
                <span className="sr-only">Download</span>
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end">
              <DropdownMenuItem
                disabled={isExporting || isLoading || isRebuilding}
                onClick={() => void handleExport("excel")}
              >
                Download Excel
              </DropdownMenuItem>
              <DropdownMenuItem
                disabled={isExporting || isLoading || isRebuilding}
                onClick={() => void handleExport("csv")}
              >
                Download CSV
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
          <Button
            variant="outline"
            size="icon-lg"
            className="h-9 w-9 shrink-0"
            disabled={isLoading}
            onClick={() => {
              setRefreshKey((k) => k + 1);
              toast.success("Summary list refreshed");
            }}
            title="Reload list"
          >
            <RefreshCw className={cn("size-4", isLoading && "animate-spin")} />
            <span className="sr-only">Reload list</span>
          </Button>
          <Button
            variant={empty ? "default" : "outline"}
            size="sm"
            className="h-9 shrink-0 gap-1.5"
            disabled={busyRebuilding || isStartingRebuild || (Boolean(effectiveAsOf) && isRebuilding)}
            onClick={() => void handleRebuild()}
          >
            <RefreshCw className={cn("size-3.5", busyRebuilding && "animate-spin")} />
            {busyRebuilding ? "Rebuilding…" : effectiveAsOf ? "Rebuild snapshot" : "Rebuild"}
          </Button>
          <Button variant="outline" size="icon-lg" className="h-9 w-9 shrink-0" asChild>
            <Link href={`/customer/${tenant}/dashboard`}>
              <X className="size-4" />
              <span className="sr-only">Close</span>
            </Link>
          </Button>
        </div>
      </div>

      {effectiveAsOf ? (
        <AsOfSnapshotBanner
          asOf={effectiveAsOf}
          view={asOfView}
          buildingAsOf={asOfState?.buildingAsOf}
          onBackToLive={() => setAsOfDate("")}
        />
      ) : null}
      {isCacheEmpty && !isRebuilding ? (
        <div className="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-dashed bg-muted/30 px-4 py-3">
          <p className="text-muted-foreground text-sm">
            No cached summary rows for this tenant. Rebuild from live holdings to populate the report.
          </p>
          <Button size="sm" disabled={isStartingRebuild} onClick={() => void handleRebuild()}>
            Rebuild summary
          </Button>
        </div>
      ) : null}
      {isFilteredEmpty && scopeActive && !isRebuilding ? (
        <div className="flex flex-wrap items-center justify-between gap-3 rounded-lg border px-4 py-3">
          <p className="text-muted-foreground text-sm">
            Portfolio Scope is active and this selection has no Overall Summary rows. Clear Scope, or
            rebuild after confirming live Transactions exist for that customer.
          </p>
        </div>
      ) : null}

      <PortfolioTotalsBar
        currencyTotals={currencyTotals}
        portfolioValue={portfolioValue}
        reportingCurrency={headerReportingCurrency}
        excludeCount={excludeCount}
        lastUpdated={lastUpdated}
        asOfLabel={effectiveAsOf ? formatYmd(effectiveAsOf) : null}
      />

      <ReportLoadingPanel
        loading={showLoadingOverlay}
        label={
          asOfWaiting
            ? `Building snapshot as of ${formatYmd(effectiveAsOf)}…`
            : isRebuilding
              ? "Rebuilding summary table…"
              : "Loading holdings…"
        }
        className={cn(showLoadingOverlay && "pointer-events-none")}
      >
        <div className="overflow-x-auto rounded-lg border bg-card">
          <Table>
            <TableHeader>
              <TableRow className="bg-muted/20 hover:bg-muted/20">
                {visibleColumns.map((column) => {
                  const headClass = cn(
                    column.align === "right" && "text-right",
                    column.headClassName,
                  );
                  if (column.sortKey) {
                    return (
                      <ReportSortableHead
                        key={column.key}
                        label={column.label}
                        onSort={() => handleSort(column.sortKey!)}
                        sortOrder={sortKey === column.sortKey ? sortOrder : null}
                        className={headClass}
                        align={column.align === "right" ? "right" : "left"}
                      />
                    );
                  }
                  return (
                    <ReportTableHead key={column.key} className={headClass}>
                      {column.label}
                    </ReportTableHead>
                  );
                })}
              </TableRow>
              <TableRow className="bg-muted/10 hover:bg-muted/10">
                {visibleColumns.map((column) => {
                  if (column.filterKey && column.filterKind === "select") {
                    return (
                      <SelectFilterHead
                        key={column.key}
                        value={columnFilters[column.filterKey]}
                        onChange={(v) => setCol(column.filterKey!, v)}
                        options={columnSelectOptions(column.filterKey, stableColumnFilterOptions)}
                        placeholder={column.filterPlaceholder || "All"}
                        className={column.headClassName}
                      />
                    );
                  }
                  if (column.filterKey) {
                    return (
                      <FilterHead
                        key={column.key}
                        value={columnFilters[column.filterKey]}
                        onChange={(v) => setCol(column.filterKey!, v)}
                        placeholder={column.filterPlaceholder || column.label}
                        className={column.headClassName}
                      />
                    );
                  }
                  return <TableHead key={column.key} className="px-2 py-2" />;
                })}
              </TableRow>
            </TableHeader>
            <TableBody>
              {displayRows.length === 0 && !showLoadingOverlay ? (
                <TableRow>
                  <TableCell
                    colSpan={Math.max(visibleColumns.length, 1)}
                    className="h-24 text-center text-muted-foreground text-sm"
                  >
                    <div className="flex flex-col items-center gap-3">
                      <span>{emptyTableMessage}</span>
                      {isCacheEmpty && !isRebuilding ? (
                        <Button size="sm" onClick={() => void handleRebuild()}>
                          Rebuild summary
                        </Button>
                      ) : null}
                    </div>
                  </TableCell>
                </TableRow>
              ) : (
                displayRows.map((row, index) => (
                  <TableRow key={row.id} className={index % 2 === 0 ? "bg-muted/5" : undefined}>
                    {visibleColumns.map((column) => (
                      <TableCell
                        key={column.key}
                        className={cn(
                          "px-2 py-2 align-middle text-sm",
                          column.align === "right" && "text-right tabular-nums",
                          column.key === "refId" || column.key === "isin"
                            ? "whitespace-nowrap font-mono"
                            : column.key === "parentBank"
                              ? "whitespace-nowrap text-muted-foreground"
                              : column.key === "remarks"
                                ? "text-muted-foreground"
                                : "whitespace-nowrap",
                          column.cellClassName,
                        )}
                      >
                        {renderOverallSummaryCell(column, row)}
                      </TableCell>
                    ))}
                  </TableRow>
                ))
              )}
            </TableBody>
            {displayRows.length > 0 ? (
              <tfoot>
                <TableRow className="border-t-2 bg-muted/40 font-semibold hover:bg-muted/40">
                  {(() => {
                    const cells: React.ReactNode[] = [];
                    let skipUntil = -1;
                    visibleColumns.forEach((column, index) => {
                      if (index < skipUntil) return;
                      const rendered = renderOverallSummaryFooterCell(column, pageTotals, footerLabelSpan);
                      if (!rendered) return;
                      const colSpan = rendered.colSpan ?? 1;
                      if (colSpan > 1) skipUntil = index + colSpan;
                      cells.push(
                        <TableCell key={column.key} colSpan={colSpan} className={cn("px-2 py-2", rendered.className)}>
                          {rendered.content}
                        </TableCell>,
                      );
                    });
                    return cells;
                  })()}
                </TableRow>
              </tfoot>
            ) : null}
            {page === 0 && !showLoadingOverlay
              ? [
                  {
                    key: "upcoming",
                    rows: upcomingRows,
                    label: "Upcoming — trade date after today, not included in totals",
                    bandClass: "bg-sky-500/5 hover:bg-sky-500/5 text-sky-800 dark:text-sky-200",
                  },
                  {
                    key: "potential",
                    rows: potentialRows,
                    label: "Potential — structures awaiting handling, not included in totals",
                    bandClass: "bg-amber-500/5 hover:bg-amber-500/5 text-amber-800 dark:text-amber-200",
                  },
                ]
                  .filter((group) => group.rows.length > 0)
                  .map((group) => (
                    <TableBody key={group.key}>
                      <TableRow className={cn("border-t-2", group.bandClass)}>
                        <TableCell
                          colSpan={Math.max(visibleColumns.length, 1)}
                          className="px-2 py-2 text-xs font-semibold uppercase tracking-wide"
                        >
                          {group.label} ({group.rows.length})
                        </TableCell>
                      </TableRow>
                      {group.rows.map((row) => (
                        <TableRow key={row.id} className="opacity-80">
                          {visibleColumns.map((column) => (
                            <TableCell
                              key={column.key}
                              className={cn(
                                "px-2 py-2 align-middle text-sm",
                                column.align === "right" && "text-right tabular-nums",
                                column.key === "refId" || column.key === "isin"
                                  ? "whitespace-nowrap font-mono"
                                  : "whitespace-nowrap",
                                column.cellClassName,
                              )}
                            >
                              {renderOverallSummaryCell(column, row)}
                            </TableCell>
                          ))}
                        </TableRow>
                      ))}
                    </TableBody>
                  ))
              : null}
          </Table>
        </div>
      </ReportLoadingPanel>

      {totalCount > 0 ? (
        <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
          <p className="text-muted-foreground text-sm">
            Page {resolvedPageCount === 0 ? 0 : page + 1} of {resolvedPageCount}
          </p>
          <ReportPageNavigation
            page={page + 1}
            pageCount={resolvedPageCount}
            onPageChange={(next) => setPage(next - 1)}
            disabled={isLoading || isRebuilding}
          />
        </div>
      ) : null}

      <CustomerFieldsPanel
        open={fieldsOpen}
        onOpenChange={setFieldsOpen}
        catalog={OVERALL_SUMMARY_COLUMNS}
        visibility={columnVisibility as Record<string, boolean>}
        order={columnOrder}
        lockedKeys={OVERALL_SUMMARY_LOCKED_KEYS}
        title="Fields"
        description="Choose which columns to show in the overall summary table and drag to reorder."
        onToggle={setColumnVisible}
        onReorder={reorderShown}
        onHideAll={hideAll}
      />
    </div>
  );
}
