/**
 * VoiceCatalogContext
 *
 * Loads the central voice/language catalog from the backend exactly once and
 * makes it available to every component via `useVoiceCatalog()`.
 *
 * Provides convenience helpers for ISO ⇄ BCP-47 lookups and curated default
 * voices, mirroring the backend `voiceCatalog` API. Components MUST NOT keep
 * their own static language lists.
 */

import React, {
    createContext,
    ReactNode,
    useCallback,
    useContext,
    useEffect,
    useMemo,
    useState,
} from 'react';
import { fetchVoiceCatalog, VoiceLanguage } from '../api/voiceCatalogApi';

interface VoiceCatalogContextType {
    languages: VoiceLanguage[];
    isLoading: boolean;
    error: string | null;
    getByBcp47: (code: string | null | undefined) => VoiceLanguage | undefined;
    getByIso: (iso: string | null | undefined) => VoiceLanguage | undefined;
    isoToBcp47: (iso: string | null | undefined) => string | undefined;
    getDefaultVoice: (bcp47: string | null | undefined) => string | null;
}

const VoiceCatalogContext = createContext<VoiceCatalogContextType | undefined>(undefined);

interface VoiceCatalogProviderProps {
    children: ReactNode;
}

export const VoiceCatalogProvider: React.FC<VoiceCatalogProviderProps> = ({ children }) => {
    const [languages, setLanguages] = useState<VoiceLanguage[]>([]);
    const [isLoading, setIsLoading] = useState<boolean>(true);
    const [error, setError] = useState<string | null>(null);

    useEffect(() => {
        let cancelled = false;
        (async () => {
            try {
                const data = await fetchVoiceCatalog();
                if (!cancelled) {
                    setLanguages(data);
                    setError(null);
                }
            } catch (err: any) {
                if (!cancelled) {
                    setError(err?.message || 'Failed to load voice catalog');
                    setLanguages([]);
                }
            } finally {
                if (!cancelled) setIsLoading(false);
            }
        })();
        return () => {
            cancelled = true;
        };
    }, []);

    const byBcp47 = useMemo(() => {
        const map = new Map<string, VoiceLanguage>();
        for (const v of languages) map.set(v.bcp47.toLowerCase(), v);
        return map;
    }, [languages]);

    const byIso = useMemo(() => {
        const map = new Map<string, VoiceLanguage>();
        for (const v of languages) {
            if (!map.has(v.iso.toLowerCase())) map.set(v.iso.toLowerCase(), v);
        }
        return map;
    }, [languages]);

    const getByBcp47 = useCallback(
        (code: string | null | undefined) =>
            code ? byBcp47.get(code.trim().toLowerCase()) : undefined,
        [byBcp47],
    );

    const getByIso = useCallback(
        (iso: string | null | undefined) =>
            iso ? byIso.get(iso.trim().toLowerCase()) : undefined,
        [byIso],
    );

    const isoToBcp47 = useCallback(
        (iso: string | null | undefined): string | undefined => {
            if (!iso) return undefined;
            const trimmed = iso.trim();
            if (!trimmed) return undefined;
            if (trimmed.includes('-')) {
                const canonical = byBcp47.get(trimmed.toLowerCase());
                return canonical ? canonical.bcp47 : trimmed;
            }
            const entry = byIso.get(trimmed.toLowerCase());
            if (entry) return entry.bcp47;
            return `${trimmed.toLowerCase()}-${trimmed.toUpperCase()}`;
        },
        [byBcp47, byIso],
    );

    const getDefaultVoice = useCallback(
        (bcp47: string | null | undefined): string | null => {
            const entry = getByBcp47(bcp47);
            return entry?.defaultVoice ?? null;
        },
        [getByBcp47],
    );

    const value = useMemo<VoiceCatalogContextType>(
        () => ({
            languages,
            isLoading,
            error,
            getByBcp47,
            getByIso,
            isoToBcp47,
            getDefaultVoice,
        }),
        [languages, isLoading, error, getByBcp47, getByIso, isoToBcp47, getDefaultVoice],
    );

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

export const useVoiceCatalog = (): VoiceCatalogContextType => {
    const ctx = useContext(VoiceCatalogContext);
    if (!ctx) {
        throw new Error('useVoiceCatalog must be used within VoiceCatalogProvider');
    }
    return ctx;
};

/**
 * Map a profile language (ISO short code) to a default voice locale.
 * Returns the catalog's BCP-47 for the ISO if available, else falls back to
 * `de-DE` so the UI always has a deterministic starting value.
 */
export const useDefaultVoiceLocale = (profileLanguage?: string | null): string => {
    const { isoToBcp47 } = useVoiceCatalog();
    return isoToBcp47(profileLanguage) || 'de-DE';
};
