import { z } from "zod";

// ---------------------------------------------------------------------------
// Input schema — merge into any tRPC procedure input with `.merge()` or spread
// ---------------------------------------------------------------------------

export const PaginationInputSchema = z.object({
  page: z.number().int().min(1).default(1),
  limit: z.number().int().min(1).max(500).default(50),
  cursor: z.string().optional(),
});

export type PaginationInput = z.infer<typeof PaginationInputSchema>;

// ---------------------------------------------------------------------------
// Result envelope
// ---------------------------------------------------------------------------

export interface PaginatedResult<T> {
  items: T[];
  total: number;
  page: number;
  limit: number;
  nextCursor: string | null;
}

// ---------------------------------------------------------------------------
// Core paginate helper
// ---------------------------------------------------------------------------

/**
 * Run a Prisma-style `findMany` + `count` with the project's standard
 * offset+cursor hybrid pagination and the `take: limit + 1` sentinel trick.
 *
 * @param findMany  Prisma `findMany` (or compatible) that receives `{ skip, take }`.
 *                  The caller is responsible for applying `where`/`orderBy`/`include`
 *                  and for injecting the cursor condition into `where` when
 *                  `cursorWhere` is provided.
 * @param count     Prisma `count` for the *base* where clause (without cursor).
 * @param input     The validated pagination input fields.
 * @param getId     Optional — extract the cursor id from the last item.
 *                  Defaults to `item.id` for items that have an `id: string`.
 */
export async function paginate<T>(
  findMany: (args: { skip: number; take: number }) => Promise<T[]>,
  count: () => Promise<number>,
  input: PaginationInput,
  getId: (item: T) => string = (item) => (item as unknown as { id: string }).id,
): Promise<PaginatedResult<T>> {
  const page = input.page ?? 1;
  const limit = input.limit ?? 50;
  const skip = input.cursor ? 0 : (page - 1) * limit;

  const [rawItems, total] = await Promise.all([
    findMany({ skip, take: limit + 1 }),
    count(),
  ]);

  const hasMore = rawItems.length > limit;
  const items = hasMore ? rawItems.slice(0, limit) : rawItems;
  const nextCursor = hasMore && items.length > 0 ? getId(items[items.length - 1]!) : null;

  return { items, total, page, limit, nextCursor };
}

// ---------------------------------------------------------------------------
// Cursor-only variant (no total count, lighter)
// ---------------------------------------------------------------------------

export interface CursorResult<T> {
  items: T[];
  nextCursor: string | null;
}

export const CursorInputSchema = z.object({
  limit: z.number().int().min(1).max(500).default(50),
  cursor: z.string().optional(),
});

export type CursorInput = z.infer<typeof CursorInputSchema>;

/**
 * Cursor-only pagination without a total count query.
 * Useful for large tables or infinite-scroll UIs that don't need a total.
 */
export async function paginateCursor<T>(
  findMany: (args: { take: number }) => Promise<T[]>,
  input: CursorInput,
  getId: (item: T) => string = (item) => (item as unknown as { id: string }).id,
): Promise<CursorResult<T>> {
  const limit = input.limit ?? 50;

  const rawItems = await findMany({ take: limit + 1 });

  const hasMore = rawItems.length > limit;
  const items = hasMore ? rawItems.slice(0, limit) : rawItems;
  const nextCursor = hasMore && items.length > 0 ? getId(items[items.length - 1]!) : null;

  return { items, nextCursor };
}
