"use client";

import * as React from "react";
import { format, parseISO, isValid } from "date-fns";
import { ArrowLeftRight, Loader2, Search, X } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetTitle,
} from "@/components/ui/sheet";
import {
  SortableTh,
  StickyTableScroll,
  useTableSort,
} from "@/app/customer/[tenant]/reports/_shared/components/sortable-table";
import { cn } from "@/lib/utils";

import {
  ConsolidatedIndividualDialog,
  type ConsolidatedIndividualTarget,
} from "@/app/customer/[tenant]/reports/consolidated-holdings-report/_components/consolidated-holdings-report/consolidated-individual-dialog";

import { fetchPortfolioAnalyticsDayChangesClient } from "../../_lib/portfolio-analytics-api";
import type { PortfolioAnalyticsDayChangeRow } from "../../_lib/portfolio-analytics-server-api";
import { formatCompact } from "./utils";

function canOpenIsinDrilldown(row: { isin?: string; fType?: string; assetTypeTitle?: string }) {
  const isin = row.isin?.trim();
  return Boolean(isin && isin !== "—" && isin !== "--" && row.fType?.trim() && row.assetTypeTitle?.trim());
}

function toIndividualTarget(row: PortfolioAnalyticsDayChangeRow): ConsolidatedIndividualTarget {
  return {
    isin: row.isin.trim(),
    name: row.name?.trim() || undefined,
    assetType: row.assetTypeTitle.trim(),
    assetClass: row.fType.trim(),
    productType: row.productType?.trim() || undefined,
  };
}

type TabId = "all" | "opened" | "closed" | "changed";

type ChangeKind = "Opened" | "Closed" | "Qty" | "Price";

type TableRow = PortfolioAnalyticsDayChangeRow & { kind: ChangeKind };

function formatDay(iso: string) {
  const d = parseISO(iso);
  return isValid(d) ? format(d, "d MMM yyyy") : iso;
}

function formatSigned(value: number) {
  const abs = formatCompact(Math.abs(value));
  if (value > 0) return `+${abs}`;
  if (value < 0) return `−${abs}`;
  return abs;
}

function aumDeltaSum(rows: readonly { valueDelta: number }[]) {
  return rows.reduce((sum, row) => sum + row.valueDelta, 0);
}

/** Share of absolute AUM movement (what % of the period’s |Δ| this section is). */
function absSharePct(part: number, allParts: readonly number[]) {
  const denom = allParts.reduce((sum, n) => sum + Math.abs(n), 0);
  if (denom < 1e-9) return null;
  return (Math.abs(part) / denom) * 100;
}

function formatSharePct(pct: number | null) {
  if (pct == null || !Number.isFinite(pct)) return null;
  return `${pct >= 10 || pct === 0 ? pct.toFixed(0) : pct.toFixed(1)}%`;
}

function formatDeltaShare(delta: number, share: number | null) {
  const pct = formatSharePct(share);
  return pct ? `${formatSigned(delta)} · ${pct}` : formatSigned(delta);
}

function deltaToneClass(delta: number) {
  if (delta > 0) return "text-emerald-600 dark:text-emerald-400";
  if (delta < 0) return "text-red-600 dark:text-red-400";
  return "text-muted-foreground";
}

function mktPct(row: PortfolioAnalyticsDayChangeRow): number | null {
  if (row.priceBefore == null || row.priceAfter == null) return null;
  if (Math.abs(row.priceBefore) < 1e-12) return null;
  return ((row.priceAfter - row.priceBefore) / Math.abs(row.priceBefore)) * 100;
}

/** Compact qty/price for table cells — avoid 10+ decimal soup. */
function formatNum(value: number) {
  const abs = Math.abs(value);
  if (abs >= 1_000_000) return formatCompact(value);
  if (abs >= 1_000) {
    return value.toLocaleString("en-US", { maximumFractionDigits: 2 });
  }
  if (abs >= 1) {
    return value.toLocaleString("en-US", { maximumFractionDigits: 4 });
  }
  return value.toLocaleString("en-US", { maximumFractionDigits: 6 });
}

