"use client";
import { useState, Ref, useImperativeHandle, useEffect, useCallback } from "react";
import SortableTable, { Pagination, RowsPerPage, SortableTableProps } from "./Table";
import { PrismaClient } from "@repo/db";
import { getData } from "./pagiantedTableActions";
import { cn, useDebounce } from "@repo/shared-components";

export interface PaginatedTableRef {
	refresh: () => void;
}

interface PaginatedTableProps<TData> extends Omit<SortableTableProps<TData>, "data"> {
	prismaModel: keyof PrismaClient;
	stickyHeaders?: boolean;
	filter?: Record<string, unknown>;
	initialRowsPerPage?: number;
	searchFields?: string[];
	include?: Record<string, boolean>;
	strictQuery?: boolean;
	leftOfSearch?: React.ReactNode;
	rightOfSearch?: React.ReactNode;
	leftOfPagination?: React.ReactNode;
	hide?: boolean;
	ref?: Ref<PaginatedTableRef>;
}

export function PaginatedTable<TData>({
	prismaModel,
	initialRowsPerPage = 30,
	searchFields = [],
	filter,
	include,
	ref,
	strictQuery = false,
	stickyHeaders = false,
	leftOfSearch,
	rightOfSearch,
	leftOfPagination,
	hide,
	...restProps
}: PaginatedTableProps<TData>) {
	const [data, setData] = useState<TData[]>([]);
	const [rowsPerPage, setRowsPerPage] = useState(initialRowsPerPage);
	const [page, setPage] = useState(0);
	const [total, setTotal] = useState(0);
	const [searchTerm, setSearchTerm] = useState("");
	const [orderBy, setOrderBy] = useState<Record<string, "asc" | "desc">>(
		restProps.initialOrderBy
			? restProps.initialOrderBy.reduce(
					(acc, sort) => {
						acc[sort.id] = sort.desc ? "desc" : "asc";
						return acc;
					},
					{} as Record<string, "asc" | "desc">,
				)
			: {},
	);
	const [loading, setLoading] = useState(false);

	const refreshTableData = useCallback(async () => {
		setLoading(true);
		getData(
			prismaModel,
			rowsPerPage,
			page * rowsPerPage,
			searchTerm,
			searchFields,
			filter,
			include,
			orderBy,
			strictQuery
				? restProps.columns
						.filter(
							(col): col is { accessorKey: string } =>
								typeof (col as { accessorKey?: unknown }).accessorKey === "string",
						)
						.map((col) => col.accessorKey)
						.reduce<Record<string, boolean>>((acc, key) => {
							acc[key] = true;
							return acc;
						}, {})
				: undefined,
		)
			.then((result) => {
				if (result) {
					setData(result.data);
					setTotal(result.total);
				}
			})
			.finally(() => {
				setLoading(false);
			});
	}, [
		prismaModel,
		rowsPerPage,
		page,
		searchTerm,
		searchFields,
		filter,
		include,
		orderBy,
		strictQuery,
		restProps.columns,
	]);

	useImperativeHandle(ref, () => ({
		refresh: () => {
			refreshTableData();
		},
	}));

	// useEffect to show loading spinner
	useEffect(() => {
		setLoading(true);
	}, [searchTerm, page, rowsPerPage, orderBy, filter, setLoading]);

	useDebounce(
		() => {
			refreshTableData();
		},
		500,
		[searchTerm, page, rowsPerPage, orderBy, filter],
	);

	return (
		<div className="space-y-4 m-4">
			{(rightOfSearch || leftOfSearch || searchFields.length > 0) && (
				<div
					className={cn(
						"flex items-center gap-2 sticky py-2 z-20",
						stickyHeaders && "sticky top-0 bg-base-100/80 backdrop-blur border-b",
					)}
				>
					<div className="flex-1 flex gap-2">
						<div>{leftOfSearch}</div>
						<div>{loading && <span className="loading loading-dots loading-md" />}</div>
					</div>
					{searchFields.length > 0 && (
						<input
							type="text"
							placeholder="Suchen..."
							value={searchTerm}
							onChange={(e) => {
								setSearchTerm(e.target.value);
								setPage(0); // Reset to first page on search
							}}
							className="input input-bordered w-full max-w-xs justify-end"
						/>
					)}
					<div className="flex justify-center">{rightOfSearch}</div>
				</div>
			)}
			{!hide && (
				<SortableTable
					data={data}
					prismaModel={prismaModel}
					setOrderBy={setOrderBy}
					{...restProps}
				/>
			)}
			<div className="flex items-between">
				{leftOfPagination}
				{!hide && (
					<>
						<RowsPerPage rowsPerPage={rowsPerPage} setRowsPerPage={setRowsPerPage} />
						<Pagination totalPages={Math.ceil(total / rowsPerPage)} page={page} setPage={setPage} />
					</>
				)}
			</div>
		</div>
	);
}
