// packages/core/src/components/data-table/data-table-root.tsx
import type { JSX } from "solid-js";
import { createMemo, createSignal, splitProps } from "solid-js";
import { createControllableSignal } from "../../primitives/create-controllable-signal";
import type { InternalDataTableContextValue } from "./data-table-context";
import { DataTableContextProvider, InternalDataTableContextProvider, useDataTableContext } from "./data-table-context";
import { DataTableBody } from "./data-table-body";
import { DataTableCell } from "./data-table-cell";
import { DataTableHeader } from "./data-table-header";
import { DataTableHeaderCell } from "./data-table-header-cell";
import { DataTablePagination } from "./data-table-pagination";
import { DataTableRow } from "./data-table-row";
import type { DataTableColumn, DataTableRootProps, SortDirection } from "./data-table.props";

/** Apply global filter across all rows using JSON serialisation for a broad match. */
function applyFilter<T>(data: T[], filter: string, columns: DataTableColumn<T>[]): T[] {
  if (!filter) return data;
  const lower = filter.toLowerCase();
  return data.filter((row) => {
    for (const col of columns) {
      if (col.filterFn && col.filterFn(row, filter)) return true;
    }
    return JSON.stringify(row).toLowerCase().includes(lower);
  });
}

/** Default sort comparator using localeCompare on stringified cell values. */
function defaultSortFn<T>(col: DataTableColumn<T>, a: T, b: T): number {
  const rowA = a as Record<string, unknown>;
  const rowB = b as Record<string, unknown>;
  const aVal = String(JSON.stringify(rowA[col.id] ?? ""));
  const bVal = String(JSON.stringify(rowB[col.id] ?? ""));
  return aVal.localeCompare(bVal);
}

/** Sort a copy of data by a column in the given direction. */
function applySort<T>(data: T[], col: DataTableColumn<T>, direction: SortDirection): T[] {
  if (direction === "none") return data;
  return [...data].sort((a, b) => {
    const result = col.sortFn ? col.sortFn(a, b) : defaultSortFn(col, a, b);
    return direction === "asc" ? result : -result;
  });
}

/** Cycle sort direction for a column: none/new-column → asc → desc → none. */
function nextSortDirection(current: SortDirection, isSameColumn: boolean): SortDirection {
  if (!isSameColumn) return "asc";
  if (current === "none") return "asc";
  if (current === "asc") return "desc";
  return "none";
}

/**
 * Root component for DataTable. Owns all state: sort, filter, pagination,
 * and row selection. Provides context to all sub-components.
 */
export function DataTableRoot<T = unknown>(props: DataTableRootProps<T>): JSX.Element {
  const [local] = splitProps(props, [
    "data",
    "columns",
    "pageSize",
    "defaultPage",
    "page",
    "filterValue",
    "defaultFilterValue",
    "enableRowSelection",
    "enableMultiRowSelection",
    "onPageChange",
    "onFilterChange",
    "onSelectionChange",
    "children",
  ]);

  const resolvedPageSize = () => local.pageSize ?? 10;

  const [currentPage, setCurrentPage] = createControllableSignal<number>({
    value: () => local.page,
    defaultValue: () => local.defaultPage ?? 0,
    onChange: local.onPageChange,
  });

  const [filterValue, setFilterInternal] = createControllableSignal<string>({
    value: () => local.filterValue,
    defaultValue: () => local.defaultFilterValue ?? "",
    onChange: local.onFilterChange,
  });

  const [sortColumnId, setSortColumnId] = createSignal<string | undefined>(undefined);
  const [sortDirection, setSortDirection] = createSignal<SortDirection>("none");
  const [selectedIndices, setSelectedIndices] = createSignal<Set<number>>(new Set());

  const filteredData = createMemo(() => applyFilter(local.data, filterValue(), local.columns));

  const sortedData = createMemo(() => {
    const colId = sortColumnId();
    const dir = sortDirection();
    if (!colId || dir === "none") return filteredData();
    const col = local.columns.find((c) => c.id === colId);
    if (!col) return filteredData();
    return applySort(filteredData(), col, dir);
  });

  const pageCount = createMemo(() =>
    Math.max(1, Math.ceil(sortedData().length / resolvedPageSize())),
  );

  const safePage = () => Math.min(currentPage(), pageCount() - 1);

  const pageData = createMemo(() => {
    const start = safePage() * resolvedPageSize();
    return sortedData().slice(start, start + resolvedPageSize());
  });

  /** Toggle sort on a column; cycles none → asc → desc → none. */
  function toggleSort(columnId: string): void {
    const isSame = sortColumnId() === columnId;
    const next = nextSortDirection(sortDirection(), isSame);
    setSortColumnId(next === "none" ? undefined : columnId);
    setSortDirection(next);
    setCurrentPage(0);
  }

  /** Update the global filter and reset to page 0. */
  function setFilter(value: string): void {
    setFilterInternal(value);
    setCurrentPage(0);
  }

  /** Navigate to a page, clamped to valid range. */
  function goToPage(page: number): void {
    setCurrentPage(Math.max(0, Math.min(page, pageCount() - 1)));
  }

  /** Toggle selection of a row by its original-data index. */
  function toggleRowSelection(index: number): void {
    setSelectedIndices((prev) => {
      const next = new Set(prev);
      if (next.has(index)) {
        next.delete(index);
      } else {
        if (!local.enableMultiRowSelection) next.clear();
        next.add(index);
      }
      local.onSelectionChange?.(next);
      return next;
    });
  }

  const internalCtx: InternalDataTableContextValue = {
    columns: () => local.columns as DataTableColumn<unknown>[],
    pageData,
    pageCount,
    currentPage: safePage,
    pageSize: resolvedPageSize,
    goToPage,
    sortColumnId,
    sortDirection,
    toggleSort,
    filterValue,
    setFilter,
    selectedIndices,
    toggleRowSelection,
    enableRowSelection: () => local.enableRowSelection ?? false,
    enableMultiRowSelection: () => local.enableMultiRowSelection ?? false,
    totalRows: () => sortedData().length,
  };

  return (
    <InternalDataTableContextProvider value={internalCtx}>
      <DataTableContextProvider value={internalCtx}>
        {local.children}
      </DataTableContextProvider>
    </InternalDataTableContextProvider>
  );
}

/** Compound DataTable with all sub-components as static properties. */
export const DataTable = Object.assign(DataTableRoot, {
  Header: DataTableHeader,
  HeaderCell: DataTableHeaderCell,
  Body: DataTableBody,
  Row: DataTableRow,
  Cell: DataTableCell,
  Pagination: DataTablePagination,
  useContext: useDataTableContext,
});
