"use client";

import {
    createContext,
    use,
    useCallback,
    useEffect,
    useMemo,
    useSyncExternalStore,
    type ReactNode,
} from "react";

import {
    DEFAULT_LANGUAGE,
    LANGUAGE_STORAGE_KEY,
    getApiLanguage,
    translations,
    type LanguageCode,
    type TranslationKey,
} from "@/components/layout/header/header-data";

type ProviderLanguage = LanguageCode | "km";

type AppLanguageContextValue = {
    language: LanguageCode;
    apiLanguage: "km" | "en";
    setLanguage: (language: ProviderLanguage) => void;
    changeLanguage: (language: ProviderLanguage) => void;
    t: (key: TranslationKey) => string;
};

const AppLanguageContext = createContext<AppLanguageContextValue | null>(null);

const LANGUAGE_CHANGED_EVENT = "gpsf-language-changed";

function normalizeLanguage(value: string | null | undefined): LanguageCode {
    if (value === "en") {
        return "en";
    }

    if (value === "kh" || value === "km") {
        return "kh";
    }

    return DEFAULT_LANGUAGE;
}

function getServerLanguageSnapshot(): LanguageCode {
    return DEFAULT_LANGUAGE;
}

function getClientLanguageSnapshot(): LanguageCode {
    if (typeof window === "undefined") {
        return DEFAULT_LANGUAGE;
    }

    try {
        return normalizeLanguage(window.localStorage.getItem(LANGUAGE_STORAGE_KEY));
    } catch {
        return DEFAULT_LANGUAGE;
    }
}

function subscribeLanguageStore(callback: () => void) {
    if (typeof window === "undefined") {
        return () => { };
    }

    const handleChange = () => {
        callback();
    };

    window.addEventListener("storage", handleChange);
    window.addEventListener(LANGUAGE_CHANGED_EVENT, handleChange);

    return () => {
        window.removeEventListener("storage", handleChange);
        window.removeEventListener(LANGUAGE_CHANGED_EVENT, handleChange);
    };
}

function writeLanguageToStore(language: LanguageCode) {
    if (typeof window === "undefined") {
        return;
    }

    try {
        window.localStorage.setItem(LANGUAGE_STORAGE_KEY, language);
    } catch {
        // Ignore localStorage error.
    }

    window.dispatchEvent(new Event(LANGUAGE_CHANGED_EVENT));
}

function applyLanguageToDocument(language: LanguageCode) {
    if (typeof document === "undefined") {
        return;
    }

    const htmlLang = language === "kh" ? "km" : "en";

    document.documentElement.lang = htmlLang;
    document.documentElement.dataset.language = language;

    if (document.body) {
        document.body.dataset.language = language;
    }
}

function getCurrentTranslations(language: LanguageCode) {
    const translationMap = translations as Record<
        string,
        Partial<Record<TranslationKey, string>>
    >;

    return (
        translationMap[language] ??
        (language === "kh" ? translationMap.km : undefined) ??
        translationMap.en ??
        {}
    );
}

export function AppLanguageProvider({ children }: { children: ReactNode }) {
    const language = useSyncExternalStore(
        subscribeLanguageStore,
        getClientLanguageSnapshot,
        getServerLanguageSnapshot,
    );

    useEffect(() => {
        applyLanguageToDocument(language);
    }, [language]);

    const setLanguage = useCallback((nextLanguage: ProviderLanguage) => {
        const normalizedLanguage = normalizeLanguage(nextLanguage);

        writeLanguageToStore(normalizedLanguage);
        applyLanguageToDocument(normalizedLanguage);
    }, []);

    const t = useCallback(
        (key: TranslationKey) => {
            const currentTranslations = getCurrentTranslations(language);
            const englishTranslations = getCurrentTranslations("en");

            return (
                currentTranslations[key] ??
                englishTranslations[key] ??
                String(key)
            );
        },
        [language],
    );

    const value = useMemo<AppLanguageContextValue>(() => {
        return {
            language,
            apiLanguage: getApiLanguage(language),
            setLanguage,
            changeLanguage: setLanguage,
            t,
        };
    }, [language, setLanguage, t]);

    return (
        <AppLanguageContext.Provider value={value}>
            {children}
        </AppLanguageContext.Provider>
    );
}

export function useAppLanguage() {
    const context = use(AppLanguageContext);

    if (!context) {
        throw new Error("useAppLanguage must be used inside AppLanguageProvider");
    }

    return context;
}
