/**
 * ClickUp API v2 client with rate limiting.
 *
 * Rate limit: 100 requests per minute per token.
 * Uses a sliding-window token bucket with automatic backoff.
 */

const CLICKUP_API = 'https://api.clickup.com/api/v2';
const RATE_LIMIT = 100;
const RATE_WINDOW_MS = 60_000;

// ── Rate limiter ──

interface RateBucket {
	timestamps: number[];
}

const buckets = new Map<string, RateBucket>();

async function waitForSlot(token: string): Promise<void> {
	let bucket = buckets.get(token);
	if (!bucket) {
		bucket = { timestamps: [] };
		buckets.set(token, bucket);
	}

	const now = Date.now();
	bucket.timestamps = bucket.timestamps.filter(t => now - t < RATE_WINDOW_MS);

	if (bucket.timestamps.length >= RATE_LIMIT) {
		const oldest = bucket.timestamps[0];
		const waitMs = RATE_WINDOW_MS - (now - oldest) + 50;
		await new Promise(r => setTimeout(r, waitMs));
		return waitForSlot(token); // retry
	}

	bucket.timestamps.push(Date.now());
}

// ── HTTP helpers ──

export class ClickUpApiError extends Error {
	constructor(
		public statusCode: number,
		public body: string,
		public endpoint: string,
	) {
		super(`ClickUp API ${statusCode} on ${endpoint}: ${body.slice(0, 200)}`);
		this.name = 'ClickUpApiError';
	}
}

async function request(
	token: string,
	method: string,
	path: string,
	body?: Record<string, any>,
): Promise<any> {
	await waitForSlot(token);

	const url = `${CLICKUP_API}${path}`;
	const headers: Record<string, string> = {
		'Authorization': token,
		'Content-Type': 'application/json',
	};

	const res = await fetch(url, {
		method,
		headers,
		...(body ? { body: JSON.stringify(body) } : {}),
	});

	if (!res.ok) {
		const text = await res.text();
		throw new ClickUpApiError(res.status, text, `${method} ${path}`);
	}

	const contentType = res.headers.get('content-type') || '';
	if (contentType.includes('application/json')) {
		return res.json();
	}
	return res.text();
}

// ── Client class ──

export class ClickUpClient {
	constructor(private token: string) {}

	// ── Teams (Workspaces) ──

	async getTeams(): Promise<any[]> {
		const data = await request(this.token, 'GET', '/team');
		return data.teams || [];
	}

	// ── Spaces ──

	async getSpaces(teamId: string): Promise<any[]> {
		const data = await request(this.token, 'GET', `/team/${teamId}/space?archived=false`);
		return data.spaces || [];
	}

	// ── Folders & Lists ──

	async getFolders(spaceId: string): Promise<any[]> {
		const data = await request(this.token, 'GET', `/space/${spaceId}/folder?archived=false`);
		return data.folders || [];
	}

	async getFolderlessLists(spaceId: string): Promise<any[]> {
		const data = await request(this.token, 'GET', `/space/${spaceId}/list?archived=false`);
		return data.lists || [];
	}

	async getListsInFolder(folderId: string): Promise<any[]> {
		const data = await request(this.token, 'GET', `/folder/${folderId}/list?archived=false`);
		return data.lists || [];
	}

	// ── Tasks ──

	async getTasks(listId: string, page = 0): Promise<any[]> {
		const data = await request(
			this.token, 'GET',
			`/list/${listId}/task?page=${page}&include_closed=true&subtasks=true`,
		);
		return data.tasks || [];
	}

	async getTask(taskId: string): Promise<any> {
		return request(this.token, 'GET', `/task/${taskId}`);
	}

	async createTask(listId: string, body: Record<string, any>): Promise<any> {
		return request(this.token, 'POST', `/list/${listId}/task`, body);
	}

	async updateTask(taskId: string, body: Record<string, any>): Promise<any> {
		return request(this.token, 'PUT', `/task/${taskId}`, body);
	}

	// ── Webhooks ──

	async createWebhook(teamId: string, endpoint: string, events: string[], secret?: string): Promise<any> {
		const body: Record<string, any> = { endpoint, events };
		if (secret) body.secret = secret;
		return request(this.token, 'POST', `/team/${teamId}/webhook`, body);
	}

	async deleteWebhook(webhookId: string): Promise<void> {
		await request(this.token, 'DELETE', `/webhook/${webhookId}`);
	}

	async getWebhooks(teamId: string): Promise<any[]> {
		const data = await request(this.token, 'GET', `/team/${teamId}/webhook`);
		return data.webhooks || [];
	}

	// ── List details (for status mapping) ──

	async getList(listId: string): Promise<any> {
		return request(this.token, 'GET', `/list/${listId}`);
	}
}