function formatSignedNum(value: number) {
  const abs = formatNum(Math.abs(value));
  if (value > 0) return `+${abs}`;
  if (value < 0) return `−${abs}`;
  return abs;
}

function qtyDelta(row: PortfolioAnalyticsDayChangeRow): number | null {
  if (row.qtyDelta != null && Number.isFinite(row.qtyDelta)) return row.qtyDelta;
  if (row.qtyBefore != null && row.qtyAfter != null) return row.qtyAfter - row.qtyBefore;
  return null;
}

function mktDelta(row: PortfolioAnalyticsDayChangeRow): number | null {
  if (row.priceDelta != null && Number.isFinite(row.priceDelta)) return row.priceDelta;
  if (row.priceBefore != null && row.priceAfter != null) return row.priceAfter - row.priceBefore;
  return null;
}

function qtyTitle(row: PortfolioAnalyticsDayChangeRow) {
  if (row.qtyBefore != null && row.qtyAfter != null) {
    return `${formatNum(row.qtyBefore)} → ${formatNum(row.qtyAfter)}`;
  }
  if (row.qtyAfter != null) return formatNum(row.qtyAfter);
  if (row.qtyBefore != null) return formatNum(row.qtyBefore);
  return "";
}

function mktTitle(row: PortfolioAnalyticsDayChangeRow) {
  const range =
    row.priceBefore != null && row.priceAfter != null
      ? `${formatNum(row.priceBefore)} → ${formatNum(row.priceAfter)}`
      : row.priceAfter != null
        ? formatNum(row.priceAfter)
        : row.priceBefore != null
          ? formatNum(row.priceBefore)
          : "";
  const pct = mktPct(row);
  if (range && pct != null) {
    const signed = `${pct > 0 ? "+" : pct < 0 ? "−" : ""}${Math.abs(pct).toFixed(1)}%`;
    return `${range} (${signed})`;
  }
  return range;
}

function segmentClass(active: boolean) {
  return cn(
    "rounded-md px-3 py-1.5 text-xs font-medium transition-colors",
    active
      ? "bg-background text-primary shadow-sm"
      : "text-muted-foreground hover:text-foreground",
  );
}

function InfoChip({ label, value, detail }: { label: string; value: string; detail?: string }) {
  return (
    <div className="min-w-0 rounded-xl border border-border/60 bg-card/80 px-3 py-2 shadow-sm backdrop-blur-sm">
      <p className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">{label}</p>
      <p className="mt-0.5 truncate font-medium text-foreground text-sm" title={value}>
        {value}
      </p>
      {detail ? (
        <p className="mt-0.5 truncate text-muted-foreground text-xs" title={detail}>
          {detail}
        </p>
      ) : null}
    </div>
  );
}

/** Read-only analytics card for a section (not a filter control). */
function AnalyticsCard({
  label,
  count,
  aumDelta,
  sharePct,
}: {
  label: string;
  count: number;
  aumDelta: number;
  sharePct: number | null;
}) {
  const share = formatSharePct(sharePct);
  return (
    <div className="min-w-0 rounded-xl border border-border/60 bg-card/80 px-3 py-2 shadow-sm backdrop-blur-sm">
      <p className="truncate text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
        {label}
        <span className="ml-1.5 font-normal normal-case tracking-normal tabular-nums">{count}</span>
      </p>
      <p className={cn("mt-0.5 truncate font-medium text-sm tabular-nums", deltaToneClass(aumDelta))}>
        {formatSigned(aumDelta)}
      </p>
      <p className="mt-0.5 truncate text-muted-foreground text-xs tabular-nums">
        {share != null ? `${share} of |Δ|` : "—"}
      </p>
    </div>
  );
}

function matchesSearch(row: PortfolioAnalyticsDayChangeRow, q: string) {
  if (!q) return true;
  const hay =
    `${row.name} ${row.isin} ${row.bank} ${row.macro} ${row.fType} ${row.assetTypeTitle} ${row.productType}`.toLowerCase();
  return hay.includes(q);
}

function productTypeLabel(row: PortfolioAnalyticsDayChangeRow) {
  const fType = row.fType?.trim().toLowerCase() ?? "";
  const productType = row.productType?.trim() ?? "";
  // Structure (Sr/To): asset_type_title is "ISIN Structure Name" — never use it here.
  if (fType === "to" || fType === "sr") return productType || "—";
  return row.assetTypeTitle?.trim() || productType || row.fType?.trim() || "—";
}

