// Cursor-based pagination helpers — CS 499 Enhancement Two.
//
// Why cursors instead of page numbers: offset pagination (?page=4)
// re-counts skipped records on every request and produces duplicated
// or missing rows when the underlying data changes between pages. A
// cursor encodes WHERE the previous page ended (the last item's sort
// value and its unique trip code as a tiebreaker), so the next page
// resumes after that position deterministically.
//
// Security notes (Course Outcome 5):
//   - Cursors are validated structurally after decoding; a malformed
//     or truncated cursor produces a clean 400, never a crash.
//   - The cursor pins the sort/search context it was issued for. A
//     cursor replayed against different query parameters is rejected,
//     which prevents inconsistent page stitching.

/** Encodes the resume position as URL-safe base64 JSON. */
function encodeCursor(context, lastItem) {
    const payload = {
        s: context.sortBy,
        o: context.order,
        q: context.query,
        v: lastItem.sortValue,
        c: lastItem.code
    };
    return Buffer.from(JSON.stringify(payload)).toString('base64url');
}

/**
 * Decodes and validates a cursor against the current query context.
 * @returns {{ value: any, code: string }} resume position
 * @throws {Error} with .statusCode = 400 on any invalid cursor
 */
function decodeCursor(rawCursor, context) {
    let payload;
    try {
        payload = JSON.parse(Buffer.from(String(rawCursor), 'base64url').toString('utf8'));
    } catch {
        throw invalid('Malformed pagination cursor');
    }
    if (payload === null || typeof payload !== 'object' ||
        typeof payload.c !== 'string' || !('v' in payload)) {
        throw invalid('Malformed pagination cursor');
    }
    if (payload.s !== context.sortBy || payload.o !== context.order ||
        payload.q !== context.query) {
        throw invalid('Cursor does not match the current query parameters');
    }
    return { value: payload.v, code: payload.c };
}

function invalid(message) {
    const error = new Error(message);
    error.statusCode = 400;
    return error;
}

module.exports = { encodeCursor, decodeCursor };
