import * as React from 'react';
import { clsx } from 'clsx';
import {
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useReactTable,
  type Column,
  type ColumnDef,
  type ColumnOrderState,
  type ColumnPinningState,
  type Row,
  type SortingState,
} from '@tanstack/react-table';
import { useVirtualizer } from '@tanstack/react-virtual';

import type { DataTableProps } from './types.js';

const SELECT_COL_ID = '__select__';

const densityRowCls: Record<NonNullable<DataTableProps<unknown>['density']>, string> = {
  compact: 'px-3 py-1 text-xs',
  comfortable: 'px-4 py-2.5 text-sm',
};

/** Sticky-left offsets for pinned columns. */
function pinnedStyle<T>(column: Column<T, unknown>): React.CSSProperties {
  const isPinned = column.getIsPinned();
  if (isPinned !== 'left') return {};
  return {
    position: 'sticky',
    left: column.getStart('left'),
    zIndex: 1,
    background: 'var(--bl-surface-card, #fff)',
  };
}

/**
 * `<DataTable>` — a token-themed table powered by TanStack Table v8 (the
 * current stable; the roadmap's "v9" predates a stable v9 release) and
 * TanStack Virtual. Opt into features via props; everything is keyboard- and
 * screen-reader-friendly.
 */
export function DataTable<T>({
  columns,
  data,
  enableSorting = true,
  enableFilter = true,
  filterPlaceholder = 'Filter…',
  enablePagination = true,
  pageSize = 10,
  enableRowSelection = false,
  renderBulkActions,
  enableColumnResizing = false,
  enableColumnReorder = false,
  enableColumnPinning = false,
  density = 'comfortable',
  getRowId,
  emptyState,
  ariaLabel = 'Data table',
  className,
  virtualized = false,
  rowHeight = 40,
  maxBodyHeight = 480,
}: DataTableProps<T>) {
  const [sorting, setSorting] = React.useState<SortingState>([]);
  const [globalFilter, setGlobalFilter] = React.useState('');
  const [columnOrder, setColumnOrder] = React.useState<ColumnOrderState>([]);
  const [columnPinning, setColumnPinning] = React.useState<ColumnPinningState>({});
  const dragColRef = React.useRef<string | null>(null);

  const resolvedColumns = React.useMemo<ColumnDef<T, unknown>[]>(() => {
    if (!enableRowSelection) return columns;
    const selectionColumn: ColumnDef<T, unknown> = {
      id: SELECT_COL_ID,
      size: 44,
      enableSorting: false,
      enableResizing: false,
      header: ({ table }) => (
        <input
          type="checkbox"
          aria-label="Select all rows"
          checked={table.getIsAllRowsSelected()}
          ref={el => {
            if (el) el.indeterminate = table.getIsSomeRowsSelected();
          }}
          onChange={table.getToggleAllRowsSelectedHandler()}
        />
      ),
      cell: ({ row }) => (
        <input
          type="checkbox"
          aria-label={`Select row ${row.index + 1}`}
          checked={row.getIsSelected()}
          disabled={!row.getCanSelect()}
          onChange={row.getToggleSelectedHandler()}
        />
      ),
    };
    return [selectionColumn, ...columns];
  }, [columns, enableRowSelection]);

  const table = useReactTable<T>({
    data,
    columns: resolvedColumns,
    state: { sorting, globalFilter, columnOrder, columnPinning },
    onSortingChange: setSorting,
    onGlobalFilterChange: setGlobalFilter,
    onColumnOrderChange: setColumnOrder,
    onColumnPinningChange: setColumnPinning,
    getRowId,
    enableSorting,
    enableRowSelection,
    enableColumnResizing,
    columnResizeMode: 'onChange',
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: enableSorting ? getSortedRowModel() : undefined,
    getFilteredRowModel: enableFilter ? getFilteredRowModel() : undefined,
    getPaginationRowModel: enablePagination && !virtualized ? getPaginationRowModel() : undefined,
    initialState: { pagination: { pageSize } },
  });

  const rows = table.getRowModel().rows;
  const selectedRows = table.getSelectedRowModel().rows.map((r: Row<T>) => r.original);
  const rowCellCls = densityRowCls[density];

  // ── Virtualiser (only when virtualized) ──
  const scrollRef = React.useRef<HTMLDivElement>(null);
  const virtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => scrollRef.current,
    estimateSize: () => rowHeight,
    overscan: 8,
    enabled: virtualized,
    // Seed the viewport so rows render before the scroll element is measured
    // (SSR + headless environments report a zero-size rect otherwise).
    initialRect: { width: 0, height: maxBodyHeight },
  });

  const onHeaderDragStart = (colId: string) => {
    dragColRef.current = colId;
  };
  const onHeaderDrop = (targetId: string) => {
    const source = dragColRef.current;
    dragColRef.current = null;
    if (!source || source === targetId) return;
    const order = table.getAllLeafColumns().map(c => c.id);
    const current = columnOrder.length ? columnOrder : order;
    const from = current.indexOf(source);
    const to = current.indexOf(targetId);
    if (from === -1 || to === -1) return;
    const next = [...current];
    next.splice(to, 0, next.splice(from, 1)[0]!);
    setColumnOrder(next);
  };

  function renderHeaderCell(
    header: ReturnType<typeof table.getHeaderGroups>[number]['headers'][number]
  ) {
    const column = header.column;
    const canSort = enableSorting && column.getCanSort();
    const sortDir = column.getIsSorted();
    const isSelectCol = column.id === SELECT_COL_ID;
    const reorderable = enableColumnReorder && !isSelectCol;
    return (
      <th
        key={header.id}
        scope="col"
        aria-sort={sortDir === 'asc' ? 'ascending' : sortDir === 'desc' ? 'descending' : 'none'}
        draggable={reorderable}
        onDragStart={reorderable ? () => onHeaderDragStart(column.id) : undefined}
        onDragOver={reorderable ? e => e.preventDefault() : undefined}
        onDrop={reorderable ? () => onHeaderDrop(column.id) : undefined}
        style={{
          width: enableColumnResizing ? header.getSize() : undefined,
          position: 'relative',
          ...pinnedStyle(column),
        }}
        className={clsx(
          rowCellCls,
          'border-b border-[var(--bl-border,rgba(0,0,0,0.12))] text-left font-semibold text-[var(--bl-text-secondary,#444)]'
        )}
      >
        <span className="flex items-center gap-1">
          {header.isPlaceholder ? null : (
            <button
              type="button"
              disabled={!canSort}
              onClick={canSort ? column.getToggleSortingHandler() : undefined}
              className={clsx('inline-flex items-center gap-1', canSort && 'cursor-pointer')}
            >
              {flexRender(column.columnDef.header, header.getContext())}
              {sortDir === 'asc' && <span aria-hidden="true">▲</span>}
              {sortDir === 'desc' && <span aria-hidden="true">▼</span>}
            </button>
          )}
          {enableColumnPinning && !isSelectCol && (
            <button
              type="button"
              aria-label={
                column.getIsPinned() === 'left' ? `Unpin ${column.id}` : `Pin ${column.id} left`
              }
              aria-pressed={column.getIsPinned() === 'left'}
              onClick={() => column.pin(column.getIsPinned() === 'left' ? false : 'left')}
              className="text-[var(--bl-text-tertiary,#999)]"
            >
              {column.getIsPinned() === 'left' ? '📌' : '📍'}
            </button>
          )}
        </span>
        {enableColumnResizing && column.getCanResize() && (
          <span
            role="separator"
            aria-orientation="vertical"
            aria-label={`Resize ${column.id}`}
            onMouseDown={header.getResizeHandler()}
            onTouchStart={header.getResizeHandler()}
            className="absolute right-0 top-0 h-full w-1 cursor-col-resize select-none touch-none bg-[var(--bl-border,rgba(0,0,0,0.12))] opacity-0 hover:opacity-100"
          />
        )}
      </th>
    );
  }

  const colCount = table.getAllLeafColumns().length;

  return (
    <div className={clsx('flex flex-col gap-2', className)} data-testid="bl-data-table">
      {enableFilter && (
        <input
          type="search"
          role="searchbox"
          aria-label={filterPlaceholder}
          value={globalFilter}
          onChange={e => setGlobalFilter(e.target.value)}
          placeholder={filterPlaceholder}
          className="h-9 w-full max-w-xs rounded-lg border border-[var(--bl-border,rgba(0,0,0,0.12))] bg-[var(--bl-surface-card,#fff)] px-3 text-sm outline-none focus-visible:border-[var(--bl-accent,#6366f1)]"
        />
      )}

      {enableRowSelection && selectedRows.length > 0 && (
        <div
          data-testid="bl-bulk-actions"
          className="flex items-center gap-3 rounded-lg bg-[var(--bl-accent-muted,rgba(99,102,241,0.12))] px-3 py-2 text-sm"
        >
          <span>{selectedRows.length} selected</span>
          {renderBulkActions?.(selectedRows, () => table.resetRowSelection())}
        </div>
      )}

      <div
        ref={scrollRef}
        style={virtualized ? { maxHeight: maxBodyHeight, overflow: 'auto' } : undefined}
        className="rounded-xl border border-[var(--bl-border-subtle,rgba(0,0,0,0.06))]"
      >
        <table
          aria-label={ariaLabel}
          role="table"
          className="w-full border-collapse"
          style={{ width: enableColumnResizing ? table.getTotalSize() : '100%' }}
        >
          <thead>
            {table.getHeaderGroups().map(hg => (
              <tr key={hg.id}>{hg.headers.map(h => renderHeaderCell(h))}</tr>
            ))}
          </thead>
          <tbody
            style={
              virtualized
                ? { display: 'block', height: virtualizer.getTotalSize(), position: 'relative' }
                : undefined
            }
          >
            {rows.length === 0 ? (
              <tr>
                <td
                  colSpan={colCount}
                  className={clsx(rowCellCls, 'text-center text-[var(--bl-text-tertiary,#999)]')}
                >
                  {emptyState ?? 'No rows.'}
                </td>
              </tr>
            ) : virtualized ? (
              virtualizer.getVirtualItems().map(vi => {
                const row = rows[vi.index]!;
                return (
                  <tr
                    key={row.id}
                    data-index={vi.index}
                    style={{
                      position: 'absolute',
                      top: 0,
                      left: 0,
                      width: '100%',
                      height: rowHeight,
                      transform: `translateY(${vi.start}px)`,
                      display: 'flex',
                    }}
                  >
                    {row.getVisibleCells().map(cell => (
                      <td key={cell.id} className={clsx(rowCellCls, 'flex-1')}>
                        {flexRender(cell.column.columnDef.cell, cell.getContext())}
                      </td>
                    ))}
                  </tr>
                );
              })
            ) : (
              rows.map(row => (
                <tr
                  key={row.id}
                  data-state={row.getIsSelected() ? 'selected' : undefined}
                  className={clsx(
                    'border-b border-[var(--bl-border-subtle,rgba(0,0,0,0.06))] last:border-0',
                    row.getIsSelected() && 'bg-[var(--bl-accent-muted,rgba(99,102,241,0.10))]'
                  )}
                >
                  {row.getVisibleCells().map(cell => (
                    <td
                      key={cell.id}
                      style={{
                        width: enableColumnResizing ? cell.column.getSize() : undefined,
                        ...pinnedStyle(cell.column),
                      }}
                      className={rowCellCls}
                    >
                      {flexRender(cell.column.columnDef.cell, cell.getContext())}
                    </td>
                  ))}
                </tr>
              ))
            )}
          </tbody>
        </table>
      </div>

      {enablePagination && !virtualized && rows.length > 0 && (
        <div className="flex items-center justify-between text-sm text-[var(--bl-text-secondary,#444)]">
          <span>
            Page {table.getState().pagination.pageIndex + 1} of {Math.max(1, table.getPageCount())}
          </span>
          <span className="flex gap-2">
            <button
              type="button"
              onClick={() => table.previousPage()}
              disabled={!table.getCanPreviousPage()}
              className="rounded-md border border-[var(--bl-border,rgba(0,0,0,0.12))] px-2 py-1 disabled:opacity-40"
            >
              Previous
            </button>
            <button
              type="button"
              onClick={() => table.nextPage()}
              disabled={!table.getCanNextPage()}
              className="rounded-md border border-[var(--bl-border,rgba(0,0,0,0.12))] px-2 py-1 disabled:opacity-40"
            >
              Next
            </button>
          </span>
        </div>
      )}
    </div>
  );
}