function macroOf(row: { macro?: string }) {
  return row.macro?.trim() || "Unclassified";
}

function sortMacroNames(names: string[]) {
  return [...names].sort((a, b) => {
    if (a === "Unclassified") return 1;
    if (b === "Unclassified") return -1;
    return a.localeCompare(b);
  });
}

type SectionStat = {
  id: string;
  label: string;
  count: number;
  aumDelta: number;
  sharePct: number | null;
};

/** Default: group by macro label; Unclassified last, others A–Z. */
function groupRowsByMacro(
  rows: readonly TableRow[],
): { macro: string; rows: TableRow[]; aumDelta: number; sharePct: number | null }[] {
  const buckets = new Map<string, TableRow[]>();
  for (const row of rows) {
    const macro = macroOf(row);
    const list = buckets.get(macro) ?? [];
    list.push(row);
    buckets.set(macro, list);
  }
  const groups = sortMacroNames([...buckets.keys()]).map((macro) => {
    const groupRows = buckets.get(macro) ?? [];
    return {
      macro,
      rows: groupRows,
      aumDelta: aumDeltaSum(groupRows),
    };
  });
  const deltas = groups.map((g) => g.aumDelta);
  return groups.map((g) => ({ ...g, sharePct: absSharePct(g.aumDelta, deltas) }));
}

function macroTabItems(rows: TableRow[]): SectionStat[] {
  const buckets = new Map<string, TableRow[]>();
  for (const row of rows) {
    const macro = macroOf(row);
    const list = buckets.get(macro) ?? [];
    list.push(row);
    buckets.set(macro, list);
  }
  const macros = sortMacroNames([...buckets.keys()]).map((macro) => {
    const groupRows = buckets.get(macro) ?? [];
    return {
      id: macro,
      label: macro,
      count: groupRows.length,
      aumDelta: aumDeltaSum(groupRows),
    };
  });
  const deltas = macros.map((m) => m.aumDelta);
  const allDelta = aumDeltaSum(rows);
  return [
    {
      id: "all",
      label: "All",
      count: rows.length,
      aumDelta: allDelta,
      sharePct: rows.length > 0 ? 100 : null,
    },
    ...macros.map((m) => ({ ...m, sharePct: absSharePct(m.aumDelta, deltas) })),
  ];
}

function typeTabStats(
  opened: readonly TableRow[],
  closed: readonly TableRow[],
  changed: readonly TableRow[],
): SectionStat[] {
  const parts = [
    { id: "opened" as const, label: "Opened", rows: opened },
    { id: "closed" as const, label: "Closed", rows: closed },
    { id: "changed" as const, label: "Changed", rows: changed },
  ];
  const deltas = parts.map((p) => aumDeltaSum(p.rows));
  const allRows = [...opened, ...closed, ...changed];
  return [
    {
      id: "all",
      label: "All",
      count: allRows.length,
      aumDelta: aumDeltaSum(allRows),
      sharePct: allRows.length > 0 ? 100 : null,
    },
    ...parts.map((p, i) => ({
      id: p.id,
      label: p.label,
      count: p.rows.length,
      aumDelta: deltas[i] ?? 0,
      sharePct: absSharePct(deltas[i] ?? 0, deltas),
    })),
  ];
}

const CHANGE_SORT_ACCESSORS = {
  kind: (row: TableRow) => row.kind,
  macro: (row: TableRow) => row.macro,
  isin: (row: TableRow) => row.isin,
  name: (row: TableRow) => row.name,
  productType: (row: TableRow) => productTypeLabel(row),
  qtyDelta: (row: TableRow) => qtyDelta(row) ?? 0,
  mktDelta: (row: TableRow) => mktDelta(row) ?? 0,
  valueDelta: (row: TableRow) => row.valueDelta,
} as const;

