import '@/types/data-table';
import {
    closestCenter,
    DndContext,

    KeyboardSensor,
    PointerSensor,
    useSensor,
    useSensors
} from '@dnd-kit/core';
import type {DragEndEvent} from '@dnd-kit/core';
import {
    arrayMove,
    SortableContext,
    sortableKeyboardCoordinates,
    useSortable,
    verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import {
    flexRender,
    getCoreRowModel,
    getSortedRowModel,
    useReactTable,
} from '@tanstack/react-table';
import type { ColumnDef, Row, SortingState } from '@tanstack/react-table';
import { GripVertical } from 'lucide-react';
import { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
import { DATA_TABLE_PAGE_SIZE } from '@/components/data-table/constants';
import { DataTablePagination } from '@/components/data-table/data-table-pagination';
import { DataTableToolbar } from '@/components/data-table/data-table-toolbar';
import { enhanceDataTableColumns } from '@/components/data-table/enhance-data-table-columns';
import {
    Table,
    TableBody,
    TableCell,
    TableHead,
    TableHeader,
    TableRow,
} from '@/components/ui/table';
import { cn } from '@/lib/utils';


type RowIdValue = number | string;

type DataTableProps<TData, TValue> = {
    columns: ColumnDef<TData, TValue>[];
    data: TData[];
    searchPlaceholder?: string;
    emptyMessage?: string;
    onReorder?: (orderedIds: RowIdValue[]) => void;
    getRowId?: (row: TData) => RowIdValue;
    canExpandRow?: (row: TData) => boolean;
    isRowExpanded?: (row: TData) => boolean;
    renderExpandedRow?: (row: TData) => React.ReactNode;
};

type ExpandableRowOptions<TData> = {
    columnCount: number;
    canExpandRow?: (row: TData) => boolean;
    isRowExpanded?: (row: TData) => boolean;
    renderExpandedRow?: (row: TData) => React.ReactNode;
};

function ExpandedTableRow<TData>({
    row,
    columnCount,
    canExpandRow,
    isRowExpanded,
    renderExpandedRow,
}: ExpandableRowOptions<TData> & { row: TData }) {
    if (
        !canExpandRow?.(row) ||
        !isRowExpanded?.(row) ||
        !renderExpandedRow
    ) {
        return null;
    }

    return (
        <TableRow className="bg-muted/20 hover:bg-muted/20">
            <TableCell
                colSpan={columnCount}
                className="p-0 whitespace-normal"
            >
                {renderExpandedRow(row)}
            </TableCell>
        </TableRow>
    );
}

function rowMatchesGlobalFilter<TData>(row: TData, filterValue: string): boolean {
    const search = filterValue.trim().toLowerCase();

    if (!search) {
        return true;
    }

    const values: string[] = [];

    const collect = (value: unknown): void => {
        if (value == null) {
            return;
        }

        if (Array.isArray(value)) {
            value.forEach(collect);

            return;
        }

        if (typeof value === 'object') {
            Object.values(value as Record<string, unknown>).forEach(collect);

            return;
        }

        values.push(String(value));
    };

    collect(row);

    return values.some((value) => value.toLowerCase().includes(search));
}

function SortableTableRow<TData>({
    row,
    disabled,
    columnCount,
    canExpandRow,
    isRowExpanded,
    renderExpandedRow,
}: {
    row: Row<TData>;
    disabled: boolean;
} & ExpandableRowOptions<TData>) {
    const {
        attributes,
        listeners,
        setNodeRef,
        transform,
        transition,
        isDragging,
    } = useSortable({ id: row.id, disabled });

    const style: React.CSSProperties = {
        transform: CSS.Transform.toString(transform),
        transition,
    };

    return (
        <>
            <TableRow
                ref={setNodeRef}
                style={style}
                className={cn(isDragging && 'relative z-10 bg-muted shadow-sm')}
            >
                <TableCell className="w-8">
                    <button
                        type="button"
                        aria-label="Drag to reorder"
                        disabled={disabled}
                        className={cn(
                            'flex size-7 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground',
                            disabled
                                ? 'cursor-not-allowed opacity-40'
                                : 'cursor-grab active:cursor-grabbing',
                        )}
                        {...attributes}
                        {...listeners}
                    >
                        <GripVertical className="size-4" />
                    </button>
                </TableCell>
                {row.getVisibleCells().map((cell) => (
                    <TableCell key={cell.id}>
                        {flexRender(
                            cell.column.columnDef.cell,
                            cell.getContext(),
                        )}
                    </TableCell>
                ))}
            </TableRow>
            <ExpandedTableRow
                row={row.original}
                columnCount={columnCount}
                canExpandRow={canExpandRow}
                isRowExpanded={isRowExpanded}
                renderExpandedRow={renderExpandedRow}
            />
        </>
    );
}

export function DataTable<TData, TValue>({
    columns,
    data,
    searchPlaceholder,
    emptyMessage = 'No results.',
    onReorder,
    getRowId,
    canExpandRow,
    isRowExpanded,
    renderExpandedRow,
}: DataTableProps<TData, TValue>) {
    const reorderable = typeof onReorder === 'function';

    const [globalFilter, setGlobalFilter] = useState('');
    const [columnVisibility, setColumnVisibility] = useState({});
    const [sorting, setSorting] = useState<SortingState>([]);
    const [pageIndex, setPageIndex] = useState(0);
    const [items, setItems] = useState<TData[]>(data);

    useEffect(() => {
        setItems(data);
    }, [data]);

    const resolveRowId = useCallback(
        (row: TData): RowIdValue =>
            getRowId ? getRowId(row) : (row as { id: RowIdValue }).id,
        [getRowId],
    );

    const enhancedColumns = useMemo(
        () => enhanceDataTableColumns(columns, { sortable: !reorderable }),
        [columns, reorderable],
    );

    const sourceData = reorderable ? items : data;

    const filteredData = useMemo(
        () =>
            sourceData.filter((row) =>
                rowMatchesGlobalFilter(row, globalFilter),
            ),
        [sourceData, globalFilter],
    );

    const table = useReactTable({
        data: filteredData,
        columns: enhancedColumns,
        state: {
            columnVisibility,
            sorting,
        },
        getRowId: reorderable
            ? (row) => String(resolveRowId(row))
            : undefined,
        onColumnVisibilityChange: setColumnVisibility,
        onSortingChange: (updater) => {
            setSorting(updater);
            setPageIndex(0);
        },
        getCoreRowModel: getCoreRowModel(),
        getSortedRowModel: getSortedRowModel(),
    });

    const sortedRows = table.getRowModel().rows;
    const totalRows = sortedRows.length;
    const pageCount = Math.max(1, Math.ceil(totalRows / DATA_TABLE_PAGE_SIZE));
    const safePageIndex = Math.min(pageIndex, pageCount - 1);
    const pageStartIndex = safePageIndex * DATA_TABLE_PAGE_SIZE;
    const pageRows = reorderable
        ? sortedRows
        : sortedRows.slice(
              pageStartIndex,
              pageStartIndex + DATA_TABLE_PAGE_SIZE,
          );

    useEffect(() => {
        setPageIndex((current) => {
            const maxPageIndex = Math.max(
                0,
                Math.ceil(totalRows / DATA_TABLE_PAGE_SIZE) - 1,
            );

            return Math.min(current, maxPageIndex);
        });
    }, [totalRows]);

    const sensors = useSensors(
        useSensor(PointerSensor, {
            activationConstraint: { distance: 5 },
        }),
        useSensor(KeyboardSensor, {
            coordinateGetter: sortableKeyboardCoordinates,
        }),
    );

    const dndEnabled = reorderable && globalFilter.trim() === '';

    const handleDragEnd = (event: DragEndEvent) => {
        const { active, over } = event;

        if (!over || active.id === over.id) {
            return;
        }

        setItems((current) => {
            const oldIndex = current.findIndex(
                (row) => String(resolveRowId(row)) === active.id,
            );
            const newIndex = current.findIndex(
                (row) => String(resolveRowId(row)) === over.id,
            );

            if (oldIndex === -1 || newIndex === -1) {
                return current;
            }

            const next = arrayMove(current, oldIndex, newIndex);
            onReorder?.(next.map((row) => resolveRowId(row)));

            return next;
        });
    };

    const handleGlobalFilterChange = (value: string) => {
        setGlobalFilter(value);
        setPageIndex(0);
    };

    const handleResetTableState = () => {
        table.resetSorting();
        handleGlobalFilterChange('');
    };

    const columnCount = enhancedColumns.length + (reorderable ? 1 : 0);

    const tableElement = (
        <div className="overflow-hidden rounded-md border">
            <Table>
                <TableHeader className="bg-muted/40">
                    {table.getHeaderGroups().map((headerGroup) => (
                        <TableRow key={headerGroup.id}>
                            {reorderable ? (
                                <TableHead className="w-8" aria-hidden />
                            ) : null}
                            {headerGroup.headers.map((header) => (
                                <TableHead key={header.id}>
                                    {header.isPlaceholder
                                        ? null
                                        : flexRender(
                                              header.column.columnDef.header,
                                              header.getContext(),
                                          )}
                                </TableHead>
                            ))}
                        </TableRow>
                    ))}
                </TableHeader>
                <TableBody>
                    {pageRows.length > 0 ? (
                        reorderable ? (
                            <SortableContext
                                items={pageRows.map((row) => row.id)}
                                strategy={verticalListSortingStrategy}
                            >
                                {pageRows.map((row) => (
                                    <SortableTableRow
                                        key={row.id}
                                        row={row}
                                        disabled={!dndEnabled}
                                        columnCount={columnCount}
                                        canExpandRow={canExpandRow}
                                        isRowExpanded={isRowExpanded}
                                        renderExpandedRow={renderExpandedRow}
                                    />
                                ))}
                            </SortableContext>
                        ) : (
                            pageRows.map((row) => (
                                <Fragment key={row.id}>
                                    <TableRow>
                                        {row.getVisibleCells().map((cell) => (
                                            <TableCell key={cell.id}>
                                                {flexRender(
                                                    cell.column.columnDef.cell,
                                                    cell.getContext(),
                                                )}
                                            </TableCell>
                                        ))}
                                    </TableRow>
                                    <ExpandedTableRow
                                        row={row.original}
                                        columnCount={columnCount}
                                        canExpandRow={canExpandRow}
                                        isRowExpanded={isRowExpanded}
                                        renderExpandedRow={renderExpandedRow}
                                    />
                                </Fragment>
                            ))
                        )
                    ) : (
                        <TableRow>
                            <TableCell
                                colSpan={columnCount}
                                className="h-24 text-center"
                            >
                                {emptyMessage}
                            </TableCell>
                        </TableRow>
                    )}
                </TableBody>
            </Table>
        </div>
    );

    return (
        <div className="flex flex-col gap-4">
            <DataTableToolbar
                table={table}
                globalFilter={globalFilter}
                onGlobalFilterChange={handleGlobalFilterChange}
                onReset={handleResetTableState}
                searchPlaceholder={searchPlaceholder}
            />
            {reorderable ? (
                <DndContext
                    sensors={sensors}
                    collisionDetection={closestCenter}
                    onDragEnd={handleDragEnd}
                >
                    {tableElement}
                </DndContext>
            ) : (
                tableElement
            )}
            {reorderable ? null : (
                <DataTablePagination
                    pageIndex={safePageIndex}
                    pageCount={totalRows === 0 ? 0 : pageCount}
                    pageSize={DATA_TABLE_PAGE_SIZE}
                    totalRows={totalRows}
                    pageRowCount={pageRows.length}
                    onPreviousPage={() => setPageIndex(safePageIndex - 1)}
                    onNextPage={() => setPageIndex(safePageIndex + 1)}
                />
            )}
        </div>
    );
}
