import { v1ErrorBody } from './v1Errors';
import { NextResponse } from 'next/server';

/**
 * Cursor pagination helpers for the public REST API.
 *
 * The public surface uses an opaque `cursor` token plus `limit` (no `page`,
 * `total`, or `pages` fields exposed). Internally we still call the existing
 * page-based service functions for now — the cursor encodes the next page
 * number. Treating it as opaque means we can swap the underlying mechanism
 * later (true keyset pagination) without breaking SDK consumers.
 *
 * Cursor format: base64url-encoded JSON `{"p": <pageNumber>}`. Reject any
 * other shape so a malformed/forged cursor returns a clean 400.
 */

export const DEFAULT_LIMIT = 20;
export const MAX_LIMIT = 100;

export type CursorPage = { page: number; limit: number };

function b64urlDecode(s: string): string {
  const pad = s.length % 4 === 0 ? '' : '='.repeat(4 - (s.length % 4));
  const norm = s.replace(/-/g, '+').replace(/_/g, '/') + pad;
  return Buffer.from(norm, 'base64').toString('utf8');
}

function b64urlEncode(s: string): string {
  return Buffer.from(s, 'utf8').toString('base64')
    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

export function encodeCursor(page: number): string {
  return b64urlEncode(JSON.stringify({ p: page }));
}

export class CursorError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'CursorError';
  }
}

export function decodeCursor(token: string | null | undefined): number {
  if (!token) return 1;
  try {
    const decoded = JSON.parse(b64urlDecode(token));
    if (typeof decoded?.p !== 'number' || !Number.isInteger(decoded.p) || decoded.p < 1) {
      throw new CursorError('Invalid cursor payload');
    }
    return decoded.p;
  } catch (e) {
    if (e instanceof CursorError) throw e;
    throw new CursorError('Invalid or malformed cursor');
  }
}

/**
 * Parse the standard `cursor` and `limit` query params for v1 list endpoints.
 * Returns the resolved page (1-indexed for the underlying offset query) and
 * a clamped limit.
 */
export function parseListParams(url: URL, defaultLimit = DEFAULT_LIMIT, maxLimit = MAX_LIMIT): CursorPage {
  const cursor = url.searchParams.get('cursor');
  const page = decodeCursor(cursor);
  const limitRaw = Number(url.searchParams.get('limit') ?? defaultLimit);
  const limit = Number.isFinite(limitRaw) && limitRaw > 0
    ? Math.min(maxLimit, Math.floor(limitRaw))
    : defaultLimit;
  return { page, limit };
}

/**
 * Build the standard `{ data, next_cursor }` v1 list envelope. We expose
 * neither the page number nor the total count — `next_cursor` is the only
 * paging signal a public client should rely on.
 */
export function buildListEnvelope<T>(
  rows: T[],
  page: number,
  limit: number
): { data: T[]; next_cursor: string | null } {
  const hasMore = rows.length === limit;
  return {
    data: rows,
    next_cursor: hasMore ? encodeCursor(page + 1) : null,
  };
}

export function cursorErrorResponse(err: CursorError): NextResponse {
  return NextResponse.json(
    v1ErrorBody(err.message, 'VALIDATION_ERROR', { fields: { cursor: [err.message] } }),
    { status: 400 }
  );
}
