import React, {useCallback, useState} from "react";
import {Pagination} from "./Pagination";
import {SpinningLoader} from "../Loaders/SpinningLoader";
import {Await, useAsync} from "../../Async";

/**
 * Paginated content component with custom page handling.
 */
export function Paginate({ page, onChange, count, children }: React.PropsWithChildren<{
	/**
	 * The current page.
	 */
	page: number;

	/**
	 * Called when a new page is selected.
	 * @param newPage The newly selected page.
	 */
	onChange: (newPage: number) => void;

	/**
	 * Pages count.
	 */
	count: number;
}>)
{
	return (
		<>
			{children}

			<Pagination page={page} onChange={onChange} count={count} />
		</>
	);
}

/**
 * Paginated content component.
 */
export function AutoPaginate({ count, children }: {
	/**
	 * Pages count.
	 */
	count: number;

	/**
	 * Show the given page.
	 * @param page The page to show.
	 */
	children: (page: number) => React.ReactElement;
})
{
	// The current page.
	const [page, setPage] = useState<number>(1);

	return (
		<Paginate page={page} onChange={setPage} count={count}>
			{children(page)}
		</Paginate>
	);
}

/**
 * Asynchronous paginated content component.
 */
export function AsyncPaginate<T>({ count, getData, children }: {
	/**
	 * Get pages count.
	 */
	count: () => Promise<number>;

	/**
	 * Get data for the given page.
	 * @param page The page for which to get data.
	 */
	getData: (page: number) => Promise<T>;

	/**
	 * Show the current page with its retrieved data.
	 * @param data Data of the page to show.
	 * @param page The page to show.
	 */
	children: (data: T, page: number) => React.ReactElement;
})
{
	// Getting pages count.
	const [asyncCount] = useAsync(count, []);

	return (
		<Await async={asyncCount} fallback={<SpinningLoader />}>
			{
				(count) => (
					<AutoPaginate count={count}>
						{(page) => <AsyncPage page={page} getData={getData} render={children} />}
					</AutoPaginate>
				)
			}
		</Await>
	);
}

/**
 * An async page to render.
 */
export function AsyncPage<T>({page, getData, render}: {
	/**
	 * The page number to show.
	 */
	page: number;

	/**
	 * Get data for the given page.
	 * @param page The page for which to get data.
	 */
	getData: (page: number) => Promise<T>;

	/**
	 * Render the page with its retrieved data.
	 * @param data Data of the page to show.
	 * @param page The page to show.
	 */
	render: (data: T, page: number) => React.ReactElement;
})
{
	// Store function to get page data.
	const getPageData = useCallback(() => {
		return getData(page);
	}, [page]);

	// Getting page data.
	const [asyncPageData] = useAsync(getPageData, [getPageData]);

	return (
		<Await async={asyncPageData} fallback={<SpinningLoader />}>
			{(pageData) => render(pageData, page)}
		</Await>
	);
}