function DeltaCell({
  delta,
  title,
  emptyFallback,
}: {
  delta: number | null;
  title: string;
  emptyFallback: string;
}) {
  if (delta == null) {
    return (
      <td className="px-3 py-2 text-right text-muted-foreground tabular-nums whitespace-nowrap">
        {emptyFallback}
      </td>
    );
  }
  return (
    <td className="px-3 py-2 text-right whitespace-nowrap" title={title || undefined}>
      <div
        className={cn(
          "font-medium tabular-nums",
          delta > 0
            ? "text-emerald-600 dark:text-emerald-400"
            : delta < 0
              ? "text-red-600 dark:text-red-400"
              : "text-foreground",
        )}
      >
        {formatSignedNum(delta)}
      </div>
      {title ? (
        <div className="text-[10px] tabular-nums text-muted-foreground">{title}</div>
      ) : null}
    </td>
  );
}

function ChangeTableBody({
  rows,
  showKind,
  onIsinClick,
}: {
  rows: readonly TableRow[];
  showKind?: boolean;
  onIsinClick?: (row: PortfolioAnalyticsDayChangeRow) => void;
}) {
  return (
    <>
      {rows.map((row, index) => {
        const qRaw = qtyDelta(row);
        const pRaw = mktDelta(row);
        const qD = qRaw != null && Math.abs(qRaw) >= 1e-8 ? qRaw : null;
        const pD = pRaw != null && Math.abs(pRaw) >= 1e-8 ? pRaw : null;
        const qtyFallback =
          row.qtyAfter != null
            ? formatNum(row.qtyAfter)
            : row.qtyBefore != null
              ? formatNum(row.qtyBefore)
              : "—";
        const mktFallback =
          row.priceAfter != null
            ? formatNum(row.priceAfter)
            : row.priceBefore != null
              ? formatNum(row.priceBefore)
              : "—";
        const isinClickable = Boolean(onIsinClick && canOpenIsinDrilldown(row));
        return (
          <tr
            key={`${row.kind}-${row.macro}-${row.isin}-${index}`}
            className={cn("border-border/60 border-t", index % 2 === 1 && "bg-muted/20")}
          >
            {showKind ? (
              <td className="px-3 py-2 text-muted-foreground whitespace-nowrap">{row.kind}</td>
            ) : null}
            <td className="px-3 py-2 font-mono text-xs tracking-tight whitespace-nowrap">
              {isinClickable ? (
                <button
                  type="button"
                  className="text-primary underline-offset-2 hover:underline"
                  title="Who holds this ISIN"
                  onClick={() => onIsinClick?.(row)}
                >
                  {row.isin}
                </button>
              ) : (
                row.isin || "—"
              )}
            </td>
            <td className="max-w-[16rem] truncate px-3 py-2 font-medium" title={row.name}>
              {row.name}
            </td>
            <td
              className="max-w-[10rem] truncate px-3 py-2 text-muted-foreground text-xs"
              title={
                productTypeLabel(row) !== "—" && row.fType?.trim()
                  ? `${productTypeLabel(row)} (${row.fType})`
                  : productTypeLabel(row)
              }
            >
              {productTypeLabel(row)}
            </td>
            <DeltaCell
              delta={qD}
              title={qD != null ? qtyTitle(row) : ""}
              emptyFallback={qtyFallback}
            />
            <DeltaCell
              delta={pD}
              title={pD != null ? mktTitle(row) : ""}
              emptyFallback={mktFallback}
            />
            <td
              className={cn(
                "px-3 py-2 text-right font-medium tabular-nums whitespace-nowrap",
                row.valueDelta > 0
                  ? "text-emerald-600 dark:text-emerald-400"
                  : row.valueDelta < 0
                    ? "text-red-600 dark:text-red-400"
                    : "text-foreground",
              )}
            >
              {formatSigned(row.valueDelta)}
            </td>
          </tr>
        );
      })}
    </>
  );
}

