import { View, SxProp, useSx } from 'dripsy'
import { useState, useMemo, Dispatch, SetStateAction, useEffect } from 'react'
import { Pressable, ScrollView, StyleSheet } from 'react-native'
import {
    useReactTable,
    ColumnDef,
    flexRender,
    Row,
    SortingState,
    getCoreRowModel,
    getSortedRowModel,
    getFilteredRowModel,
    getPaginationRowModel,
    FilterMeta,
    filterFns,
} from '@tanstack/react-table'
import { RowSelector, Pagination } from './components'

interface TableProps<TData> {
    data: TData[]
    columns: ColumnDef<TData>[]
    globalFilterState?: [string, Dispatch<SetStateAction<string>>]
    rowSelectionState?: [any, Dispatch<SetStateAction<any>>]
    containerSx?: SxProp
    rowSx?: SxProp
    headerSx?: SxProp
    showHeader?: boolean
    pageSize?: number
    paddingWidth?: number
    filterFn?: (
        row: Row<TData>,
        columnId: string,
        filterValue: any,
        addMeta: (meta: FilterMeta) => void
    ) => boolean
    onRowPress?: (row: Row<TData>) => void
    checkboxContainerSx?: SxProp
    checkmarkSize?: number
}

export function Table<TData extends object>({
    data,
    columns,
    globalFilterState,
    rowSelectionState,
    containerSx,
    rowSx,
    headerSx,
    showHeader,
    pageSize,
    paddingWidth,
    filterFn,
    onRowPress,
    checkboxContainerSx,
    checkmarkSize,
}: TableProps<TData>) {
    // UI state
    const tableData = useMemo(() => data, [data])
    const tableColumns = useMemo(() => columns, [data])
    const [sorting, setSorting] = useState<SortingState>([])
    const [descSort, setDescSort] = useState<boolean>(true)
    const [rowSelection, setRowSelection] = useState<any>({})
    const [bodyRowStyle, setBodyRowStyle] = useState<SxProp[]>(() => {
        const rowStyle = data.map((_) => {
            const style = { ...styles.bodyRow, ...rowSx }
            return style
        })
        return rowStyle
    })
    const containerStyles = { ...styles.container, ...containerSx }
    const [globalFilter, setGlobalFilter] = getGlobalFilterState()
    const displayHeader = showHeader ?? true
    const tablePageSize = pageSize ?? 10
    const tablePaddingWidth = paddingWidth ?? 20
    const filterFunction = filterFn ?? filterFns.includesString
    const rowPressDisabled = !onRowPress
    const sx = useSx()

    const tableOptions = {
        data: tableData,
        columns: tableColumns,
        initialState: {
            pagination: {
                pageSize: tablePageSize,
            },
        },
        state: {
            rowSelection,
            sorting,
            globalFilter,
        },
        globalFilterFn: filterFunction,
        enableRowSelection: true,
        autoResetPageIndex: false,
        onRowSelectionChange: setRowSelection,
        onSortingChange: setSorting,
        onGlobalFilterChange: setGlobalFilter,
        getCoreRowModel: getCoreRowModel<TData>(),
        getSortedRowModel: getSortedRowModel<TData>(),
        getFilteredRowModel: getFilteredRowModel<TData>(),
        getPaginationRowModel: getPaginationRowModel<TData>(),
        getColumnCanGlobalFilter: () => true,
    }

    const {
        getHeaderGroups,
        getRowModel,
        getIsAllRowsSelected,
        getPrePaginationRowModel,
        toggleAllRowsSelected,
        getSelectedRowModel,
        getAllColumns,
        getState,
        setPageIndex,
        setPageSize,
        getPageCount,
    } = useReactTable<TData>(tableOptions)

    useEffect(() => {
        updateSelectionState()
    }, [rowSelection])

    // Updates row selection state
    function updateSelectionState() {
        if (!rowSelection || !rowSelectionState) return

        const setSelection = rowSelectionState[1]
        const dataRows = getSelectedRowModel().flatRows.map((row) => {
            return row.original
        })
        setSelection(dataRows)
    }

    function getGlobalFilterState(): [
        string,
        Dispatch<SetStateAction<string>>
    ] {
        const filterState = globalFilterState
            ? globalFilterState
            : useState<string>('')
        return filterState
    }

    function onRowPressed(row: Row<TData>) {
        if (!onRowPress) return

        onRowPress(row)
    }

    function onHoverIn(rowIndex: number) {
        if (rowPressDisabled) return

        const newBodyRowStyle = bodyRowStyle.map((_: SxProp, index: number) => {
            if (index === rowIndex) {
                const style = { ...styles.bodyRowHover, ...rowSx }
                return style
            }
            const style = { ...styles.bodyRow, ...rowSx }
            return style
        })
        setBodyRowStyle(newBodyRowStyle)
    }

    function onHoverOut() {
        if (rowPressDisabled) return

        const newBodyRowStyle = bodyRowStyle.map((sx: SxProp) => {
            const style = { ...styles.bodyRow, ...rowSx }
            return style
        })
        setBodyRowStyle(newBodyRowStyle)
    }

    // Adds left padding to the first item of each row
    function getPaddingLeft(index: number) {
        if (index === 0) {
            return tablePaddingWidth
        }
        return 0
    }

    // Adds right padding to the last item of each row
    function getPaddingRight(index: number) {
        const columns = getAllColumns()
        if (index === columns.length - 1) {
            return tablePaddingWidth
        }
        return 0
    }

    return (
        <View sx={containerStyles}>
            <ScrollView
                style={styles.scrollView}
                horizontal={true}
                scrollEnabled={true}
                showsHorizontalScrollIndicator={false}
                contentContainerStyle={styles.horizontalScrollContainer}
            >
                {displayHeader && (
                    <View sx={styles.header}>
                        {getHeaderGroups().map((headerGroup) => (
                            <View
                                sx={{ ...styles.headerRow, ...headerSx }}
                                key={headerGroup.id}
                            >
                                {rowSelectionState && (
                                    <RowSelector
                                        checked={getIsAllRowsSelected()}
                                        onChange={toggleAllRowsSelected}
                                        margins={getPaddingLeft(0)}
                                        containerSx={checkboxContainerSx}
                                        checkmarkSize={checkmarkSize}
                                    ></RowSelector>
                                )}
                                {headerGroup.headers.map((header, index) => (
                                    <Pressable
                                        style={sx({
                                            minWidth:
                                                header.column.columnDef.minSize,
                                            maxWidth:
                                                header.column.columnDef.maxSize,
                                            paddingLeft: rowSelectionState
                                                ? 0
                                                : getPaddingLeft(index),
                                            paddingRight:
                                                getPaddingRight(index),
                                            flex: 1,
                                        })}
                                        key={header.id}
                                        onPress={() => {
                                            header.column.toggleSorting(
                                                descSort
                                            )
                                            setDescSort(!descSort)
                                        }}
                                    >
                                        {flexRender(
                                            header.column.columnDef.header,
                                            header.getContext()
                                        )}
                                    </Pressable>
                                ))}
                            </View>
                        ))}
                    </View>
                )}
                {rowPressDisabled ? (
                    <ScrollView
                        style={styles.scrollView}
                        showsVerticalScrollIndicator={false}
                        contentContainerStyle={{ flex: 1 }}
                    >
                        {getRowModel().rows.map((row, index) => (
                            <View
                                sx={{
                                    ...styles.bodyRowContainer,
                                    zIndex: getRowModel().rows.length - index,
                                }}
                                key={row.id}
                            >
                                <View sx={bodyRowStyle[row.index]}>
                                    {rowSelectionState && (
                                        <RowSelector
                                            checked={row.getIsSelected()}
                                            onChange={row.getToggleSelectedHandler()}
                                            margins={getPaddingLeft(0)}
                                            containerSx={checkboxContainerSx}
                                            checkmarkSize={checkmarkSize}
                                        ></RowSelector>
                                    )}
                                    {row
                                        .getVisibleCells()
                                        .map((cell, columnIndex) => (
                                            <View
                                                sx={{
                                                    minWidth:
                                                        cell.column.columnDef
                                                            .minSize,
                                                    maxWidth:
                                                        cell.column.columnDef
                                                            .maxSize,
                                                    paddingLeft:
                                                        rowSelectionState
                                                            ? 0
                                                            : getPaddingLeft(
                                                                  columnIndex
                                                              ),
                                                    paddingRight:
                                                        getPaddingRight(
                                                            columnIndex
                                                        ),
                                                    flex: 1,
                                                }}
                                                key={cell.id}
                                            >
                                                {flexRender(
                                                    cell.column.columnDef.cell,
                                                    cell.getContext()
                                                )}
                                            </View>
                                        ))}
                                </View>
                            </View>
                        ))}
                    </ScrollView>
                ) : (
                    <ScrollView
                        style={styles.scrollView}
                        showsVerticalScrollIndicator={false}
                        contentContainerStyle={{ flex: 1 }}
                    >
                        {getRowModel().rows.map((row, index) => (
                            <Pressable
                                style={sx({
                                    ...styles.bodyRowContainer,
                                    zIndex: getRowModel().rows.length - index,
                                })}
                                onPress={() => onRowPressed(row)}
                                key={row.id}
                                onHoverIn={() => onHoverIn(row.index)}
                                onHoverOut={() => onHoverOut()}
                            >
                                <View sx={bodyRowStyle[row.index]}>
                                    {rowSelectionState && (
                                        <RowSelector
                                            checked={row.getIsSelected()}
                                            onChange={row.getToggleSelectedHandler()}
                                            margins={getPaddingLeft(0)}
                                            containerSx={checkboxContainerSx}
                                            checkmarkSize={checkmarkSize}
                                        ></RowSelector>
                                    )}
                                    {row
                                        .getVisibleCells()
                                        .map((cell, columnIndex) => (
                                            <View
                                                sx={{
                                                    minWidth:
                                                        cell.column.columnDef
                                                            .minSize,
                                                    maxWidth:
                                                        cell.column.columnDef
                                                            .maxSize,
                                                    paddingLeft:
                                                        rowSelectionState
                                                            ? 0
                                                            : getPaddingLeft(
                                                                  columnIndex
                                                              ),
                                                    paddingRight:
                                                        getPaddingRight(
                                                            columnIndex
                                                        ),
                                                    flex: 1,
                                                }}
                                                key={cell.id}
                                            >
                                                {flexRender(
                                                    cell.column.columnDef.cell,
                                                    cell.getContext()
                                                )}
                                            </View>
                                        ))}
                                </View>
                            </Pressable>
                        ))}
                    </ScrollView>
                )}
            </ScrollView>
            <View sx={styles.footer}>
                <Pagination
                    tableState={getState()}
                    setPageIndex={setPageIndex}
                    setPageSize={setPageSize}
                    getPageCount={getPageCount}
                    recordCount={getPrePaginationRowModel().rows.length}
                />
            </View>
        </View>
    )
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: 'transparent',
        borderRadius: 3,
    },
    scrollView: {
        height: '65vh',
        minWidth: '100%',
    },
    horizontalScrollContainer: {
        flex: 1,
        flexDirection: 'column',
        alignItems: 'flex-start',
    },
    header: {
        marginBottom: '2px',
        minWidth: '100%',
    },
    headerRow: {
        flexDirection: 'row',
        borderRadius: 3,
        backgroundColor: '$secondary.5',
    },
    bodyRowContainer: {
        marginBottom: '2px',
    },
    bodyRow: {
        flexDirection: 'row',
        borderRadius: 3,
        backgroundColor: '$secondary.5',
    },
    bodyRowHover: {
        flexDirection: 'row',
        borderRadius: 3,
        backgroundColor: '$secondary.7',
    },
    footer: {
        maxWidth: '100%',
        height: 50,
        marginTop: '4px',
    },
})
