"use client";

import * as React from "react";

import type { DateRange } from "react-day-picker";
import type { ColumnDef, SortingState } from "@tanstack/react-table";

import { CustomerAssetServerListTable } from "@/app/customer/_components/customer-asset-server-list-table";
import type { TransactionDetailSheetProps } from "@/app/customer/_components/customer-asset-server-list-table";
import type {
  TransactionFilterOptions,
  TransactionRangeFilters,
} from "@/app/customer/_components/customer-transaction-list-filters";
import { toServerTransactionFilterOptions } from "@/app/customer/_lib/customer-asset-filter-options";
import {
  defaultOrderFromGridColumns,
  defaultVisibilityFromGridColumns,
  type CustomerColumnPrefs,
  type CustomerGridColumn,
} from "@/app/customer/_lib/customer-grid-columns";
import type { CustomerTransactionModuleId } from "@/app/customer/_lib/customer-asset-modules";
import { useCustomerAssetListTableState } from "@/app/customer/_lib/use-customer-asset-list-table-state";
import { useCustomerModuleListPath } from "@/components/form/transaction-workspace/list-table-form-actions";
import type { TransactionModuleFormConfig } from "@/components/form/transaction-workspace/types";
import type { StockPagePermissions } from "@/app/customer/_lib/customer-api-permissions";
import type { TransactionTableRow } from "@/app/customer/_components/customer-transaction-list-columns";

type FilterSelectOption = { value: string; label: string };

type StandardAssetListHookResult<TRow> = {
  rows: TRow[];
  setRows: React.Dispatch<React.SetStateAction<TRow[]>>;
  totalCount: number;
  pageCount: number;
  permissions: StockPagePermissions;
  gridColumns: CustomerGridColumn[];
  columnPrefs: CustomerColumnPrefs | null;
  typeOptions?: FilterSelectOption[];
  executionTypeOptions?: FilterSelectOption[];
  transactionOptions?: FilterSelectOption[];
  bankOptions?: FilterSelectOption[];
  /** Pre-built filter options (skips toServerTransactionFilterOptions). */
  filterOptions?: TransactionFilterOptions;
  isLoading: boolean;
  errorMessage: string | null;
  refresh: () => void;
};

type StandardAssetListHookArgs<TFilters> = {
  pagination: { pageIndex: number; pageSize: number };
  sorting: SortingState;
  filters: TFilters;
};

export type StandardAssetCreateColumnsOptions = {
  priceDecimals: number;
  quantityDecimals: number;
  listPath: string;
  pagePermissions: StockPagePermissions;
};

/** Handlers a custom `filterRow` needs; mirrors what the shared filter row receives. */
export type StandardAssetFilterRowContext<TFilters> = {
  filters: TFilters;
  filterOptions: TransactionFilterOptions;
  placementDateRange: DateRange | undefined;
  onPlacementDateRangeChange: (range: DateRange | undefined) => void;
  getFilterValue: (columnId: string) => string;
  onColumnFilterChange: (columnId: string, value: string | undefined) => void;
  rangeFilters: TransactionRangeFilters;
  onRangeFilterChange: (
    field: keyof TransactionRangeFilters,
    value: { from?: string; to?: string },
  ) => void;
};

export type CustomerStandardAssetListTableConfig<
  TRow extends TransactionTableRow,
  TFilters extends {
    placementDateFrom?: string;
    placementDateTo?: string;
  },
  TDetailSheet extends React.ComponentType<TransactionDetailSheetProps<TRow>>,
