import { badRequest } from "@/lib/api/errors";

/**
 * Cursor format (opaque for clients):
 * - base64url(JSON.stringify(payload))
 *
 * We keep the cursor extensible so we can switch provider internals later
 * (e.g. async-search context_id) without changing the public API.
 *
 * Current v1 payload shape:
 * {
 *   v: 1,
 *   mode: "sync",
 *   offset: number,
 *   contextId?: string
 * }
 */

function isPlainObject(value) {
	return Boolean(value && typeof value === "object" && !Array.isArray(value));
}

function toBase64Url(jsonString) {
	// Node.js supports "base64url" encoding out of the box.
	return Buffer.from(String(jsonString), "utf8").toString("base64url");
}

function fromBase64Url(b64) {
	return Buffer.from(String(b64), "base64url").toString("utf8");
}

/**
 * Encode a cursor payload into an opaque string.
 *
 * @param {{ v?: number, mode?: string, offset: number, contextId?: string }} payload
 * @returns {string}
 */
export function encodeCursor(payload) {
	if (!isPlainObject(payload)) {
		throw badRequest("VALIDATION_SEARCH_CURSOR", "Invalid cursor payload");
	}

	const v = payload.v ?? 1;
	const mode = payload.mode ?? "sync";
	const offset = payload.offset;

	if (!Number.isInteger(offset) || offset < 0) {
		throw badRequest("VALIDATION_SEARCH_CURSOR", "Invalid cursor payload", {
			offset,
		});
	}

	const normalized = {
		v,
		mode: String(mode),
		offset,
	};

	if (payload.contextId) normalized.contextId = String(payload.contextId);

	return toBase64Url(JSON.stringify(normalized));
}

/**
 * Decode an opaque cursor string.
 *
 * @param {string|null|undefined} cursor
 * @returns {{ v: number, mode: string, offset: number, contextId: string|null }}
 */
export function decodeCursor(cursor) {
	if (!cursor) {
		return { v: 1, mode: "sync", offset: 0, contextId: null };
	}

	if (typeof cursor !== "string" || !cursor.trim()) {
		throw badRequest("VALIDATION_SEARCH_CURSOR", "Invalid cursor");
	}

	let raw;
	try {
		raw = fromBase64Url(cursor.trim());
	} catch (err) {
		throw badRequest("VALIDATION_SEARCH_CURSOR", "Invalid cursor");
	}

	let parsed;
	try {
		parsed = JSON.parse(raw);
	} catch (err) {
		throw badRequest("VALIDATION_SEARCH_CURSOR", "Invalid cursor");
	}

	if (!isPlainObject(parsed)) {
		throw badRequest("VALIDATION_SEARCH_CURSOR", "Invalid cursor");
	}

	const v = Number(parsed.v ?? 1);
	const mode = String(parsed.mode ?? "sync");
	const offset = Number(parsed.offset);

	if (!Number.isInteger(v) || v !== 1) {
		throw badRequest("VALIDATION_SEARCH_CURSOR", "Invalid cursor");
	}

	if (!Number.isInteger(offset) || offset < 0) {
		throw badRequest("VALIDATION_SEARCH_CURSOR", "Invalid cursor");
	}

	const contextId =
		typeof parsed.contextId === "string" && parsed.contextId.trim()
			? parsed.contextId.trim()
			: null;

	return { v, mode, offset, contextId };
}
