import { useEffect } from 'react';

import SparkMD5 from 'spark-md5';
import { create } from 'zustand';

import { rpc } from '@/api/request';
import {
    LangsBDEFHIJPRZEnum,
    TargetLangBDEFHIJPRZEnum,
} from '@/api/types';

const BATCH_DELAY = 1000;
const MASK_CHAR = '▧';

export const maskText = (text: string): string => {
    const length = [...text].length;
    if (length === 0) return '';
    return MASK_CHAR.repeat(Math.ceil(length / 2));
};

const hashText = (text: string): string => SparkMD5.hash(text);

export const getCacheKey = (text: string, lang: LangsBDEFHIJPRZEnum): string =>
    `${lang}:${hashText(text)}`;

type LocalizationCacheState = {
    cache: Record<string, string>;
    setEntries: (entries: Record<string, string>) => void;
};

export const useLocalizationCache = create<LocalizationCacheState>((set) => ({
    cache: {},
    setEntries: (entries) => set((state) => ({
        cache: { ...state.cache, ...entries },
    })),
}));

type PendingRequest = {
    texts: Set<string>;
    resolvers: Map<string, Array<(value: string) => void>>;
    rejectors: Map<string, Array<(error: unknown) => void>>;
};

const pendingByLang = new Map<TargetLangBDEFHIJPRZEnum, PendingRequest>();
let batchTimer: ReturnType<typeof setTimeout> | null = null;

const getCachedTranslation = (
    text: string,
    lang: LangsBDEFHIJPRZEnum,
): string | undefined => {
    const key = getCacheKey(text, lang);
    return useLocalizationCache.getState().cache[key];
};

const resolvePending = (
    request: PendingRequest,
    text: string,
    value: string,
): void => {
    const resolvers = request.resolvers.get(text);
    if (resolvers) {
        for (const resolver of resolvers) {
            try {
                resolver(value);
            } catch (error) {
                console.error('Error resolving localization promise:', error);
            }
        }
    }
    request.resolvers.delete(text);
    request.rejectors.delete(text);
};

const rejectPending = (
    request: PendingRequest,
    text: string,
    error: unknown,
): void => {
    const rejectors = request.rejectors.get(text);
    const resolvers = request.resolvers.get(text);

    if (rejectors) {
        for (const rejector of rejectors) {
            try {
                rejector(error);
            } catch (rejectError) {
                console.error('Error rejecting localization promise:', rejectError);
            }
        }
    } else if (resolvers) {
        for (const resolver of resolvers) {
            try {
                resolver(maskText(text));
            } catch (resolveError) {
                console.error('Error resolving localization promise on error:', resolveError);
            }
        }
    }

    request.resolvers.delete(text);
    request.rejectors.delete(text);
};

const processBatch = async (): Promise<void> => {
    if (pendingByLang.size === 0) {
        return;
    }

    const requestsToProcess = Array.from(pendingByLang.entries());
    pendingByLang.clear();

    for (const [targetLang, request] of requestsToProcess) {
        const texts = Array.from(request.texts);
        const uncachedTexts: string[] = [];

        for (const text of texts) {
            const cached = getCachedTranslation(text, targetLang);
            if (cached !== undefined) {
                resolvePending(request, text, cached);
            } else {
                uncachedTexts.push(text);
            }
        }

        if (uncachedTexts.length === 0) {
            continue;
        }

        try {
            const response = await rpc.localizations({
                texts: uncachedTexts.map((text) => ({
                    md5: hashText(text),
                    text,
                })),
                targetLang,
            });

            const resultMap = new Map<string, string>();
            if (response?.result) {
                for (const item of response.result) {
                    if (item.md5 && item.text !== undefined) {
                        resultMap.set(item.md5, item.text);
                    }
                }
            }

            const cacheEntries: Record<string, string> = {};

            for (const text of uncachedTexts) {
                const md5 = hashText(text);
                const translated = resultMap.get(md5) ?? maskText(text);
                cacheEntries[getCacheKey(text, targetLang)] = translated;
                resolvePending(request, text, translated);
            }

            if (Object.keys(cacheEntries).length > 0) {
                useLocalizationCache.getState().setEntries(cacheEntries);
            }
        } catch (error) {
            console.error('Error fetching localizations:', error);
            for (const text of uncachedTexts) {
                rejectPending(request, text, error);
            }
        }
    }
};

export const queueTranslation = (
    text: string,
    lang: LangsBDEFHIJPRZEnum,
): Promise<string> => {
    if (!text || lang === 'ru') {
        return Promise.resolve(text);
    }

    const cached = getCachedTranslation(text, lang);
    if (cached !== undefined) {
        return Promise.resolve(cached);
    }

    return new Promise((resolve, reject) => {
        let pendingRequest = pendingByLang.get(lang);
        if (!pendingRequest) {
            pendingRequest = {
                texts: new Set(),
                resolvers: new Map(),
                rejectors: new Map(),
            };
            pendingByLang.set(lang, pendingRequest);
        }

        if (pendingRequest.texts.has(text)) {
            const existingResolvers = pendingRequest.resolvers.get(text) || [];
            const existingRejectors = pendingRequest.rejectors.get(text) || [];
            pendingRequest.resolvers.set(text, [...existingResolvers, resolve]);
            pendingRequest.rejectors.set(text, [...existingRejectors, reject]);
        } else {
            pendingRequest.texts.add(text);
            pendingRequest.resolvers.set(text, [resolve]);
            pendingRequest.rejectors.set(text, [reject]);
        }

        if (batchTimer) {
            clearTimeout(batchTimer);
        }

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

export const getCachedTranslationSync = (
    text: string,
    lang: LangsBDEFHIJPRZEnum,
): string | undefined => getCachedTranslation(text, lang);

export const useLLText = (
    text: string,
    lang: LangsBDEFHIJPRZEnum,
): string => {
    const cacheKey = getCacheKey(text, lang);
    const cached = useLocalizationCache((state) => state.cache[cacheKey]);

    useEffect(() => {
        if (!text || lang === 'ru') return;
        if (getCachedTranslation(text, lang) !== undefined) return;
        queueTranslation(text, lang).catch((error) => {
            console.error('Error queueing translation:', error);
        });
    }, [text, lang, cacheKey]);

    if (!text) return '';
    if (lang === 'ru') return text;
    if (cached !== undefined) return cached;

    return maskText(text);
};
