import type { ColumnDef, PaginationState, SortingState } from '@tanstack/angular-table';
import type { ValueOf } from '../../types';

export const DEFAULT_PAGE_SIZE = 10;

export const ColumnDataType = {
  ACTIONS: 'ACTIONS',
  ARRAY: 'ARRAY',
  BOOLEAN: 'BOOLEAN',
  CHECKBOX: 'CHECKBOX',
  DATE: 'DATE',
  DRAG_AND_DROP: 'DRAG_AND_DROP',
  ENUM: 'ENUM',
  JSONB: 'JSONB',
  NUMBER: 'NUMBER',
  STRING: 'STRING',
  TIMESTAMP: 'TIMESTAMP',
  TRANSLATIONS_MENU: 'TRANSLATIONS_MENU',
  UUID: 'UUID',
} as const;

export type ColumnDataType = ValueOf<typeof ColumnDataType>;

export const StickySide = {
  LEFT: 'left',
  RIGHT: 'right',
} as const;

export type StickySide = ValueOf<typeof StickySide>;

/**
 * Recursive descriptor of a column's association chain. Mirrors the shape of
 * Sequelize's `include` so a list of `ColumnRelation` can be passed straight
 * to a paginate query. For deep chains (e.g. token → account → user), nest:
 *
 *   { association: 'account', include: [{ association: 'user' }] }
 *
 * The `association` strings MUST match the Sequelize association names on
 * the backend model.
 */
export interface ColumnRelation {
  association: string;
  include?: ColumnRelation[];
}

export type DataTableColumnDef<T> = ColumnDef<T> & {
  readonly label?: string | (() => string);
  readonly sortable?: boolean;
  readonly filterable?: boolean;
  readonly exportable?: boolean;
  readonly searchable?: boolean;
  /**
   * Marks the column as referencing a field from an associated entity (e.g.
   * `'boundedContext.name'` for one hop, `'account.user.name'` for two).
   * Used by `getRelationIncludes` to derive a merged `include` tree for the
   * paginate query — required for Sequelize to resolve `$a.b.c$` predicates
   * and `[a, b, c, dir]` order entries.
   */
  readonly relation?: ColumnRelation;
  /**
   * Anchors the column to the left or right edge of the scroll wrapper so it
   * stays visible while non-sticky columns scroll horizontally. Multiple
   * sticky columns on the same edge stack with cumulative offsets computed
   * from their `getSize()` values. Sticky columns SHOULD be rigid (declare
   * only `size`, no `minSize`) so the offsets remain accurate when the
   * viewport is narrower than the table's `min-width`.
   */
  readonly sticky?: StickySide;
};

/**
 * TanStack Table merges every `columnDef` with internal defaults
 * (`size: 150`, `minSize: 20`, `maxSize: Number.MAX_SAFE_INTEGER`). That means
 * `colDef.minSize` is NEVER `undefined` at runtime — it is always at least
 * `20`. To distinguish "developer-declared minSize" from "TanStack default",
 * the helpers in this module treat `minSize > TANSTACK_DEFAULT_MIN_SIZE` as
 * an explicit declaration. No real-world elastic column would declare a
 * minimum of `20px` (it is too small to be meaningful), so the threshold is
 * safe.
 */
export const TANSTACK_DEFAULT_MIN_SIZE = 20;

/**
 * Distinguishes rigid from elastic columns. A column is **elastic** when it
 * declares both `size` (preferred width) and `minSize` (minimum width before
 * truncation kicks in) with `minSize` strictly less than `size` AND greater
 * than the TanStack default. Elastic columns absorb surplus space when the
 * viewport is wider than the sum of column sizes, and shrink down to
 * `minSize` (with `truncate` + native tooltip) when horizontal scroll is
 * required. Columns that only declare `size` (or whose `minSize` is the
 * TanStack default) are **rigid**: they keep their width regardless of
 * available space.
 */
export function isElasticColumn<T>(colDef: DataTableColumnDef<T>): boolean {
  const size = colDef.size ?? 150;
  const minSize = colDef.minSize ?? TANSTACK_DEFAULT_MIN_SIZE;
  return minSize > TANSTACK_DEFAULT_MIN_SIZE && minSize < size;
}

/**
 * A column is **rigid** when it declares `size` and either doesn't declare
 * `minSize` or declares it as the TanStack default (`20`). Rigid columns
 * receive triple-locked width (`width` = `min-width` = `max-width` = `size`)
 * to prevent `table-layout: fixed` from rescaling them.
 */
export function isRigidColumn<T>(colDef: DataTableColumnDef<T>): boolean {
  if (colDef.size === undefined) return false;
  const minSize = colDef.minSize ?? TANSTACK_DEFAULT_MIN_SIZE;
  return minSize <= TANSTACK_DEFAULT_MIN_SIZE;
}

export interface DataTableData<T = any> {
  // The total number of records filtered.
  count: number;

  // The total number of records that are available.
  total: number;

  // The data that will be rendered by the Grid as an array.
  rows: T[];
}

export interface LabeledColumn {
  key: string;
  label: string;
}

export interface ColumnFilter {
  column: string;
  columnLabel: string;
  operator: string;
  value: string;
}

/**
 * Aggregated state of a server-paginated table (sort + pagination + filters +
 * search). The preset `usePaginatedDataTable` exposes this shape so a host
 * can persist it or react to it without coupling to internal atom signals.
 */
export interface ServerTableState {
  readonly sorting: SortingState;
  readonly pagination: PaginationState;
  readonly columnFilters: ColumnFilter[];
  readonly search: string;
}
