import { computed, type Signal } from '@angular/core';
import { Operator } from '@aurorajs.dev/core-common';
import type { ColumnPinningState, Row } from '@tanstack/angular-table';
import type { Observable } from 'rxjs';
import { buildFilterWhere } from '../../components/data-table/lib/build-filter-where';
import { buildSearchWhere } from '../../components/data-table/lib/build-search-where';
import { buildSortOrder } from '../../components/data-table/lib/build-sort-order';
import { derivePinningState } from '../../components/data-table/lib/column-helpers';
import {
  DEFAULT_PAGE_SIZE,
  type ColumnFilter,
  type DataTableColumnDef,
  type DataTableData,
  type ServerTableState,
} from '../../components/data-table/types';
import { useTableColumnOrder } from '../atoms/data-table/use-table-column-order';
import { useTableColumnPinning } from '../atoms/data-table/use-table-column-pinning';
import { useTableColumnVisibility } from '../atoms/data-table/use-table-column-visibility';
import { useTableData } from '../atoms/data-table/use-table-data';
import { useTableFilters } from '../atoms/data-table/use-table-filters';
import { useTablePagination } from '../atoms/data-table/use-table-pagination';
import { useTableSearch } from '../atoms/data-table/use-table-search';
import { useTableSelection } from '../atoms/data-table/use-table-selection';
import { useTableSort } from '../atoms/data-table/use-table-sort';
import { useDataTable, type DataTableResult } from './use-data-table';

export interface PaginatedDataTableConfig<T> {
  readonly paginate: (input: {
    query: Record<string, unknown>;
    constraint?: Record<string, unknown>;
  }) => Observable<DataTableData<T>>;
  readonly columns: DataTableColumnDef<T>[] | (() => DataTableColumnDef<T>[]);
  readonly searchableColumnIds?: string[];
  readonly defaultPageSize?: number;
  readonly constraint?: () => Record<string, unknown> | null | undefined;
  readonly relationIncludes?: () => unknown[];
  readonly getRowId?: (row: T, index: number) => string;
  readonly enableRowSelection?: boolean | ((row: Row<T>) => boolean);
}

export interface PaginatedDataTable<T> extends DataTableResult<T> {
  readonly data: Signal<DataTableData<T>>;
  readonly loading: Signal<boolean>;
  readonly state: Signal<ServerTableState>;
  readonly search: ReturnType<typeof useTableSearch>;
  readonly filters: ReturnType<typeof useTableFilters>;
  refetch(): Promise<void>;
  seed(d: DataTableData<T>): void;
  setSearch(s: string): void;
  setFilters(f: ColumnFilter[]): void;
}

/**
 * Canonical preset for server-paginated tables. Composes:
 *
 *  - Atoms: `useTableSearch`, `useTableSort`, `useTablePagination`,
 *    `useTableFilters`, `useTableSelection`, `useTableColumnVisibility`,
 *    `useTableColumnOrder`, `useTableData`.
 *  - Binding: `useDataTable` (preset that wires atoms ↔ TanStack Table).
 *
 * Builds the query from the atoms via the existing `data-table/lib` helpers
 * (`buildSortOrder`, `buildSearchWhere`, `buildFilterWhere`). On every
 * change to sort / pagination / search / filters, refetch is triggered. On
 * search / filter changes, pagination is reset to `pageIndex: 0` before
 * refetching.
 *
 * Designed to replace the inline boilerplate in standard list components
 * (e.g. `tag-list.component.ts`) and to be consumed by `grid-pick-dialog`
 * and the `grid-select-multiple-elements` manager outer table.
 */
export function usePaginatedDataTable<T>(
  config: PaginatedDataTableConfig<T>,
): PaginatedDataTable<T> {
  const search = useTableSearch();
  const sort = useTableSort();
  const pagination = useTablePagination(config.defaultPageSize ?? DEFAULT_PAGE_SIZE);
  const filters = useTableFilters();
  const selection = useTableSelection();
  const columnVisibility = useTableColumnVisibility();
  const columnOrder = useTableColumnOrder();
  // Seed pinning from declared `sticky`. Wrapped in try/catch because
  // `config.columns` may be a reactive function whose required inputs
  // aren't set at construction time (component tests). In that case we
  // start empty and the column-toggle (or external setter) applies the
  // right state when columns become available.
  let initialPinning: ColumnPinningState = { left: [], right: [] };
  try {
    const initialColumns = typeof config.columns === 'function' ? config.columns() : config.columns;
    initialPinning = derivePinningState(initialColumns);
  } catch {
    /* leave empty */
  }
  const columnPinning = useTableColumnPinning(initialPinning);
  const tableData = useTableData<T>({ fetch: config.paginate });

  function buildFetchInput(): {
    query: Record<string, unknown>;
    constraint?: Record<string, unknown>;
  } {
    const order = buildSortOrder(sort.state());
    const offset = pagination.offset();
    const limit = pagination.limit();
    const searchWhere = buildSearchWhere(search.value(), config.searchableColumnIds ?? []);
    const filterWhere = buildFilterWhere(filters.value());
    const userWhere =
      searchWhere && filterWhere
        ? { [Operator.and]: [searchWhere, filterWhere] }
        : (searchWhere ?? filterWhere ?? null);
    const constraintWhere = config.constraint?.() ?? null;
    const where =
      constraintWhere && userWhere
        ? { [Operator.and]: [constraintWhere, userWhere] }
        : (constraintWhere ?? userWhere);
    const includes = config.relationIncludes?.() ?? [];
    const include = includes.length > 0 ? includes : undefined;
    const query: Record<string, unknown> = { where, order, offset, limit, include };
    const constraint =
      where || include ? ({ where, include } as Record<string, unknown>) : undefined;
    return { query, constraint };
  }

  async function refetch(): Promise<void> {
    await tableData.refetch(buildFetchInput());
  }

  function resetPageIndex(): void {
    const current = pagination.state();
    pagination.set({ pageIndex: 0, pageSize: current.pageSize });
  }

  function setSearch(s: string): void {
    search.set(s);
    resetPageIndex();
    void refetch();
  }

  function setFilters(f: ColumnFilter[]): void {
    filters.set(f);
    resetPageIndex();
    void refetch();
  }

  const binding = useDataTable<T>({
    data: () => tableData.data().rows,
    columns: config.columns,
    totalRows: () => tableData.data().total,
    pageSize: config.defaultPageSize,
    manualSorting: true,
    manualPagination: true,
    getRowId: config.getRowId,
    enableRowSelection: config.enableRowSelection,
    sort,
    pagination,
    columnVisibility,
    columnOrder,
    columnPinning,
    selection,
    onSortingChange: () => {
      resetPageIndex();
      void refetch();
    },
    onPaginationChange: () => {
      void refetch();
    },
  });

  const state = computed<ServerTableState>(() => ({
    sorting: sort.state(),
    pagination: pagination.state(),
    columnFilters: filters.value(),
    search: search.value(),
  }));

  return {
    ...binding,
    data: tableData.data,
    loading: tableData.loading,
    state,
    search,
    filters,
    refetch,
    seed: tableData.seed,
    setSearch,
    setFilters,
  };
}