function ChangeTable({
  title,
  hint,
  rows,
  showKind,
  groupByMacro = true,
  emptyLabel = "No matching positions.",
  onIsinClick,
}: {
  title?: string;
  hint?: string;
  rows: TableRow[];
  showKind?: boolean;
  groupByMacro?: boolean;
  emptyLabel?: string | null;
  onIsinClick?: (row: PortfolioAnalyticsDayChangeRow) => void;
}) {
  const { rows: sortedRows, toggle, directionFor } = useTableSort(rows, CHANGE_SORT_ACCESSORS);
  const groups = React.useMemo(
    () => (groupByMacro ? groupRowsByMacro(sortedRows) : null),
    [groupByMacro, sortedRows],
  );

  if (rows.length === 0) {
    return emptyLabel ? (
      <p className="py-8 text-center text-muted-foreground text-sm">{emptyLabel}</p>
    ) : null;
  }

  const colSpan = (showKind ? 1 : 0) + 6;

  return (
    <div className="space-y-2">
      {title ? (
        <div>
          <p className="font-medium text-foreground text-sm">{title}</p>
          {hint ? <p className="text-muted-foreground text-xs">{hint}</p> : null}
        </div>
      ) : null}
      <StickyTableScroll className="max-h-[max(20rem,calc(100dvh-22rem))] rounded-2xl border border-border/60 bg-card shadow-sm">
        <table className="w-full min-w-[880px] border-collapse text-sm">
          <thead>
            <tr>
              {showKind ? (
                <SortableTh
                  label="Type"
                  direction={directionFor("kind")}
                  onSort={() => toggle("kind")}
                />
              ) : null}
              <SortableTh
                label="ISIN"
                direction={directionFor("isin")}
                onSort={() => toggle("isin")}
              />
              <SortableTh
                label="Name"
                direction={directionFor("name")}
                onSort={() => toggle("name")}
              />
              <SortableTh
                label="Product type"
                direction={directionFor("productType")}
                onSort={() => toggle("productType")}
              />
              <SortableTh
                label="Qty Δ"
                align="right"
                direction={directionFor("qtyDelta")}
                onSort={() => toggle("qtyDelta")}
              />
              <SortableTh
                label="Mkt Δ"
                align="right"
                direction={directionFor("mktDelta")}
                onSort={() => toggle("mktDelta")}
              />
              <SortableTh
                label="AUM Δ"
                align="right"
                direction={directionFor("valueDelta")}
                onSort={() => toggle("valueDelta")}
              />
            </tr>
          </thead>
          <tbody>
            {groups
              ? groups.map((group) => (
                  <React.Fragment key={group.macro}>
                    <tr className="border-border/60 border-t bg-muted/40">
                      <td colSpan={colSpan} className="px-3 py-2">
                        <div className="flex items-baseline justify-between gap-3">
                          <span className="font-medium text-foreground text-xs tracking-wide">
                            {group.macro}
                            <span className="ml-2 font-normal text-muted-foreground">
                              {group.rows.length}
                            </span>
                          </span>
                          <span
                            className={cn(
                              "font-medium text-xs tabular-nums",
                              deltaToneClass(group.aumDelta),
                            )}
                            title="Section AUM Δ · share of absolute movement"
                          >
                            {formatDeltaShare(group.aumDelta, group.sharePct)}
                          </span>
                        </div>
                      </td>
                    </tr>
                    <ChangeTableBody
                      rows={group.rows}
                      showKind={showKind}
                      onIsinClick={onIsinClick}
                    />
                  </React.Fragment>
                ))
              : (
                <ChangeTableBody
                  rows={sortedRows}
                  showKind={showKind}
                  onIsinClick={onIsinClick}
                />
              )}
          </tbody>
        </table>
      </StickyTableScroll>
    </div>
  );
}

function withKind(rows: PortfolioAnalyticsDayChangeRow[], kind: ChangeKind): TableRow[] {
  return rows.map((row) => ({ ...row, kind }));
}

