"use client";

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

import {
  type ColumnDef,
  flexRender,
  getCoreRowModel,
  type PaginationState,
  type SortingState,
  useReactTable,
} from "@tanstack/react-table";
import { CircleHelp, RefreshCw } from "lucide-react";
import type { DateRange } from "react-day-picker";
import {
  CustomerFieldsButton,
  CustomerFieldsPanel,
} from "@/app/customer/_components/customer-fields-panel";
import { useCustomerPortalSession } from "@/app/customer/_components/customer-portal-session-context";
import type { CustomerTransactionModuleId } from "@/app/customer/_lib/customer-asset-modules";
import {
  type CustomerColumnPrefs,
  type CustomerGridColumn,
} from "@/app/customer/_lib/customer-grid-columns";
import { useCustomerTableColumnPrefs } from "@/app/customer/_lib/use-customer-table-column-prefs";
import { useCustomerTransactionDelete } from "@/app/customer/_lib/use-customer-transaction-delete";
import {
  NewTransactionButton,
  useTransactionFormEdit,
} from "@/components/form/transaction-workspace/list-table-form-actions";
import type { TransactionModuleFormConfig } from "@/components/form/transaction-workspace/types";
import { ErrorBanner } from "@/components/shared/error-banner";
import { Button } from "@/components/ui/button";
import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";

import type { TransactionTableRow } from "./customer-transaction-list-columns";
import {
  CustomerTransactionListFilterRow,
  renderCustomerAssetColumnFilter,
  type TransactionFilterOptions,
  type TransactionRangeFilters,
} from "./customer-transaction-list-filters";
import { CustomerTablePageNavigation, CustomerTableRecordsPerPage } from "./customer-table-pagination";
import { formatAmount, formatPlainAmount } from "@/lib/format/numbers";
import { cn } from "@/lib/utils";

export type TransactionDetailSheetProps<TRow> = {
  row: TRow | null;
  open: boolean;
  onOpenChange: (open: boolean) => void;
  onEdit: (row: TRow) => void;
  onDelete: (row: TRow) => void;
};

export type CustomerAssetServerListState<TRow> = {
  rows: TRow[];
  setRows: React.Dispatch<React.SetStateAction<TRow[]>>;
  totalCount: number;
  pageCount: number;
  isLoading: boolean;
  errorMessage: string | null;
  refresh: () => void;
  onRefreshClick?: () => void;
  canCreate: boolean;
  pagination: PaginationState;
  setPagination: React.Dispatch<React.SetStateAction<PaginationState>>;
  sorting: SortingState;
  setSorting: React.Dispatch<React.SetStateAction<SortingState>>;
  getFilterValue: (columnId: string) => string;
  onColumnFilterChange: (columnId: string, value: string | undefined) => void;
  placementDateRange: DateRange | undefined;
  onPlacementDateRangeChange: (range: DateRange | undefined) => void;
  filterOptions: TransactionFilterOptions;
  rangeFilters?: TransactionRangeFilters;
  onRangeFilterChange?: (
    field: keyof TransactionRangeFilters,
    value: { from?: string; to?: string },
  ) => void;
  showParentIsinFilter?: boolean;
  /** When false, hides the shared transaction filter row (e.g. cash-balance grid). */
  showFilterRow?: boolean;
  columnTotals?: Record<string, unknown> | null;
  /** When set, this currency is shown inline in the Amount total; all others go into a tooltip. */
  primaryCurrency?: string;
};

export type CustomerAssetColumnPrefsConfig = {
  catalog: CustomerGridColumn[];
  initialPrefs: CustomerColumnPrefs | null;
  savePrefs: (prefs: CustomerColumnPrefs) => Promise<void>;
  lockedKeys?: string[];
  /** Column keys that support a Fields-panel Total toggle (Accumulator / Structure). */
  totalKeys?: Set<string>;
};

