import { rpc } from './request';

// Кеш для хранения результатов
const cache = new Map<string, string | null>();

// Очередь запросов для батчинга
type PendingRequest = {
    collection: string;
    ids: Set<string>;
    resolvers: Map<string, Array<(value: string | null) => void>>;
    rejectors: Map<string, Array<(error: any) => void>>;
};

const pendingRequests = new Map<string, PendingRequest>();

// Таймер для батчинга (debounce)
let batchTimer: ReturnType<typeof setTimeout> | null = null;
const BATCH_DELAY = 50; // Задержка в мс для накопления запросов

/**
 * Получить ключ кеша для конкретного id и коллекции
 */
const getCacheKey = (collection: string, id: string): string => {
    return `${collection}:${id}`;
};

/**
 * Получить значение из кеша
 */
const getCached = (collection: string, id: string): string | null | undefined => {
    const key = getCacheKey(collection, id);
    return cache.get(key);
};

/**
 * Сохранить значение в кеш
 */
const setCached = (collection: string, id: string, value: string | null): void => {
    const key = getCacheKey(collection, id);
    cache.set(key, value);
};

/**
 * Обработать накопленные запросы
 */
const processBatch = async (): Promise<void> => {
    if (pendingRequests.size === 0) {
        return;
    }

    // Создаем копию запросов и очищаем очередь
    const requestsToProcess = Array.from(pendingRequests.entries());
    pendingRequests.clear();

    // Обрабатываем каждый набор запросов по коллекциям
    for (const [collection, request] of requestsToProcess) {
        const ids = Array.from(request.ids);

        // Проверяем кеш перед запросом
        const uncachedIds: string[] = [];
        const cachedResults = new Map<string, string | null>();

        for (const id of ids) {
            const cached = getCached(collection, id);
            if (cached !== undefined) {
                cachedResults.set(id, cached);
            } else {
                uncachedIds.push(id);
            }
        }

        // Разрешаем запросы из кеша
        for (const [id, value] of cachedResults) {
            const resolvers = request.resolvers.get(id);
            if (resolvers) {
                for (const resolver of resolvers) {
                    try {
                        resolver(value);
                    } catch (error) {
                        console.error('Error resolving cached promise:', error);
                    }
                }
                request.resolvers.delete(id);
                request.rejectors.delete(id);
            }
        }

        // Если есть некешированные id, делаем запрос
        if (uncachedIds.length > 0) {
            try {
                const result = await rpc.displayValues({
                    collection,
                    ids: uncachedIds,
                });

                // Сохраняем результаты в кеш и разрешаем промисы
                const resultMap = new Map<string, string | null>();
                if (result?.result?.data) {
                    for (const item of result.result.data) {
                        if (item._id) {
                            const displayValue = item.displayValue ?? null;
                            setCached(collection, item._id, displayValue);
                            resultMap.set(item._id, displayValue);
                        }
                    }
                }

                // Разрешаем все промисы (даже если id нет в ответе - возвращаем null)
                for (const id of uncachedIds) {
                    const resolvers = request.resolvers.get(id);

                    // Если id нет в ответе, сохраняем null в кеш и возвращаем null
                    const value = resultMap.get(id);
                    if (value === undefined) {
                        // ID не найден в ответе - кешируем null
                        setCached(collection, id, null);
                    }

                    // Разрешаем все промисы для этого id
                    if (resolvers) {
                        for (const resolver of resolvers) {
                            try {
                                resolver(value ?? null);
                            } catch (error) {
                                console.error('Error resolving promise:', error);
                            }
                        }
                    }

                    // Очищаем обработчики
                    request.resolvers.delete(id);
                    request.rejectors.delete(id);
                }
            } catch (error) {
                // Отклоняем все промисы при ошибке
                for (const id of uncachedIds) {
                    const resolvers = request.resolvers.get(id);
                    const rejectors = request.rejectors.get(id);

                    if (rejectors) {
                        for (const rejector of rejectors) {
                            try {
                                rejector(error);
                            } catch (rejectError) {
                                console.error('Error rejecting promise:', rejectError);
                            }
                        }
                    } else if (resolvers) {
                        // Если нет rejectors, но есть resolvers, разрешаем с null
                        for (const resolver of resolvers) {
                            try {
                                resolver(null);
                            } catch (resolveError) {
                                console.error('Error resolving promise on error:', resolveError);
                            }
                        }
                    }

                    // Очищаем обработчики
                    request.resolvers.delete(id);
                    request.rejectors.delete(id);
                }
            }
        }
    }
};

/**
 * Получить display value для конкретного id и коллекции
 * Автоматически батчит запросы и использует кеш
 */
export const getDisplayValue = (
    collection: string,
    id: string
): Promise<string | null> => {
    return new Promise((resolve, reject) => {
        // Проверяем кеш
        const cached = getCached(collection, id);
        if (cached !== undefined) {
            resolve(cached);
            return;
        }

        // Добавляем в очередь батчинга
        let pendingRequest = pendingRequests.get(collection);
        if (!pendingRequest) {
            pendingRequest = {
                collection,
                ids: new Set(),
                resolvers: new Map(),
                rejectors: new Map(),
            };
            pendingRequests.set(collection, pendingRequest);
        }

        // Если id уже есть в очереди, добавляем обработчики к существующим
        if (pendingRequest.ids.has(id)) {
            const existingResolvers = pendingRequest.resolvers.get(id) || [];
            const existingRejectors = pendingRequest.rejectors.get(id) || [];

            existingResolvers.push(resolve);
            existingRejectors.push(reject);

            pendingRequest.resolvers.set(id, existingResolvers);
            pendingRequest.rejectors.set(id, existingRejectors);
        } else {
            // Новый id - добавляем в очередь
            pendingRequest.ids.add(id);
            pendingRequest.resolvers.set(id, [resolve]);
            pendingRequest.rejectors.set(id, [reject]);
        }

        // Сбрасываем таймер и устанавливаем новый
        if (batchTimer) {
            clearTimeout(batchTimer);
        }

        batchTimer = setTimeout(() => {
            batchTimer = null;
            processBatch().catch((error) => {
                console.error('Error processing batch:', error);
            });
        }, BATCH_DELAY);
    });
};

/**
 * Очистить кеш (можно использовать при необходимости)
 */
export const clearDisplayValuesCache = (): void => {
    cache.clear();
};

/**
 * Очистить кеш для конкретной коллекции
 */
export const clearDisplayValuesCacheForCollection = (collection: string): void => {
    const keysToDelete: string[] = [];
    for (const key of cache.keys()) {
        if (key.startsWith(`${collection}:`)) {
            keysToDelete.push(key);
        }
    }
    for (const key of keysToDelete) {
        cache.delete(key);
    }
};

