'use client'

import {
  flexRender,
  getCoreRowModel,
  useReactTable,
  type ColumnDef,
  type ColumnPinningState,
  type ColumnSort,
  type RowSelectionState,
  type SortingState,
  type Table,
  type VisibilityState
} from '@tanstack/react-table'
import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode
} from 'react'
import { useSearchParams } from 'react-router'

import { Card, CardContent } from '@repo/ui/components/ui/card'
import {
  Table as Table_,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow
} from '@repo/ui/components/ui/table'
import { cn } from '@repo/ui/lib/utils'
import { DataTablePagination } from './pagination'

interface DataTableProps<TData, TValue> {
  children?: ReactNode
  columns: ColumnDef<TData, TValue>[]
  columnPinning?: ColumnPinningState
  data: TData[]
  setSelectedRows?: (selectedRows: TData[]) => void
  pageIndex: number
  sort: SortingState
  pageSize: number
  rowCount: number
  columnVisibilityInit?: VisibilityState
}

interface TableContextProps<TData> {
  table: Table<TData>
}

const TableContext = createContext<TableContextProps<any> | null>(null)

export function useDataTable<TData>() {
  const ctx = useContext(TableContext)

  if (!ctx) {
    throw new Error('TableContext is null')
  }

  return ctx as { table: Table<TData> }
}