type CustomerAssetServerListTableProps<TRow extends TransactionTableRow> = {
  title: string;
  emptyMessage: string;
  rowsPerPageId: string;
  moduleId?: CustomerTransactionModuleId;
  /** When set, picks the delete/update module per row (Deposit hub: Dc vs Cl). */
  resolveModuleId?: (row: TRow) => CustomerTransactionModuleId | null;
  /** When set, overrides formConfig.listPath for Edit navigation per row. */
  resolveEditListPath?: (row: TRow) => string | null;
  singularName: string;
  formConfig?: Pick<TransactionModuleFormConfig, "listPath" | "newButtonLabel">;
  editMode?: "form" | "placeholder";
  columnFactory: (handlers: {
    onView: (row: TRow) => void;
    onEdit: (row: TRow) => void;
  }) => ColumnDef<TRow>[];
  DetailSheet: React.ComponentType<TransactionDetailSheetProps<TRow>>;
  server: CustomerAssetServerListState<TRow>;
  /** Optional extra actions rendered in the toolbar, before the New button. */
  toolbarActions?: React.ReactNode;
  /** When false, hides the generic New transaction button (stock funds uses a custom create menu). */
  showNewButton?: boolean;
  /**
   * Optional custom filter row. When provided, replaces the shared stock-style
   * transaction filter row (used by cash-balance and similar grids).
   */
  filterRow?: React.ReactNode;
  /** When set, enables Fields panel + per-user column visibility/order. */
  columnPrefs?: CustomerAssetColumnPrefsConfig;
};