> = {
  title: string;
  emptyMessage: string;
  rowsPerPageId: string;
  /** Omit on hub lists that resolve the module per row via `resolveModuleId`. */
  moduleId?: CustomerTransactionModuleId;
  /** Picks the delete/update module per row (Deposit hub: fixed-deposit vs call-deposit). */
  resolveModuleId?: (row: TRow) => CustomerTransactionModuleId | null;
  /** Overrides `formConfig.listPath` for Edit navigation per row. */
  resolveEditListPath?: (row: TRow) => string | null;
  singularName: string;
  /**
   * Omit on lists with no create/edit form of their own (the derivatives hubs).
   * Leaving it unset keeps `editMode` at "placeholder", so the shared Edit
   * handler stays inert and `wrapOnEdit` owns navigation entirely.
   */
  formConfig?: Pick<TransactionModuleFormConfig, "listPath" | "newButtonLabel">;
  /** Module list path when `formConfig` is omitted. Defaults to `formConfig.listPath`. */
  listPath?: string;
  editMode?: "form" | "placeholder";
  optionsColumnId: string;
  /** Column keys locked in the Fields panel. Defaults to `[optionsColumnId]`. */
  lockedKeys?: string[];
  /** Replaces the shared transaction filter row (deposit/fixed-deposit use a custom one). */
  filterRow?:
    | React.ReactNode
    | ((ctx: StandardAssetFilterRowContext<TFilters>) => React.ReactNode);
  /** When false, renders no filter row at all. */
  showFilterRow?: boolean;
  fallbackColumns: CustomerGridColumn[];
  defaultPageSize: number;
  defaultSorting: SortingState;
  emptyFilters: TFilters;
  priceDecimals?: number;
  quantityDecimals?: number;
  showParentIsinFilter?: boolean;
  showNewButton?: boolean;
  resetColumnPrefsOnRefresh?: boolean;
  toolbarActions?:
    | React.ReactNode
    | ((ctx: {
        listPath: string;
        permissions: StockPagePermissions;
        filters: TFilters;
        setFilters: React.Dispatch<React.SetStateAction<TFilters>>;
        setPagination: React.Dispatch<React.SetStateAction<{ pageIndex: number; pageSize: number }>>;
        filterOptions: TransactionFilterOptions;
      }) => React.ReactNode);
  resolveCanCreate?: (permissions: StockPagePermissions) => boolean;
  useList: (args: StandardAssetListHookArgs<TFilters>) => StandardAssetListHookResult<TRow>;
  saveColumnPrefs: (prefs: CustomerColumnPrefs) => Promise<CustomerColumnPrefs>;
  parsePageState: (searchParams: URLSearchParams) => {
    pagination: { pageIndex: number; pageSize: number };
    sorting: SortingState;
    filters: TFilters;
    placementDateRange?: DateRange;
  };
  buildPageSearchParams: (args: {
    pagination: { pageIndex: number; pageSize: number };
    sorting: SortingState;
    filters: TFilters;
  }) => URLSearchParams;
  getFilterColumnValue: (filters: TFilters, columnId: string) => string;
  setFilterColumnValue: (filters: TFilters, columnId: string, value?: string) => TFilters;
  placementDateRangeToFilters: (range: DateRange | undefined) => Partial<TFilters>;
  getRangeFilters?: (filters: TFilters) => TransactionRangeFilters;
  applyRangeFilter?: (
    filters: TFilters,
    field: keyof TransactionRangeFilters,
    value: { from?: string; to?: string },
  ) => TFilters;
  createColumns: (
    catalog: CustomerGridColumn[],
    onView: (row: TRow) => void,
    onEdit: (row: TRow) => void,
    options: StandardAssetCreateColumnsOptions,
  ) => ColumnDef<TRow>[];
  /**
   * Optional edit interceptor (e.g. commodity/mixed-funds child deep-links).
   * Call `defaultOnEdit(row)` to fall back to the standard edit navigation.
   */
  wrapOnEdit?: (
    row: TRow,
    defaultOnEdit: (row: TRow) => void,
    ctx: { listPath: string; router: { push: (href: string) => void } },
  ) => void;
  DetailSheet: TDetailSheet;
};

function defaultGetRangeFilters(
  filters: {
    price?: { from?: string; to?: string };
    quantity?: { from?: string; to?: string };
    amount?: { from?: string; to?: string };
  },
): TransactionRangeFilters {
  return {
    price: filters.price,
    quantity: filters.quantity,
    amount: filters.amount,
  };
}

function defaultApplyRangeFilter<TFilters extends object>(
  filters: TFilters,
  field: keyof TransactionRangeFilters,
  value: { from?: string; to?: string },
): TFilters {
  if (field !== "price" && field !== "quantity" && field !== "amount") {
    return filters;
  }
  return { ...filters, [field]: value };
}