export function DataTable<TData, TValue>({
  data,
  children,
  columns,
  columnPinning = { left: [], right: [] },
  sort,
  pageIndex,
  pageSize,
  rowCount,
  setSelectedRows,
  columnVisibilityInit = {}
}: DataTableProps<TData, TValue>) {
  const [dataTable, setDataTable] = useState<TData[]>(data)
  const [searchParams, setSearchParams] = useSearchParams()
  const [rowSelection, setRowSelection] = useState<RowSelectionState>({})

  const columnVisibilityInit_ = useMemo<VisibilityState>(() => {
    const columns = searchParams.get('columns')

    if (columns === null) {
      return columnVisibilityInit
    }

    const columnVisibility = columns ? columns.split(',') : []
    return Object.keys(columnVisibilityInit).reduce<VisibilityState>(
      (acc, col) => ({
        ...acc,
        [col]: columnVisibility.includes(col)
      }),
      {}
    )
  }, [])

  const [columnVisibility, setColumnVisibility_] = useState<VisibilityState>(
    columnVisibilityInit_
  )

  const sorting = useMemo(() => {
    const sortParam = searchParams.get('sort')

    if (!sortParam) {
      return sort
    }

    return sortParam.split(',').map((s) => {
      const [id, dir] = s.split(':')
      return { id, desc: dir === 'desc' }
    })
  }, [searchParams, sort])

  const setPagination = useCallback(
    (updater: any) => {
      const newState =
        typeof updater === 'function'
          ? updater({ pageIndex, pageSize })
          : updater

      setRowSelection({})
      setSearchParams((prev) => {
        prev.set('p', newState?.pageIndex.toString())
        prev.set('perPage', newState?.pageSize.toString())

        return prev
      })
    },
    [pageIndex, pageSize, setSearchParams]
  )

  const setSorting = useCallback(
    (updater: any) => {
      const newSorting =
        typeof updater === 'function' ? updater(sorting) : updater

      setSearchParams((prev) => {
        if (newSorting.length) {
          const sort = newSorting
            .map((s: ColumnSort) => `${s.id}:${s.desc ? 'desc' : 'asc'}`)
            .join(',')
          prev.set('sort', sort)
        } else {
          prev.delete('sort')
        }

        return prev
      })
    },
    [sorting, setSearchParams]
  )

  const setColumnVisibility = useCallback(
    (updater: any) => {
      const newColumnVisibility =
        typeof updater === 'function' ? updater(columnVisibility) : updater

      setColumnVisibility_(newColumnVisibility)

      setSearchParams((prev) => {
        const columns = Object.entries(newColumnVisibility)
          .filter(([_, visible]) => visible)
          .map(([col, _]) => col)

        if (columns.length > 0) {
          prev.set('columns', columns.join(','))
        } else {
          prev.set('columns', '')
        }

        return prev
      })
    },
    [setSearchParams, setColumnVisibility_]
  )

  useEffect(() => {
    setDataTable(data)
  }, [data])

  const getRowId = useCallback((row: any) => row.id, [])
  const removeRow = useCallback((rowId: string) => {
    setDataTable((rows) => rows.filter((row: any) => row.id !== rowId))
  }, [])

  const table = useReactTable({
    data: dataTable,
    columns,
    rowCount,
    state: {
      sorting,
      rowSelection,
      columnVisibility,
      columnPinning,
      pagination: { pageIndex, pageSize }
    },
    manualSorting: true,
    manualPagination: true,
    enableColumnPinning: true,
    enableRowSelection: true,
    getRowId,
    getCoreRowModel: getCoreRowModel(),
    onRowSelectionChange: setRowSelection,
    onSortingChange: setSorting,
    onColumnVisibilityChange: setColumnVisibility,
    onPaginationChange: setPagination,
    meta: { removeRow }
  })

  useEffect(() => {
    if (!setSelectedRows) return

    const selected = table.getSelectedRowModel().flatRows.map((r) => r.original)
    setSelectedRows(selected)
  }, [rowSelection, setSelectedRows, table])

  return (
    <TableContext value={{ table }}>
      <div className="space-y-2.5 max-md:mb-2">
        <Card className="relative w-full overflow-auto">
          <CardContent>
            {children}

            <Table_ className="table-auto w-full">
              <TableHeader>
                {table.getHeaderGroups().map((headerGroup) => (
                  <TableRow
                    key={headerGroup.id}
                    className="hover:bg-transparent"
                  >
                    {headerGroup.headers.map((header) => {
                      const isPinned = header.column.getIsPinned()

                      return (
                        <TableHead
                          key={header.id}
                          className={cn(
                            isPinned && 'z-1 lg:sticky bg-card',
                            isPinned === 'left' && 'left-0',
                            isPinned === 'right' && 'right-0',
                            'p-2.5',
                            // Override the shadcn class
                            '[&:has([role=checkbox])]:px-1.5',
                            // @ts-ignore
                            header.column.columnDef.meta?.className
                          )}
                        >
                          {header.isPlaceholder
                            ? null
                            : flexRender(
                                header.column.columnDef.header,
                                header.getContext()
                              )}
                        </TableHead>
                      )
                    })}
                  </TableRow>
                ))}
              </TableHeader>

              <TableBody>
                {table.getRowModel().rows?.length ? (
                  table.getRowModel().rows.map((row) => {
                    const isSelected = row.getIsSelected()

                    return (
                      <TableRow
                        key={row.id}
                        data-state={isSelected && 'selected'}
                        className="group has-data-[state=open]:bg-muted
                                   data-[state=selected]:bg-gray-50
                                   dark:data-[state=selected]:bg-neutral-900
                                   transition-none hover:bg-muted relative z-5"
                      >
                        {row.getVisibleCells().map((cell) => {
                          const isPinned = cell.column.getIsPinned()

                          return (
                            <TableCell
                              key={cell.id}
                              className={cn(
                                isPinned &&
                                  'lg:sticky z-1 bg-card group-has-data-[state=open]:bg-muted',
                                isPinned &&
                                  !isSelected &&
                                  'group-hover:bg-muted',
                                isPinned &&
                                  isSelected &&
                                  'bg-gray-50 dark:bg-neutral-900',
                                isPinned === 'left' && 'left-0',
                                isPinned === 'right' && 'right-0',
                                // Override the shadcn class
                                '[&:has([role=checkbox])]:px-1.5',
                                'p-2.5',
                                // @ts-ignore
                                cell.column.columnDef.meta?.className
                              )}
                            >
                              {flexRender(
                                cell.column.columnDef.cell,
                                cell.getContext()
                              )}
                            </TableCell>
                          )
                        })}
                      </TableRow>
                    )
                  })
                ) : (
                  <TableRow className="hover:bg-transparent">
                    <TableCell
                      colSpan={columns.length}
                      className="h-24 text-center"
                    >
                      Nenhum resultado.
                    </TableCell>
                  </TableRow>
                )}
              </TableBody>
            </Table_>
          </CardContent>
        </Card>

        <DataTablePagination table={table} />
      </div>
    </TableContext>
  )
}