export function CustomerAssetServerListTable<TRow extends TransactionTableRow>({
  title,
  emptyMessage,
  rowsPerPageId,
  moduleId,
  resolveModuleId,
  resolveEditListPath,
  singularName,
  formConfig,
  editMode = formConfig ? "form" : "placeholder",
  columnFactory,
  DetailSheet,
  server,
  toolbarActions,
  showNewButton = true,
  filterRow,
  columnPrefs,
}: CustomerAssetServerListTableProps<TRow>) {
  const { canCreateTransactions } = useCustomerPortalSession();
  const [selectedRow, setSelectedRow] = React.useState<TRow | null>(null);
  const [detailOpen, setDetailOpen] = React.useState(false);
  const [fieldsOpen, setFieldsOpen] = React.useState(false);

  const handleView = React.useCallback((row: TRow) => {
    setSelectedRow(row);
    setDetailOpen(true);
  }, []);

  const navigateToEdit = useTransactionFormEdit(formConfig ?? { listPath: "" });

  const handleEdit = React.useCallback(
    (row: TRow) => {
      if (editMode !== "form") {
        return;
      }

      const closeDetail = () => {
        setDetailOpen(false);
        setSelectedRow(null);
      };

      const editListPath = resolveEditListPath?.(row);
      if (editListPath) {
        navigateToEdit(
          row,
          closeDetail,
          // Prefer per-row form module path (Deposit hub Dc/Cl).
          editListPath,
        );
        return;
      }

      if (formConfig) {
        navigateToEdit(row, closeDetail);
      }
    },
    [editMode, formConfig, navigateToEdit, resolveEditListPath],
  );

  const { deleteRow: handleDelete } = useCustomerTransactionDelete<TRow>({
    moduleId,
    resolveModuleId,
    singularName,
    onDeleted: (row) => {
      server.setRows((prev) => prev.filter((item) => item.id !== row.id));
      setDetailOpen(false);
      setSelectedRow(null);
    },
  });

  const columns = React.useMemo(
    () => columnFactory({ onView: handleView, onEdit: handleEdit }),
    [columnFactory, handleEdit, handleView],
  );

  const lockedKeys = columnPrefs?.lockedKeys ?? ["options"];
  const totalKeys = columnPrefs?.totalKeys;
  const hasTotals = Boolean(totalKeys && totalKeys.size > 0);

  const totalPrefsRef = React.useRef<CustomerColumnPrefs>({
    visibility: {},
    order: [],
  });
  const fieldPrefsRef = React.useRef<CustomerColumnPrefs>({
    visibility: {},
    order: [],
  });

  const saveFieldPrefs = React.useCallback(
    async (prefs: CustomerColumnPrefs) => {
      if (!columnPrefs) return;
      try {
        await columnPrefs.savePrefs(
          hasTotals
            ? {
                visibility: prefs.visibility,
                order: prefs.order,
                totalsVisibility: totalPrefsRef.current.visibility,
                totalsOrder: totalPrefsRef.current.order,
              }
            : prefs,
        );
      } catch (error) {
        toastApiError(error, "Failed to save column preferences.");
        throw error;
      }
    },
    [columnPrefs, hasTotals],
  );

  const saveTotalPrefs = React.useCallback(
    async (prefs: CustomerColumnPrefs) => {
      if (!columnPrefs) return;
      try {
        await columnPrefs.savePrefs({
          visibility: fieldPrefsRef.current.visibility,
          order: fieldPrefsRef.current.order,
          totalsVisibility: prefs.visibility,
          totalsOrder: prefs.order,
        });
      } catch (error) {
        toastApiError(error, "Failed to save column preferences.");
        throw error;
      }
    },
    [columnPrefs],
  );

  const {
    columnVisibility,
    columnOrder,
    onColumnVisibilityChange,
    onColumnOrderChange,
    hideAll,
    setColumnVisible,
    reorderShown,
  } = useCustomerTableColumnPrefs({
    catalog: columnPrefs?.catalog ?? [],
    initialPrefs: columnPrefs?.initialPrefs ?? null,
    lockedKeys,
    savePrefs: saveFieldPrefs,
  });

  const totalsCatalog = React.useMemo<CustomerGridColumn[]>(() => {
    if (!columnPrefs || !totalKeys || totalKeys.size === 0) return [];
    return columnPrefs.catalog
      .filter((column) => totalKeys.has(column.key))
      .map((column) => ({
        key: column.key,
        label: column.label,
        defaultVisible: true,
      }));
  }, [columnPrefs, totalKeys]);

  const totalInitialPrefs = React.useMemo<CustomerColumnPrefs | null>(
    () =>
      columnPrefs?.initialPrefs
        ? {
            visibility: columnPrefs.initialPrefs.totalsVisibility ?? {},
            order: columnPrefs.initialPrefs.totalsOrder ?? [],
          }
        : null,
    [columnPrefs?.initialPrefs],
  );

  const {
    columnVisibility: totalColumnVisibility,
    columnOrder: totalColumnOrder,
    onColumnVisibilityChange: onTotalColumnVisibilityChange,
  } = useCustomerTableColumnPrefs({
    catalog: totalsCatalog,
    initialPrefs: totalInitialPrefs,
    savePrefs: saveTotalPrefs,
  });

  React.useEffect(() => {
    totalPrefsRef.current = {
      visibility: totalColumnVisibility as Record<string, boolean>,
      order: totalColumnOrder,
    };
  }, [totalColumnOrder, totalColumnVisibility]);

  React.useEffect(() => {
    fieldPrefsRef.current = {
      visibility: columnVisibility as Record<string, boolean>,
      order: columnOrder,
    };
  }, [columnOrder, columnVisibility]);

  const table = useReactTable({
    data: server.rows,
    columns,
    rowCount: server.totalCount,
    pageCount: server.pageCount || undefined,
    manualPagination: true,
    manualSorting: true,
    autoResetPageIndex: false,
    state: {
      sorting: server.sorting,
      pagination: server.pagination,
      ...(columnPrefs
        ? {
            columnVisibility,
            columnOrder,
          }
        : {}),
    },
    onSortingChange: (updater) => {
      server.setSorting(updater);
      server.setPagination((current) => ({ ...current, pageIndex: 0 }));
    },
    onPaginationChange: server.setPagination,
    ...(columnPrefs
      ? {
          onColumnVisibilityChange,
          onColumnOrderChange,
        }
      : {}),
    getCoreRowModel: getCoreRowModel(),
  });

  const { pageIndex, pageSize } = table.getState().pagination;
  const start = server.totalCount === 0 ? 0 : pageIndex * pageSize + 1;
  const end = Math.min((pageIndex + 1) * pageSize, server.totalCount);
  const visibleLeafColumns = table.getVisibleLeafColumns();
  const useDynamicFilters = Boolean(columnPrefs) && filterRow === undefined && server.showFilterRow !== false;

  const selectedTotalColumns = React.useMemo(() => {
    if (!hasTotals || !totalKeys) return [];
    const totals = server.columnTotals ?? {};
    return totalColumnOrder
      .filter((key) => totalKeys.has(key) && totalColumnVisibility[key] !== false)
      .map((key) => {
        const column = visibleLeafColumns.find((entry) => entry.id === key);
        if (!column) return null;
        const catalogColumn = columnPrefs?.catalog.find((entry) => entry.key === key);
        const raw = totals[key];
        // amount_formatted may be a per-currency map { USD: n, KYD: n }
        const byCurrency =
          raw && typeof raw === "object" && !Array.isArray(raw)
            ? (raw as Record<string, number>)
            : null;
        const scalar =
          typeof raw === "number" && Number.isFinite(raw) ? (raw as number) : null;
        return {
          key,
          label: catalogColumn?.label ?? key,
          scalar,
          byCurrency,
        };
      })
      .filter(
        (
          entry,
        ): entry is {
          key: string;
          label: string;
          scalar: number | null;
          byCurrency: Record<string, number> | null;
        } => Boolean(entry),
      );
  }, [
    columnPrefs?.catalog,
    hasTotals,
    server.columnTotals,
    totalColumnOrder,
    totalColumnVisibility,
    totalKeys,
    visibleLeafColumns,
  ]);

  const firstTotalColumnIndex = React.useMemo(
    () => visibleLeafColumns.findIndex((column) => selectedTotalColumns.some((entry) => entry.key === column.id)),
    [selectedTotalColumns, visibleLeafColumns],
  );

  const showTotalsFooter =
    hasTotals &&
    !server.isLoading &&
    !server.errorMessage &&
    server.totalCount > 0 &&
    selectedTotalColumns.length > 0 &&
    firstTotalColumnIndex >= 0;

  return (
    <div className="flex min-w-0 flex-col gap-4">
      <ErrorBanner message={server.errorMessage} className="mb-0" />

      <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">{title}</h1>
          <p className="text-muted-foreground text-sm">
            Displaying {start}-{end} of {formatPlainAmount(server.totalCount)} results.
          </p>
        </div>

        <div className="flex w-full shrink-0 flex-wrap items-center justify-end gap-2 sm:ml-auto sm:w-auto">
          <CustomerTableRecordsPerPage table={table} rowsPerPageId={rowsPerPageId} />
          {toolbarActions}
          {columnPrefs ? <CustomerFieldsButton onClick={() => setFieldsOpen(true)} /> : null}
          {formConfig && canCreateTransactions && showNewButton ? (
            <NewTransactionButton config={formConfig} />
          ) : null}
          <Button
            variant="outline"
            size="icon-lg"
            className="h-9 w-9 shrink-0"
            onClick={() => (server.onRefreshClick ?? server.refresh)()}
            disabled={server.isLoading}
          >
            <RefreshCw className="size-4" />
            <span className="sr-only">Refresh</span>
          </Button>
        </div>
      </div>

      <div className="overflow-x-auto rounded-lg border bg-card">
        <Table>
          <TableHeader>
            <TableRow className="bg-muted/20 hover:bg-muted/20">
              {table.getHeaderGroups()[0]?.headers.map((header) => (
                <TableHead key={header.id} className="h-10 whitespace-nowrap px-2">
                  {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
                </TableHead>
              ))}
            </TableRow>
            {filterRow !== undefined ? (
              filterRow
            ) : server.showFilterRow === false ? null : useDynamicFilters ? (
              <TableRow className="bg-muted/10 hover:bg-muted/10">
                {visibleLeafColumns.map((column) => (
                  <TableHead key={`filter-${column.id}`} className="px-2 py-2">
                    {renderCustomerAssetColumnFilter({
                      columnId: column.id,
                      table,
                      filterOptions: server.filterOptions,
                      placementDateRange: server.placementDateRange,
                      onPlacementDateRangeChange: server.onPlacementDateRangeChange,
                      onColumnFilterChange: (columnId, value) => {
                        server.onColumnFilterChange(columnId, value);
                        server.setPagination((current) => ({ ...current, pageIndex: 0 }));
                      },
                      getFilterValue: server.getFilterValue,
                      serverMode: true,
                      rangeFilters: server.rangeFilters,
                      onRangeFilterChange: server.onRangeFilterChange,
                    })}
                  </TableHead>
                ))}
              </TableRow>
            ) : (
              <CustomerTransactionListFilterRow
                table={table}
                filterOptions={server.filterOptions}
                placementDateRange={server.placementDateRange}
                onPlacementDateRangeChange={server.onPlacementDateRangeChange}
                onColumnFilterChange={(columnId, value) => {
                  server.onColumnFilterChange(columnId, value);
                  server.setPagination((current) => ({ ...current, pageIndex: 0 }));
                }}
                getFilterValue={server.getFilterValue}
                serverMode
                rangeFilters={server.rangeFilters}
                onRangeFilterChange={server.onRangeFilterChange}
                showParentIsinFilter={server.showParentIsinFilter}
              />
            )}
          </TableHeader>
          <TableBody>
            {server.isLoading ? (
              <TableRow>
                <TableCell
                  colSpan={Math.max(visibleLeafColumns.length, 1)}
                  className="h-24 text-center text-muted-foreground text-sm"
                >
                  Loading...
                </TableCell>
              </TableRow>
            ) : table.getRowModel().rows.length ? (
              table.getRowModel().rows.map((row, index) => (
                <TableRow key={row.id} className={index % 2 === 0 ? "bg-muted/5" : undefined}>
                  {row.getVisibleCells().map((cell) => (
                    <TableCell key={cell.id} className="px-2 py-2 align-middle">
                      {flexRender(cell.column.columnDef.cell, cell.getContext())}
                    </TableCell>
                  ))}
                </TableRow>
              ))
            ) : (
              <TableRow>
                <TableCell colSpan={Math.max(visibleLeafColumns.length, 1)} className="h-24 text-center">
                  {emptyMessage}
                </TableCell>
              </TableRow>
            )}
          </TableBody>
          {showTotalsFooter ? (
            <TableFooter>
              <TableRow className="border-t-2 bg-muted/40 font-semibold hover:bg-muted/40">
                {firstTotalColumnIndex > 0 ? (
                  <TableCell colSpan={firstTotalColumnIndex} className="py-2 text-right text-xs">
                    Total
                  </TableCell>
                ) : null}
                {visibleLeafColumns.slice(Math.max(firstTotalColumnIndex, 0)).map((column) => {
                  const selectedTotal = selectedTotalColumns.find((entry) => entry.key === column.id);
                  return (
                    <TableCell
                      key={`total-${column.id}`}
                      className={cn(
                        "px-2 py-2 text-xs",
                        "text-right tabular-nums",
                      )}
                    >
                      {selectedTotal ? (
                        <div className="text-right">
                          <span className="block text-[11px] font-normal text-muted-foreground">
                            Total {selectedTotal.label}
                          </span>
                          {selectedTotal.byCurrency ? (
                            // Per-currency breakdown (Amount) — follows ConvertedTotalValueWithTooltip design
                            (() => {
                              const allEntries = Object.entries(selectedTotal.byCurrency);
                              // Named currencies only (exclude the no-currency sentinel) for inline primary
                              const namedEntries = allEntries.filter(([code]) => code !== "__none__");
                              const noneEntry = allEntries.find(([code]) => code === "__none__");

                              // Primary = filtered currency if set, else largest |value| among named
                              const candidateEntries = namedEntries.length > 0 ? namedEntries : allEntries;
                              const primaryCode =
                                server.primaryCurrency && server.primaryCurrency in selectedTotal.byCurrency
                                  ? server.primaryCurrency
                                  : candidateEntries.reduce(
                                      (best, [code, val]) =>
                                        Math.abs(val) > Math.abs(selectedTotal.byCurrency![best] ?? 0)
                                          ? code
                                          : best,
                                      candidateEntries[0]?.[0] ?? "",
                                    );
                              const primaryVal = selectedTotal.byCurrency[primaryCode] ?? 0;
                              const formatted = formatAmount(Math.abs(primaryVal));
                              const display = primaryVal < 0 ? `-${formatted}` : formatted;
                              // Show primary label: named currencies show code, no-currency shows "—"
                              const primaryLabel = primaryCode === "__none__" ? "—" : primaryCode;

                              // Only tooltip if there are more entries beyond primary
                              const hasMore = allEntries.length > 1;

                              const valueColour = (v: number) =>
                                v < 0 ? "text-red-600 dark:text-red-400" : v > 0 ? "text-green-600 dark:text-green-400" : "";
                              const tooltipColour = (v: number) =>
                                v < 0 ? "text-red-400" : v > 0 ? "text-green-400" : "";

                              const inlineSpan = (
                                <span className={cn("tabular-nums", valueColour(primaryVal))}>
                                  {primaryLabel} {display}
                                </span>
                              );

                              if (!hasMore) {
                                return inlineSpan;
                              }

                              return (
                                <Tooltip>
                                  <TooltipTrigger asChild>
                                    <button
                                      type="button"
                                      className="inline-flex items-center gap-1 text-inherit underline decoration-dotted underline-offset-2"
                                    >
                                      {inlineSpan}
                                      <CircleHelp className="size-3.5 text-muted-foreground" />
                                    </button>
                                  </TooltipTrigger>
                                  <TooltipContent side="top" align="end" className="max-w-[320px] text-xs leading-relaxed">
                                    <div className="space-y-1">
                                      <p className="font-medium">Amount by currency</p>
                                      <div className="grid grid-cols-[52px_1fr] gap-x-3 gap-y-0.5 tabular-nums">
                                        {namedEntries.map(([code, val]) => (
                                          <React.Fragment key={code}>
                                            <span className="font-semibold">{code}</span>
                                            <span className={cn(tooltipColour(val))}>{formatAmount(val)}</span>
                                          </React.Fragment>
                                        ))}
                                        {noneEntry ? (
                                          <React.Fragment key="__none__">
                                            <span className="font-semibold text-muted-foreground">—</span>
                                            <span className={cn(tooltipColour(noneEntry[1]))}>{formatAmount(noneEntry[1])}</span>
                                          </React.Fragment>
                                        ) : null}
                                      </div>
                                    </div>
                                  </TooltipContent>
                                </Tooltip>
                              );
                            })()
                          ) : (
                            // Scalar (Quantity)
                            <span>
                              {selectedTotal.scalar !== null && Number.isFinite(selectedTotal.scalar)
                                ? selectedTotal.scalar.toFixed(2)
                                : "—"}
                            </span>
                          )}
                        </div>
                      ) : null}
                    </TableCell>
                  );
                })}
              </TableRow>
            </TableFooter>
          ) : null}
        </Table>
      </div>

      <div className="mt-4 border-t pt-4">
        <CustomerTablePageNavigation table={table} />
      </div>

      <DetailSheet
        row={selectedRow}
        open={detailOpen}
        onOpenChange={(open) => {
          setDetailOpen(open);
          if (!open) setSelectedRow(null);
        }}
        onEdit={handleEdit}
        onDelete={handleDelete}
      />

      {columnPrefs ? (
        <CustomerFieldsPanel
          open={fieldsOpen}
          onOpenChange={setFieldsOpen}
          catalog={columnPrefs.catalog}
          visibility={columnVisibility as Record<string, boolean>}
          order={columnOrder}
          lockedKeys={lockedKeys}
          title="Fields"
          description={
            hasTotals
              ? "Choose which columns to show in the table and drag to reorder. For columns that support totals, use the Column and Total switches separately on the same row."
              : undefined
          }
          onToggle={setColumnVisible}
          onReorder={reorderShown}
          onHideAll={hideAll}
          totalKeys={hasTotals ? totalKeys : undefined}
          totalVisibility={hasTotals ? (totalColumnVisibility as Record<string, boolean>) : undefined}
          onTotalToggle={
            hasTotals
              ? (key, visible) => onTotalColumnVisibilityChange((prev) => ({ ...prev, [key]: visible }))
              : undefined
          }
        />
      ) : null}
    </div>
  );
}
