"use client"

import type {
	ColumnFiltersState,
	PaginationState,
	RowSelectionState,
	SortingState,
	TableOptions,
	TableState,
	Updater,
	VisibilityState,
} from "@tanstack/react-table"
import type { Parser, UseQueryStateOptions } from "nuqs"
import type { DataTableFilterField, ExtendedSortingState } from "../types"

import * as React from "react"
import {
	getCoreRowModel,
	getFacetedRowModel,
	getFacetedUniqueValues,
	getFilteredRowModel,
	getPaginationRowModel,
	getSortedRowModel,
	useReactTable,
} from "@tanstack/react-table"
import { parseAsArrayOf, parseAsInteger, parseAsString, useQueryState, useQueryStates } from "nuqs"

import { getSortingStateParser } from "../lib/parsers"
import { useDebouncedCallback } from "./use-debounced-callback"

interface UseDataTableProps<TData>
	extends Omit<
			TableOptions<TData>,
			| "pageCount"
			| "getCoreRowModel"
			| "manualFiltering"
			| "manualPagination"
			| "manualSorting"
		>,
		Required<Pick<TableOptions<TData>, "pageCount">> {
	/**
	 * Defines filter fields for the table. Supports both dynamic faceted filters and search filters.
	 * - Faceted filters are rendered when `options` are provided for a filter field.
	 * - Otherwise, search filters are rendered.
	 *
	 * The indie filter field `value` represents the corresponding column name in the database table.
	 * @default []
	 * @type { label: string, value: keyof TData, placeholder?: string, options?: { label: string, value: string, icon?: React.ComponentType<{ className?: string }> }[] }[]
	 * @example
	 * ```ts
	 * // Render a search filter
	 * const filterFields = [
	 *   { label: "Title", value: "title", placeholder: "Search titles" }
	 * ];
	 * // Render a faceted filter
	 * const filterFields = [
	 *   {
	 *     label: "Status",
	 *     value: "status",
	 *     options: [
	 *       { label: "Todo", value: "todo" },
	 *       { label: "In Progress", value: "in-progress" },
	 *     ]
	 *   }
	 * ];
	 * ```
	 */
	filterFields?: DataTableFilterField<TData>[]

	/**
	 * Determines how query updates affect history.
	 * `push` creates a new history entry; `replace` (default) updates the current entry.
	 * @default "replace"
	 */
	history?: "push" | "replace"

	/**
	 * Indicates whether the page should scroll to the top when the URL changes.
	 * @default false
	 */
	scroll?: boolean

	/**
	 * Shallow mode keeps query states client-side, avoiding server calls.
	 * Setting to `false` triggers a network request with the updated querystring.
	 * @default true
	 */
	shallow?: boolean

	/**
	 * Maximum time (ms) to wait between URL query string updates.
	 * Helps with browser rate-limiting. Minimum effective value is 50ms.
	 * @default 50
	 */
	throttleMs?: number

	/**
	 * Debounce time (ms) for filter updates to enhance performance during rapid input.
	 * @default 300
	 */
	debounceMs?: number

	/**
	 * Observe Server Component loading states for non-shallow updates.
	 * Pass `startTransition` from `React.useTransition()`.
	 * Sets `shallow` to `false` automatically.
	 * So shallow: true` and `startTransition` cannot be used at the same time.
	 * @see https://react.dev/reference/react/useTransition
	 */
	startTransition?: React.TransitionStartFunction

	/**
	 * Clear URL query key-value pair when state is set to default.
	 * Keep URL meaning consistent when defaults change.
	 * @default false
	 */
	clearOnDefault?: boolean

	/**
	 * Enable notion like column filters.
	 * Advanced filters and column filters cannot be used at the same time.
	 * @default false
	 * @type boolean
	 */
	enableAdvancedFilter?: boolean

	initialState?: Omit<Partial<TableState>, "sorting"> & {
		// Extend to make the sorting id typesafe
		sorting?: ExtendedSortingState<TData>
	}
}

