import * as React from "react";
import type { PaginationContext, PaginationEvent } from "./types";
import { getTotalPages, getTransformedRange } from "./utils";

function pageReducer(state: PaginationContext, event: PaginationEvent) {
  const totalPages = getTotalPages(state);
  switch (event.type) {
    case "SET_COUNT":
      if (event.count < 0 || event.count === state.count) {
        return state;
      }
      return { ...state, count: event.count };
    case "SET_PAGE":
      if (
        event.page < 1 ||
        event.page > totalPages ||
        event.page === state.page
      ) {
        return state;
      }
      return { ...state, page: event.page };
    case "SET_PAGE_SIZE":
      return { ...state, pageSize: event.size };
    case "FIRST_PAGE":
      return { ...state, page: 1 };
    case "LAST_PAGE":
      return { ...state, page: totalPages };
    case "PREVIOUS_PAGE":
      return { ...state, page: Math.max(state.page - 1, 1) };
    case "NEXT_PAGE":
      return {
        ...state,
        page: Math.min(state.page + 1, totalPages),
      };
  }
}

const PaginationContext = React.createContext<PaginationContext>(null!);
const PaginationDispatch = React.createContext<React.Dispatch<PaginationEvent>>(
  null!,
);
export function Pagination({
  count = 0,
  pageSize = 25,
  siblingCount = 1,
  page = 1,
  onPageChange,
  ...props
}: {
  count: number;
  pageSize: number;
  siblingCount: number;
  page: number;
  onPageChange: (page: number) => void;
} & React.ComponentPropsWithRef<"div">) {
  const [state, send] = React.useReducer(pageReducer, {
    count: count ?? 0,
    pageSize: pageSize ?? 25,
    siblingCount: siblingCount ?? 1,
    page: page ?? 1,
  });
  React.useEffect(() => {
    if (count != null && count !== state.count) {
      send({ type: "SET_COUNT", count: count });
    }
  }, [count]);
  React.useEffect(() => {
    if (pageSize != null && pageSize !== state.pageSize) {
      send({ type: "SET_PAGE_SIZE", size: pageSize });
    }
  }, [pageSize]);
  React.useEffect(() => {
    if (page != null && state.page !== page) {
      send({ type: "SET_PAGE", page });
    }
  }, [page]);
  React.useEffect(() => {
    onPageChange?.(state.page);
  }, [state.page]);
  return (
    <PaginationDispatch value={send}>
      <PaginationContext value={state}>
        <div {...props} />
      </PaginationContext>
    </PaginationDispatch>
  );
}
Pagination.PrevTrigger = PrevTrigger;
Pagination.NextTrigger = NextTrigger;
Pagination.PageList = PageList;

function PrevTrigger(props: React.ComponentPropsWithRef<"button">) {
  const ctx = React.useContext(PaginationContext);
  const send = React.use(PaginationDispatch);
  return (
    <button
      {...props}
      disabled={ctx.page === 1}
      onClick={() => send({ type: "PREVIOUS_PAGE" })}
    />
  );
}

function NextTrigger(props: React.ComponentPropsWithRef<"button">) {
  const ctx = React.useContext(PaginationContext);
  const send = React.use(PaginationDispatch);
  const hasNextPage = ctx.page < getTotalPages(ctx);
  return (
    <button
      {...props}
      disabled={!hasNextPage}
      onClick={() => send({ type: "NEXT_PAGE" })}
    />
  );
}

type Classes = {
  item?: string;
  ellipsis?: string;
};
function PageList({
  classes = {},
  ...props
}: {
  classes: Classes;
} & React.ComponentPropsWithRef<"div">) {
  const ctx = React.useContext(PaginationContext);
  const send = React.use(PaginationDispatch);
  const range = React.useMemo(() => getTransformedRange(ctx), [ctx]);
  return (
    <div {...props}>
      {range.map(
        (
          page: { type: "page"; value: number } | { type: "ellipsis" },
          index: number,
        ) =>
          page.type === "page" ? (
            <button
              key={index}
              data-active={ctx.page === page.value ? "true" : undefined}
              className={classes?.item}
              onClick={() => {
                send({ type: "SET_PAGE", page: page.value });
              }}
            >
              {page.value}
            </button>
          ) : (
            <div key={index} className={classes?.ellipsis}>
              &#8230;
            </div>
          ),
      )}
    </div>
  );
}
