"use client";

import { IMetaData } from "@/lib/api/types/Factory";
import { AxiosError } from "axios";
import { useCallback, useEffect, useMemo, useState } from "react";

const isInvalidCursorError = (error: unknown) => {
  const axiosError = error as AxiosError<{ error?: { message?: string } }>;
  if (axiosError.response?.status !== 400) return false;
  const message = axiosError.response?.data?.error?.message ?? "";
  return /cursor|pagination/i.test(message);
};

type UseHybridPaginationOptions = {
  meta?: IMetaData;
  params: Record<string, unknown>;
  setParams: React.Dispatch<React.SetStateAction<Record<string, any>>>;
  isError?: boolean;
  error?: unknown;
};

/** Each push records the absolute page we were on when this cursor was added,
 * so a later pop can fall back to that page via offset if needed. */
type CursorStackEntry = { cursor: string; fromPage: number };

export const useHybridPagination = ({
  meta,
  params,
  setParams,
  isError,
  error,
}: UseHybridPaginationOptions) => {
  const [cursorStack, setCursorStack] = useState<CursorStackEntry[]>([]);
  const [lastValidPages, setLastValidPages] = useState<number>(1);
  const [lastValidLimit, setLastValidLimit] = useState<number>(10);

  const limit = meta?.limit ?? Number(params.limit) ?? lastValidLimit;

  useEffect(() => {
    if (meta?.limit != null) {
      setLastValidLimit(meta.limit);
    }
  }, [meta?.limit]);

  useEffect(() => {
    if (meta?.pages != null) {
      setLastValidPages(meta.pages);
    }
  }, [meta?.pages]);

  const clearCursorStack = useCallback(() => {
    setCursorStack([]);
  }, []);

  const resetToFirstPage = useCallback(() => {
    clearCursorStack();
    setParams((current) => {
      const next: Record<string, any> = { ...current, offset: 0, limit };
      delete next.cursor;
      return next;
    });
  }, [clearCursorStack, limit, setParams]);

  useEffect(() => {
    if (!isError || !error || !isInvalidCursorError(error)) return;
    resetToFirstPage();
  }, [isError, error, resetToFirstPage]);

  const hasNext = Boolean(meta?.has_more && meta?.next_cursor);

  /** 0-based page index; do not trust meta.page alone while cursor pages are active. */
  const currentPageIndex = useMemo(() => {
    const pageSize = limit > 0 ? limit : 1;

    if (cursorStack.length > 0) {
      const last = cursorStack[cursorStack.length - 1];
      return Math.max(0, last.fromPage - 1 + cursorStack.length);
    }

    const paramsCursor =
      typeof params.cursor === "string" ? params.cursor.trim() : "";
    const paramsOffset = Number(params.offset);
    if (!paramsCursor && Number.isFinite(paramsOffset) && paramsOffset >= 0) {
      return Math.floor(paramsOffset / pageSize);
    }

    const metaOffset = Number(meta?.offset);
    if (Number.isFinite(metaOffset) && metaOffset >= 0) {
      return Math.floor(metaOffset / pageSize);
    }

    if (meta?.page != null) return meta.page - 1;
    return 0;
  }, [cursorStack, params.cursor, params.offset, meta?.offset, meta?.page, limit]);

  const hasPrevious = cursorStack.length > 0 || currentPageIndex > 0;

  const pagination = useMemo(
    () => ({
      currentPage: currentPageIndex,
      totalPages: meta?.pages ?? lastValidPages,
    }),
    [currentPageIndex, meta?.pages, lastValidPages],
  );

  const goToOffsetPage = useCallback(
    (pageIndex: number) => {
      clearCursorStack();
      setParams((current) => {
        const next: Record<string, any> = {
          ...current,
          offset: pageIndex * limit,
          limit,
        };
        delete next.cursor;
        return next;
      });
    },
    [clearCursorStack, limit, setParams],
  );

  const goToNextCursorPage = useCallback(
    (fromPage1Based: number) => {
      const nextCursor = meta?.next_cursor;
      if (!meta?.has_more || !nextCursor) return;

      setCursorStack((stack) => [
        ...stack,
        { cursor: nextCursor, fromPage: fromPage1Based },
      ]);
      setParams((current) => {
        const next: Record<string, any> = { ...current, cursor: nextCursor, limit };
        delete next.offset;
        return next;
      });
    },
    [limit, meta?.has_more, meta?.next_cursor, setParams],
  );

  const goToPreviousPage = useCallback(() => {
    if (cursorStack.length > 0) {
      const poppedEntry = cursorStack[cursorStack.length - 1];
      const nextStack = cursorStack.slice(0, -1);
      const previousEntry = nextStack[nextStack.length - 1];

      setCursorStack(nextStack);
      setParams((current) => {
        const next: Record<string, any> = { ...current, limit };
        if (previousEntry) {
          next.cursor = previousEntry.cursor;
          delete next.offset;
        } else if (poppedEntry.fromPage > 1) {
          next.offset = (poppedEntry.fromPage - 1) * limit;
          delete next.cursor;
        } else {
          next.offset = 0;
          delete next.cursor;
        }
        return next;
      });
      return;
    }

    if (currentPageIndex <= 0) {
      resetToFirstPage();
      return;
    }

    goToOffsetPage(currentPageIndex - 1);
  }, [
    cursorStack,
    currentPageIndex,
    goToOffsetPage,
    limit,
    resetToFirstPage,
    setParams,
  ]);

  const handlePaginationChange = useCallback(
    (e: { selected: number }) => {
      const targetIndex = e.selected;
      const delta = targetIndex - currentPageIndex;

      if (delta === 1 && hasNext) {
        goToNextCursorPage(currentPageIndex + 1);
        return;
      }

      if (delta === -1) {
        const cursorReturnIndex =
          cursorStack.length > 0
            ? cursorStack[cursorStack.length - 1].fromPage - 1
            : null;

        if (
          cursorReturnIndex != null &&
          targetIndex === cursorReturnIndex &&
          cursorStack.length > 0
        ) {
          goToPreviousPage();
          return;
        }

        if (targetIndex >= 0) {
          goToOffsetPage(targetIndex);
          return;
        }
      }

      goToOffsetPage(targetIndex);
    },
    [
      currentPageIndex,
      cursorStack,
      goToNextCursorPage,
      goToOffsetPage,
      goToPreviousPage,
      hasNext,
    ],
  );

  const buildFirstPageParams = useCallback(
    (base: Record<string, any>) => {
      const next: Record<string, any> = { ...base, offset: 0, limit };
      delete next.cursor;
      clearCursorStack();
      return next;
    },
    [clearCursorStack, limit],
  );

  return {
    pagination,
    handlePaginationChange,
    buildFirstPageParams,
    resetToFirstPage,
    clearCursorStack,
    currentPageIndex,
    hasNext,
    hasPrevious,
  };
};