export function useDataTable<TData>({
	pageCount = -1,
	filterFields = [],
	enableAdvancedFilter = false,
	history = "replace",
	scroll = false,
	shallow = true,
	throttleMs = 50,
	debounceMs = 300,
	clearOnDefault = false,
	startTransition,
	initialState,
	state = {},
	...props
}: UseDataTableProps<TData>) {
	const queryStateOptions = React.useMemo<Omit<UseQueryStateOptions<string>, "parse">>(() => {
		return {
			history,
			scroll,
			shallow,
			throttleMs,
			debounceMs,
			clearOnDefault,
			startTransition,
		}
	}, [history, scroll, shallow, throttleMs, debounceMs, clearOnDefault, startTransition])

	const [rowSelection, setRowSelection] = React.useState<RowSelectionState>(
		initialState?.rowSelection ?? {}
	)
	const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>(
		initialState?.columnVisibility ?? {}
	)

	const [page, setPage] = useQueryState(
		"page",
		parseAsInteger.withOptions(queryStateOptions).withDefault(1)
	)
	const [perPage, setPerPage] = useQueryState(
		"perPage",
		parseAsInteger
			.withOptions(queryStateOptions)
			.withDefault(initialState?.pagination?.pageSize ?? 10)
	)
	const [sorting, setSorting] = useQueryState(
		"sort",
		getSortingStateParser<TData>()
			.withOptions(queryStateOptions)
			.withDefault(initialState?.sorting ?? [])
	)

	// Create parsers for each filter field
	const filterParsers = React.useMemo(() => {
		return filterFields.reduce<Record<string, Parser<string> | Parser<string[]>>>(
			(acc, field) => {
				if (field.options) {
					// Faceted filter
					acc[field.id] = parseAsArrayOf(parseAsString, ",").withOptions(
						queryStateOptions
					)
				} else {
					// Search filter
					acc[field.id] = parseAsString.withOptions(queryStateOptions)
				}
				return acc
			},
			{}
		)
	}, [filterFields, queryStateOptions])

	const [filterValues, setFilterValues] = useQueryStates(filterParsers)

	const debouncedSetFilterValues = useDebouncedCallback((values: typeof filterValues) => {
		void setPage(1)
		void setFilterValues(values)
	}, debounceMs)

	// Paginate
	const pagination: PaginationState = {
		pageIndex: page - 1, // zero-based index -> one-based index
		pageSize: perPage,
	}

	function onPaginationChange(updaterOrValue: Updater<PaginationState>) {
		if (typeof updaterOrValue === "function") {
			const newPagination = updaterOrValue(pagination)
			void setPage(newPagination.pageIndex + 1)
			void setPerPage(newPagination.pageSize)
		} else {
			void setPage(updaterOrValue.pageIndex + 1)
			void setPerPage(updaterOrValue.pageSize)
		}
	}

	// Sort
	function onSortingChange(updaterOrValue: Updater<SortingState>) {
		if (typeof updaterOrValue === "function") {
			const newSorting = updaterOrValue(sorting) as ExtendedSortingState<TData>
			void setSorting(newSorting)
		}
	}

	// Filter
	const initialColumnFilters: ColumnFiltersState = React.useMemo(() => {
		return enableAdvancedFilter
			? []
			: Object.entries(filterValues).reduce<ColumnFiltersState>((filters, [key, value]) => {
					if (value !== null) {
						filters.push({
							id: key,
							value: Array.isArray(value) ? value : [value],
						})
					}
					return filters
				}, [])
	}, [filterValues, enableAdvancedFilter])

	const [columnFilters, setColumnFilters] =
		React.useState<ColumnFiltersState>(initialColumnFilters)

	// Memoize computation of searchableColumns and filterableColumns
	const { searchableColumns, filterableColumns } = React.useMemo(() => {
		return enableAdvancedFilter
			? { searchableColumns: [], filterableColumns: [] }
			: {
					searchableColumns: filterFields.filter((field) => !field.options),
					filterableColumns: filterFields.filter((field) => field.options),
				}
	}, [filterFields, enableAdvancedFilter])

	const onColumnFiltersChange = React.useCallback(
		(updaterOrValue: Updater<ColumnFiltersState>) => {
			// Don't process filters if advanced filtering is enabled
			if (enableAdvancedFilter) return

			setColumnFilters((prev) => {
				const next =
					typeof updaterOrValue === "function" ? updaterOrValue(prev) : updaterOrValue

				const filterUpdates = next.reduce<Record<string, string | string[] | null>>(
					(acc, filter) => {
						if (searchableColumns.find((col) => col.id === filter.id)) {
							// For search filters, use the value directly
							acc[filter.id] = filter.value as string
						} else if (filterableColumns.find((col) => col.id === filter.id)) {
							// For faceted filters, use the array of values
							acc[filter.id] = filter.value as string[]
						}
						return acc
					},
					{}
				)

				for (const prevFilter of prev) {
					if (!next.some((filter) => filter.id === prevFilter.id)) {
						filterUpdates[prevFilter.id] = null
					}
				}

				debouncedSetFilterValues(filterUpdates)
				return next
			})
		},
		[debouncedSetFilterValues, enableAdvancedFilter, filterableColumns, searchableColumns]
	)

	const table = useReactTable({
		...props,
		initialState,
		pageCount,
		state: {
			pagination,
			sorting,
			columnVisibility,
			rowSelection,
			columnFilters: enableAdvancedFilter ? [] : columnFilters,
			...state,
		},
		enableRowSelection: true,
		onRowSelectionChange: props.onRowSelectionChange ?? setRowSelection,
		onPaginationChange: props.onPaginationChange ?? onPaginationChange,
		onSortingChange: props.onSortingChange ?? onSortingChange,
		onColumnFiltersChange,
		onColumnVisibilityChange: setColumnVisibility,
		getCoreRowModel: getCoreRowModel(),
		getFilteredRowModel: enableAdvancedFilter ? undefined : getFilteredRowModel(),
		getPaginationRowModel: getPaginationRowModel(),
		getSortedRowModel: getSortedRowModel(),
		getFacetedRowModel: enableAdvancedFilter ? undefined : getFacetedRowModel(),
		getFacetedUniqueValues: enableAdvancedFilter ? undefined : getFacetedUniqueValues(),
		manualPagination: true,
		manualSorting: true,
		manualFiltering: true,
	})

	return { table }
}
