import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/24/solid";
import { useCallback, useState } from "react";
import Button from "../button/button";
import IconButton from "../icon-button/icon-button";
import Typography from "../typography/typography";

interface UsePaginationProps {
    count?: number;
    defaultPage?: number;
    page?: number;
    onChange?: (page: number) => void;
    boundaryCount?: number;
    siblingCount?: number;
}

interface UsePaginationReturnType {
    items: {
        page?: number;
        type: "page" | "start-ellipsis" | "end-ellipsis";
        selected?: boolean;
        goToThisPage?: () => void;
    }[];
    currentPage: number;
    goToNextPage: () => void;
    goToPrevPage: () => void;
    goToFirstPage: () => void;
    goToLastPage: () => void;
    goToPage: (page: number) => void;
    prevPageExists: boolean;
    nextPageExists: boolean;
}

/* eslint-disable-next-line */
interface PaginationProps extends UsePaginationProps {
    shape?: React.ComponentProps<typeof Button>["shape"];
}

const range = (start: number, stop: number, step = 1) =>
    Array(Math.ceil((stop - start) / step))
        .fill(start)
        .map((x, y) => x + y * step);

export function usePagination({
    boundaryCount = 1,
    count = 1,
    defaultPage = 1,
    onChange,
    page,
    siblingCount = 1,
}: UsePaginationProps): UsePaginationReturnType {
    const [internalPageIndex, setInternalPageIndex] = useState(defaultPage);

    const pageIndex = page ?? internalPageIndex;
    const goToPage = onChange ?? setInternalPageIndex;
    const initialPageIndex = 0;
    const finalPageIndex = count - 1;
    const prevPageExists = pageIndex > initialPageIndex;
    const nextPageExists = pageIndex < finalPageIndex;
    const prevPageIndex = prevPageExists ? pageIndex - 1 : pageIndex;
    const nextPageIndex = nextPageExists ? pageIndex + 1 : pageIndex;
    const goToNextPage = useCallback(
        () => goToPage(nextPageIndex),
        [goToPage, nextPageIndex]
    );
    const goToPrevPage = useCallback(
        () => goToPage(prevPageIndex),
        [goToPage, prevPageIndex]
    );
    const goToFirstPage = useCallback(
        () => goToPage(initialPageIndex),
        [goToPage, initialPageIndex]
    );
    const goToLastPage = useCallback(
        () => goToPage(finalPageIndex),
        [goToPage, finalPageIndex]
    );

    const createItem = useCallback(
        (page: number) => ({
            page,
            type: "page",
            selected: page === pageIndex,
            goToThisPage: () => goToPage(page),
        }),
        [goToPage, pageIndex]
    );

    let items = [];
    const minLength = 2 * boundaryCount + 2 * siblingCount + 3;
    if (count <= minLength) {
        items = range(initialPageIndex, initialPageIndex + count).map(
            createItem
        );
    } else {
        const startingArea =
            initialPageIndex + boundaryCount + siblingCount + 1;
        const endingArea = finalPageIndex - boundaryCount - siblingCount - 1;
        const isIndexNearStarting =
            pageIndex >= initialPageIndex && pageIndex <= startingArea;
        const isIndexNearEnding =
            pageIndex <= finalPageIndex && pageIndex >= endingArea;

        if (isIndexNearStarting) {
            items = [
                ...range(initialPageIndex, startingArea + siblingCount + 1).map(
                    createItem
                ),
                { type: "end-ellipsis" },
                ...range(
                    finalPageIndex - boundaryCount + 1,
                    finalPageIndex + 1
                ).map(createItem),
            ];
        } else if (isIndexNearEnding) {
            items = [
                ...range(
                    initialPageIndex,
                    initialPageIndex + boundaryCount
                ).map(createItem),
                { type: "start-ellipsis" },
                ...range(endingArea - siblingCount, finalPageIndex + 1).map(
                    createItem
                ),
            ];
        } else {
            items = [
                ...range(
                    initialPageIndex,
                    initialPageIndex + boundaryCount
                ).map(createItem),
                { type: "start-ellipsis" },
                ...range(
                    pageIndex - siblingCount,
                    pageIndex + siblingCount + 1
                ).map(createItem),
                { type: "end-ellipsis" },
                ...range(
                    finalPageIndex - boundaryCount + 1,
                    finalPageIndex + 1
                ).map(createItem),
            ];
        }
    }

    return {
        items,
        currentPage: pageIndex,
        goToFirstPage,
        goToLastPage,
        goToNextPage,
        goToPage,
        goToPrevPage,
        prevPageExists,
        nextPageExists,
    } as UsePaginationReturnType;
}

export function Pagination({
    boundaryCount = 1,
    count = 1,
    defaultPage = 0,
    onChange,
    page,
    siblingCount = 1,
    shape = "square",
}: PaginationProps) {
    const {
        items,
        prevPageExists,
        nextPageExists,
        currentPage,
        goToNextPage,
        goToPrevPage,
    } = usePagination({
        page,
        onChange,
        count,
        boundaryCount,
        defaultPage,
        siblingCount,
    });

    return (
        <div className="px-6 py-4">
            <div className="hidden sm:flex items-center gap-2 justify-between">
                <Button
                    label="Previous"
                    color="gray"
                    variant="outlined"
                    icon="arrow-left"
                    onClick={goToPrevPage}
                    disabled={!prevPageExists}
                />
                <div className="hidden sm:flex items-end">
                    {items.map(({ page, type, selected, goToThisPage }) => {
                        return type === "start-ellipsis" ||
                            type === "end-ellipsis" ? (
                            <p className="w-10 h-10 flex items-center justify-center">
                                ..
                            </p>
                        ) : (
                            <Button
                                label={`${(page ?? 0) + 1}`}
                                onClick={goToThisPage}
                                color="gray"
                                variant="text"
                                shape={shape}
                                className={`${
                                    selected ? "bg-gray-50" : ""
                                } w-10 h-10`}
                            />
                        );
                    })}
                </div>
                <Button
                    label="Next"
                    color="gray"
                    variant="outlined"
                    endIcon="arrow-right"
                    onClick={goToNextPage}
                    disabled={!nextPageExists}
                />
            </div>
            <div className="flex items-center gap-2 justify-between sm:hidden">
                <IconButton
                    color="gray"
                    variant="outlined"
                    icon="arrow-left"
                    onClick={goToPrevPage}
                    disabled={!prevPageExists}
                />
                <Typography color="gray-700" size="sm">
                    Page {currentPage + 1} of {count}
                </Typography>
                <IconButton
                    color="gray"
                    variant="outlined"
                    icon="arrow-right"
                    onClick={goToNextPage}
                    disabled={!nextPageExists}
                />
            </div>
        </div>
    );
}

export default Pagination;