export function PortfolioAnalyticsDayChangesSheet({
  tenant,
  date,
  previousDate,
  bankQuery,
  mode = "day",
  open,
  onOpenChange,
  initialMacro = null,
}: {
  tenant: string;
  date: string | null;
  previousDate: string | null;
  /** Chart bank filter query fragment (e.g. ids=1|2), without leading &. */
  bankQuery: string;
  mode?: "day" | "period";
  open: boolean;
  onOpenChange: (open: boolean) => void;
  /** Pre-select a macro tab (e.g. from Asset Allocation slice click). */
  initialMacro?: string | null;
}) {
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState<string | null>(null);
  const [opened, setOpened] = React.useState<PortfolioAnalyticsDayChangeRow[]>([]);
  const [closed, setClosed] = React.useState<PortfolioAnalyticsDayChangeRow[]>([]);
  const [qtyChanged, setQtyChanged] = React.useState<PortfolioAnalyticsDayChangeRow[]>([]);
  const [priceChanged, setPriceChanged] = React.useState<PortfolioAnalyticsDayChangeRow[]>([]);
  const [resolvedPrev, setResolvedPrev] = React.useState("");
  const [tab, setTab] = React.useState<TabId>("all");
  const [macroTab, setMacroTab] = React.useState("all");
  const [search, setSearch] = React.useState("");
  const [individualTarget, setIndividualTarget] =
    React.useState<ConsolidatedIndividualTarget | null>(null);

  React.useEffect(() => {
    if (!open) return;
    setTab("all");
    setMacroTab(initialMacro?.trim() || "all");
    setSearch("");
    setIndividualTarget(null);
  }, [open, date, initialMacro]);

  React.useEffect(() => {
    if (!open || !date) return;
    let cancelled = false;
    setLoading(true);
    setError(null);
    const params = new URLSearchParams();
    params.set("date", date);
    if (previousDate) params.set("prevDate", previousDate);
    if (bankQuery) {
      const extra = new URLSearchParams(bankQuery);
      extra.forEach((value, key) => params.append(key, value));
    }
    void fetchPortfolioAnalyticsDayChangesClient(tenant, params.toString())
      .then((data) => {
        if (cancelled) return;
        setOpened(data.opened);
        setClosed(data.closed);
        setQtyChanged(data.qtyChanged);
        setPriceChanged(data.priceChanged ?? []);
        setResolvedPrev(data.previousDate);
        setError(data.error);
      })
      .catch((err: unknown) => {
        if (cancelled) return;
        setError(err instanceof Error ? err.message : "Failed to load day changes");
        setOpened([]);
        setClosed([]);
        setQtyChanged([]);
        setPriceChanged([]);
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [open, date, previousDate, bankQuery, tenant]);

  const q = search.trim().toLowerCase();
  const filtered = React.useMemo(
    () => ({
      opened: opened.filter((row) => matchesSearch(row, q)),
      closed: closed.filter((row) => matchesSearch(row, q)),
      qty: qtyChanged.filter((row) => matchesSearch(row, q)),
      price: priceChanged.filter((row) => matchesSearch(row, q)),
    }),
    [opened, closed, qtyChanged, priceChanged, q],
  );

  const changedRows = React.useMemo(
    () => [...withKind(filtered.qty, "Qty"), ...withKind(filtered.price, "Price")],
    [filtered.qty, filtered.price],
  );

  const openedRows = React.useMemo(
    () => withKind(filtered.opened, "Opened"),
    [filtered.opened],
  );
  const closedRows = React.useMemo(
    () => withKind(filtered.closed, "Closed"),
    [filtered.closed],
  );

  const typeTabs = React.useMemo(
    () => typeTabStats(openedRows, closedRows, changedRows),
    [openedRows, closedRows, changedRows],
  );

  const typeRows = React.useMemo((): TableRow[] => {
    if (tab === "opened") return openedRows;
    if (tab === "closed") return closedRows;
    if (tab === "changed") return changedRows;
    return [...openedRows, ...closedRows, ...changedRows];
  }, [tab, openedRows, closedRows, changedRows]);

  const changeCount = typeTabs[0]?.count ?? 0;
  const totalAumDelta = typeTabs[0]?.aumDelta ?? 0;

  const macroTabs = React.useMemo(() => macroTabItems(typeRows), [typeRows]);

  React.useEffect(() => {
    if (macroTab === "all" || loading) return;
    const match = macroTabs.find(
      (item) =>
        item.count > 0 &&
        (item.id === macroTab || item.id.toLowerCase() === macroTab.toLowerCase()),
    );
    if (!match) {
      setMacroTab("all");
      return;
    }
    if (match.id !== macroTab) setMacroTab(match.id);
  }, [macroTab, macroTabs, loading]);

  const visibleRows = React.useMemo(
    () =>
      macroTab === "all" ? typeRows : typeRows.filter((row) => macroOf(row) === macroTab),
    [typeRows, macroTab],
  );

  const emptySource =
    !loading &&
    !error &&
    opened.length === 0 &&
    closed.length === 0 &&
    qtyChanged.length === 0 &&
    priceChanged.length === 0;

  const comparedTo = resolvedPrev || previousDate || "";

  /** Cards = section analytics only; tabs stay the filter. Total first. */
  const sectionAnalytics = React.useMemo((): SectionStat[] => {
    if (macroTab === "all") {
      const rest = macroTabs.filter((item) => item.id !== "all");
      const total = macroTabs.find((item) => item.id === "all");
      if (!total) return rest;
      // Portfolio total: % of |Δ| is always 100 for this scope.
      return [{ ...total, label: "Total" }, ...rest];
    }
    const inMacro = typeRows.filter((row) => macroOf(row) === macroTab);
    const parts = typeTabStats(
      inMacro.filter((row) => row.kind === "Opened"),
      inMacro.filter((row) => row.kind === "Closed"),
      inMacro.filter((row) => row.kind === "Qty" || row.kind === "Price"),
    );
    const rest = parts.filter((item) => item.id !== "all");
    // Total = this macro’s net Δ; % = share of portfolio |Δ| (from macro tab stats).
    const macroTotal = macroTabs.find((item) => item.id === macroTab);
    const total: SectionStat = {
      id: "total",
      label: "Total",
      count: inMacro.length,
      aumDelta: macroTotal?.aumDelta ?? aumDeltaSum(inMacro),
      sharePct: macroTotal?.sharePct ?? null,
    };
    return [total, ...rest];
  }, [macroTab, macroTabs, typeRows]);

  const tableMeta =
    tab === "opened"
      ? { title: "Opened", showKind: false }
      : tab === "closed"
        ? { title: "Closed", showKind: false }
        : tab === "changed"
          ? { title: "Changed", showKind: true }
          : { title: "All changes", showKind: true };

  return (
    <Sheet
      open={open}
      onOpenChange={(next) => {
        if (!next) setIndividualTarget(null);
        onOpenChange(next);
      }}
    >
      <SheetContent
        side="right"
        showCloseButton={false}
        className={cn(
          "flex h-dvh w-full flex-col gap-0 overflow-hidden bg-muted/30 p-0",
          "data-[side=right]:w-full data-[side=right]:sm:max-w-none",
          "data-[side=right]:sm:!w-[min(72rem,calc(100vw-1.5rem))]",
        )}
      >
        {individualTarget ? (
          <ConsolidatedIndividualDialog
            embedded
            open
            target={individualTarget}
            onBack={() => setIndividualTarget(null)}
            onOpenChange={(next) => {
              if (!next) {
                setIndividualTarget(null);
                onOpenChange(false);
              }
            }}
          />
        ) : (
          <>
            <div className="shrink-0 space-y-2 border-border/60 border-b bg-card/80 px-4 pt-4 pb-3 backdrop-blur-md">
              <p className="font-semibold text-[10px] text-muted-foreground tracking-widest uppercase">
                Analytics
              </p>
              <div className="flex items-center justify-between gap-2">
                <SheetTitle className="flex min-w-0 items-center gap-2 text-left font-semibold text-base tracking-tight">
                  <span className="flex size-8 shrink-0 items-center justify-center rounded-xl bg-primary text-primary-foreground shadow-md shadow-primary/25">
                    <ArrowLeftRight className="size-3.5" />
                  </span>
                  <span className="truncate">
                    {mode === "period"
                      ? "Period changes"
                      : date
                        ? formatDay(date)
                        : "Day changes"}
                  </span>
                </SheetTitle>
                <Button
                  type="button"
                  variant="ghost"
                  size="icon-sm"
                  className="rounded-lg"
                  onClick={() => onOpenChange(false)}
                  aria-label="Close"
                >
                  <X className="size-4" />
                </Button>
              </div>
              <SheetDescription className="sr-only">
                Snapshot position opens, closes, quantity and market price changes versus the prior
                OS as-of date.
              </SheetDescription>
            </div>

            <div className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto px-4 py-4">
              <section className="flex flex-col gap-3 rounded-2xl border border-border/60 bg-card p-3 shadow-sm">
                <div className="grid gap-2 sm:grid-cols-3">
                  <InfoChip
                    label={mode === "period" ? "Period end" : "As of"}
                    value={date ? formatDay(date) : "—"}
                  />
                  <InfoChip
                    label={mode === "period" ? "Period start" : "Compared to"}
                    value={comparedTo ? formatDay(comparedTo) : "—"}
                    detail={mode === "period" ? "First snapshot in range" : "Prior OS snapshot"}
                  />
                  <InfoChip
                    label="Net AUM Δ"
                    value={loading ? "…" : formatSigned(totalAumDelta)}
                    detail={
                      loading
                        ? undefined
                        : q
                          ? `${changeCount} matching · share of |Δ| below`
                          : `${changeCount} positions · share of |Δ| below`
                    }
                  />
                </div>

                <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                  <div
                    className="inline-flex w-fit max-w-full flex-wrap gap-0.5 rounded-lg border border-border/60 bg-muted/40 p-1"
                    role="tablist"
                    aria-label="Change type"
                  >
                    {typeTabs.map((item) => (
                      <button
                        key={item.id}
                        type="button"
                        role="tab"
                        aria-selected={tab === item.id}
                        className={segmentClass(tab === item.id)}
                        onClick={() => setTab(item.id as TabId)}
                      >
                        {item.label}
                        <span className="ml-1 tabular-nums text-muted-foreground">{item.count}</span>
                      </button>
                    ))}
                  </div>

                  <div className="relative w-full sm:max-w-xs">
                    <Search className="pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-muted-foreground" />
                    <Input
                      value={search}
                      onChange={(e) => setSearch(e.target.value)}
                      placeholder="Search ISIN, name, product type…"
                      className="h-8 bg-background pl-8 text-xs"
                      aria-label="Search changes"
                    />
                  </div>
                </div>

                {macroTabs.length > 1 ? (
                  <div
                    className="inline-flex max-w-full flex-wrap gap-0.5 rounded-lg border border-border/60 bg-muted/40 p-1"
                    role="tablist"
                    aria-label="Macro"
                  >
                    {macroTabs.map((item) => (
                      <button
                        key={item.id}
                        type="button"
                        role="tab"
                        aria-selected={macroTab === item.id}
                        className={segmentClass(macroTab === item.id)}
                        onClick={() => setMacroTab(item.id)}
                      >
                        {item.label}
                        <span className="ml-1 tabular-nums text-muted-foreground">{item.count}</span>
                      </button>
                    ))}
                  </div>
                ) : null}

                {!loading && sectionAnalytics.length > 0 ? (
                  <div
                    className="grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4"
                    aria-label="Section change analytics"
                  >
                    {sectionAnalytics.map((item) => (
                      <AnalyticsCard
                        key={item.id}
                        label={item.label}
                        count={item.count}
                        aumDelta={item.aumDelta}
                        sharePct={item.sharePct}
                      />
                    ))}
                  </div>
                ) : null}

              </section>

              {loading ? (
                <div className="flex items-center gap-2 py-10 text-muted-foreground text-sm">
                  <Loader2 className="size-4 animate-spin" />
                  Loading changes…
                </div>
              ) : null}

              {error ? <p className="text-destructive text-sm">{error}</p> : null}

              {emptySource ? (
                <p className="py-10 text-center text-muted-foreground text-sm">
                  No position opens, closes, quantity, or market price changes between these
                  snapshots.
                </p>
              ) : null}

              {!loading && !error && !emptySource ? (
                <ChangeTable
                  title={macroTab === "all" ? tableMeta.title : macroTab}
                  showKind={tableMeta.showKind}
                  groupByMacro={macroTab === "all"}
                  rows={visibleRows}
                  emptyLabel="No rows match this filter."
                  onIsinClick={(row) => {
                    if (!canOpenIsinDrilldown(row)) return;
                    setIndividualTarget(toIndividualTarget(row));
                  }}
                />
              ) : null}
            </div>
          </>
        )}
      </SheetContent>
    </Sheet>
  );
}
