export type BatchRequest = {
	key: string;
	endpoint: string;
	params: Record<string, string>;
};

export type BatchResult = {
	data: any;
	total: number;
	totalPages: number;
};

export type BatchTransport = (
	requests: BatchRequest[]
) => Promise<Record<string, BatchResult>>;

export class BatchFetchContext {
	private requests: BatchRequest[] = [];
	private pendingResolvers = new Map<
		string,
		Array<{ resolve: (value: BatchResult) => void; reject: (reason?: any) => void }>
	>();
	private flushed = false;

	constructor(private transport: BatchTransport) {}

	/**
	 * Called by getData functions to register a request.
	 * Returns a promise that resolves after flush() completes.
	 */
	fetch(
		endpoint: string,
		params: Record<string, string> = {}
	): Promise<BatchResult> {
		const key = `${endpoint}?${new URLSearchParams(params).toString()}`;

		if (this.flushed) {
			// Late registration — batch already sent, fall back to individual request
			return this.transport([{ key, endpoint, params }]).then(
				(r) => r[key]
			);
		}

		// Deduplicate: same endpoint+params shares the same promise resolution
		if (!this.pendingResolvers.has(key)) {
			this.requests.push({ key, endpoint, params });
			this.pendingResolvers.set(key, []);
		}

		return new Promise((resolve, reject) => {
			this.pendingResolvers.get(key)!.push({ resolve, reject });
		});
	}

	/**
	 * Called after all getData functions have registered their requests.
	 * Sends a single batch request via the transport and resolves all pending promises.
	 */
	async flush(): Promise<void> {
		this.flushed = true;
		if (this.requests.length === 0) return;

		try {
			const results = await this.transport(this.requests);
			for (const [key, resolvers] of this.pendingResolvers) {
				const result = results[key];
				if (result) {
					for (const { resolve } of resolvers) {
						resolve(result);
					}
				} else {
					for (const { reject } of resolvers) {
						reject(new Error(`No batch result for key: ${key}`));
					}
				}
			}
		} catch (error) {
			for (const [, resolvers] of this.pendingResolvers) {
				for (const { reject } of resolvers) {
					reject(error);
				}
			}
		}
	}
}