export function CustomerStandardAssetListTable<
  TRow extends TransactionTableRow,
  TFilters extends {
    placementDateFrom?: string;
    placementDateTo?: string;
  },
  TDetailSheet extends React.ComponentType<TransactionDetailSheetProps<TRow>>,
>(config: CustomerStandardAssetListTableConfig<TRow, TFilters, TDetailSheet>) {
  const listPath = useCustomerModuleListPath(config.listPath ?? config.formConfig?.listPath ?? "");
  const {
    pagination,
    setPagination,
    sorting,
    setSorting,
    filters,
    setFilters,
    placementDateRange,
    setPlacementDateRange,
    lastSyncedSearchParamsRef,
    pathname,
    router,
  } = useCustomerAssetListTableState<TFilters>({
    parsePageState: config.parsePageState,
    buildPageSearchParams: config.buildPageSearchParams,
  });

  const {
    rows,
    setRows,
    totalCount,
    pageCount,
    permissions,
    gridColumns,
    columnPrefs,
    typeOptions,
    executionTypeOptions,
    transactionOptions,
    bankOptions,
    filterOptions: listFilterOptions,
    isLoading,
    errorMessage,
    refresh,
  } = config.useList({ pagination, sorting, filters });

  const filterOptions = React.useMemo(() => {
    if (listFilterOptions) return listFilterOptions;
    return toServerTransactionFilterOptions({
      banks: bankOptions ?? [],
      types: typeOptions ?? [],
      executionTypes: executionTypeOptions ?? [],
      transactions: transactionOptions ?? [],
    });
  }, [bankOptions, executionTypeOptions, listFilterOptions, transactionOptions, typeOptions]);

  const catalog = React.useMemo(() => {
    const source = gridColumns.length ? gridColumns : config.fallbackColumns;
    const allowed = new Set(config.fallbackColumns.map((column) => column.key));
    return source.filter((column) => allowed.has(column.key));
  }, [config.fallbackColumns, gridColumns]);

  // Memoised: a fresh array literal here re-runs mergeColumnPrefs and the
  // hydration JSON.stringify in useCustomerTableColumnPrefs on every render.
  const lockedKeys = React.useMemo(
    () => config.lockedKeys ?? [config.optionsColumnId],
    [config.lockedKeys, config.optionsColumnId],
  );

  const columnFactory = React.useCallback(
    ({ onView, onEdit }: { onView: (row: TRow) => void; onEdit: (row: TRow) => void }) => {
      const resolvedOnEdit = config.wrapOnEdit
        ? (row: TRow) => config.wrapOnEdit!(row, onEdit, { listPath, router })
        : onEdit;
      return config.createColumns(catalog, onView, resolvedOnEdit, {
        priceDecimals: config.priceDecimals ?? 3,
        quantityDecimals: config.quantityDecimals ?? 2,
        listPath,
        pagePermissions: permissions,
      });
    },
    [catalog, config, listPath, permissions, router],
  );

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

  const buildDefaultColumnPrefs = React.useCallback((): CustomerColumnPrefs => {
    const visibility = defaultVisibilityFromGridColumns(catalog);
    visibility[config.optionsColumnId] = true;
    const order = [...defaultOrderFromGridColumns(catalog)];
    if (!order.includes(config.optionsColumnId)) {
      order.push(config.optionsColumnId);
    }
    return { visibility, order };
  }, [catalog, config.optionsColumnId]);

  const handleRefreshReset = React.useCallback(() => {
    lastSyncedSearchParamsRef.current = "";
    setFilters(config.emptyFilters);
    setPlacementDateRange(undefined);
    setPagination({ pageIndex: 0, pageSize: config.defaultPageSize });
    setSorting(config.defaultSorting);
    router.replace(pathname, { scroll: false });
    if (config.resetColumnPrefsOnRefresh) {
      void config.saveColumnPrefs(buildDefaultColumnPrefs()).catch(() => {
        // Keep local reset even if prefs save fails.
      });
    }
    refresh();
  }, [
    buildDefaultColumnPrefs,
    config,
    pathname,
    refresh,
    router,
    setFilters,
    setPagination,
    setPlacementDateRange,
    setSorting,
  ]);

  const canCreate = config.resolveCanCreate
    ? config.resolveCanCreate(permissions)
    : permissions.create;

  const resolvedToolbarActions =
    typeof config.toolbarActions === "function"
      ? config.toolbarActions({
          listPath,
          permissions,
          filters,
          setFilters,
          setPagination,
          filterOptions,
        })
      : config.toolbarActions;

  const getRangeFilters =
    config.getRangeFilters ??
    ((current) => defaultGetRangeFilters(current as Parameters<typeof defaultGetRangeFilters>[0]));
  const applyRangeFilter = config.applyRangeFilter ?? defaultApplyRangeFilter;

  // Hoisted out of the `server` memo so a custom `filterRow` drives the exact
  // same handlers as the shared one — otherwise the two can diverge silently.
  const getFilterValue = React.useCallback(
    (columnId: string) => config.getFilterColumnValue(filters, columnId),
    [config, filters],
  );

  const onColumnFilterChange = React.useCallback(
    (columnId: string, value: string | undefined) => {
      setFilters((current) => config.setFilterColumnValue(current, columnId, value));
      setPagination((current) => ({ ...current, pageIndex: 0 }));
    },
    [config, setFilters, setPagination],
  );

  const onPlacementDateRangeChange = React.useCallback(
    (range: DateRange | undefined) => {
      setPlacementDateRange(range);
      setFilters((current) => ({
        ...current,
        ...config.placementDateRangeToFilters(range),
      }));
      setPagination((current) => ({ ...current, pageIndex: 0 }));
    },
    [config, setFilters, setPagination, setPlacementDateRange],
  );

  const onRangeFilterChange = React.useCallback(
    (field: keyof TransactionRangeFilters, value: { from?: string; to?: string }) => {
      setFilters((current) => applyRangeFilter(current, field, value));
      setPagination((current) => ({ ...current, pageIndex: 0 }));
    },
    [applyRangeFilter, setFilters, setPagination],
  );

  const rangeFilters = getRangeFilters(filters);

  const server = React.useMemo(
    () => ({
      rows,
      setRows,
      totalCount,
      pageCount,
      isLoading,
      errorMessage,
      refresh,
      onRefreshClick: handleRefreshReset,
      canCreate,
      pagination,
      setPagination,
      sorting,
      setSorting,
      getFilterValue,
      onColumnFilterChange,
      placementDateRange,
      onPlacementDateRangeChange,
      filterOptions,
      rangeFilters,
      onRangeFilterChange,
      showParentIsinFilter: config.showParentIsinFilter ?? false,
      showFilterRow: config.showFilterRow,
    }),
    [
      canCreate,
      config.showFilterRow,
      config.showParentIsinFilter,
      errorMessage,
      filterOptions,
      getFilterValue,
      handleRefreshReset,
      isLoading,
      onColumnFilterChange,
      onPlacementDateRangeChange,
      onRangeFilterChange,
      pageCount,
      pagination,
      placementDateRange,
      rangeFilters,
      refresh,
      rows,
      setPagination,
      setRows,
      setSorting,
      sorting,
      totalCount,
    ],
  );

  const resolvedFilterRow =
    typeof config.filterRow === "function"
      ? (config.filterRow as (ctx: StandardAssetFilterRowContext<TFilters>) => React.ReactNode)({
          filters,
          filterOptions,
          placementDateRange,
          onPlacementDateRangeChange,
          getFilterValue,
          onColumnFilterChange,
          rangeFilters,
          onRangeFilterChange,
        })
      : config.filterRow;

  return (
    <CustomerAssetServerListTable
      title={config.title}
      emptyMessage={config.emptyMessage}
      rowsPerPageId={config.rowsPerPageId}
      moduleId={config.moduleId}
      resolveModuleId={config.resolveModuleId}
      resolveEditListPath={config.resolveEditListPath}
      singularName={config.singularName}
      formConfig={config.formConfig}
      editMode={config.editMode}
      showNewButton={config.showNewButton}
      toolbarActions={resolvedToolbarActions}
      filterRow={resolvedFilterRow}
      columnFactory={columnFactory}
      DetailSheet={config.DetailSheet}
      server={server}
      columnPrefs={{
        catalog,
        initialPrefs: columnPrefs,
        savePrefs,
        lockedKeys: lockedKeys,
      }}
    />
  );
}
