import { useSyncExternalStore } from 'react';

export type ResolvedAppearance = 'light' | 'dark';
export type Appearance = ResolvedAppearance | 'system';

export const DEFAULT_APPEARANCE: Appearance = 'light';

export type UseAppearanceReturn = {
    readonly appearance: Appearance;
    readonly resolvedAppearance: ResolvedAppearance;
    readonly updateAppearance: (mode: Appearance) => void;
};

const listeners = new Set<() => void>();
let currentAppearance: Appearance = DEFAULT_APPEARANCE;
let systemThemeListenerAttached = false;

const validAppearances: Appearance[] = ['light', 'dark', 'system'];

export const isValidAppearance = (value: string | null): value is Appearance => {
    return value !== null && validAppearances.includes(value as Appearance);
};

const prefersDark = (): boolean => {
    if (typeof window === 'undefined') {
        return false;
    }

    return window.matchMedia('(prefers-color-scheme: dark)').matches;
};

export const resolveAppearanceMode = (appearance: Appearance): ResolvedAppearance => {
    return isDarkMode(appearance) ? 'dark' : 'light';
};

const setCookie = (name: string, value: string, days = 365): void => {
    if (typeof document === 'undefined') {
        return;
    }

    const maxAge = days * 24 * 60 * 60;
    document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`;
};

const safeLocalStorageGet = (key: string): string | null => {
    try {
        return localStorage.getItem(key);
    } catch {
        return null;
    }
};

const safeLocalStorageSet = (key: string, value: string): void => {
    try {
        localStorage.setItem(key, value);
    } catch {
        // Storage may be blocked in private browsing or strict privacy modes.
    }
};

const getCookieAppearance = (): Appearance | null => {
    if (typeof document === 'undefined') {
        return null;
    }

    const match = document.cookie.match(/(?:^|;\s*)appearance=([^;]+)/);

    if (!match) {
        return null;
    }

    const value = decodeURIComponent(match[1]);

    return isValidAppearance(value) ? value : null;
};

const getStoredAppearance = (): Appearance | null => {
    const value = safeLocalStorageGet('appearance');

    return isValidAppearance(value) ? value : null;
};

const isDarkMode = (appearance: Appearance): boolean => {
    return appearance === 'dark' || (appearance === 'system' && prefersDark());
};

export const applyTheme = (appearance: Appearance): void => {
    if (typeof document === 'undefined') {
        return;
    }

    const isDark = isDarkMode(appearance);

    document.documentElement.classList.toggle('dark', isDark);
    document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';
};

const subscribe = (callback: () => void) => {
    listeners.add(callback);

    return () => listeners.delete(callback);
};

const notify = (): void => listeners.forEach((listener) => listener());

const mediaQuery = (): MediaQueryList | null => {
    if (typeof window === 'undefined') {
        return null;
    }

    return window.matchMedia('(prefers-color-scheme: dark)');
};

const handleSystemThemeChange = (): void => applyTheme(currentAppearance);

const readInitialAppearance = (): Appearance => {
    const fromStorage = getStoredAppearance();
    const fromCookie = getCookieAppearance();

    if (fromStorage) {
        if (!fromCookie) {
            setCookie('appearance', fromStorage);
        }

        return fromStorage;
    }

    if (fromCookie) {
        safeLocalStorageSet('appearance', fromCookie);

        return fromCookie;
    }

    safeLocalStorageSet('appearance', DEFAULT_APPEARANCE);
    setCookie('appearance', DEFAULT_APPEARANCE);

    return DEFAULT_APPEARANCE;
};

export function initializeTheme(): void {
    if (typeof window === 'undefined') {
        return;
    }

    currentAppearance = readInitialAppearance();
    applyTheme(currentAppearance);

    if (!systemThemeListenerAttached) {
        mediaQuery()?.addEventListener('change', handleSystemThemeChange);
        systemThemeListenerAttached = true;
    }
}

export function useAppearance(): UseAppearanceReturn {
    const appearance: Appearance = useSyncExternalStore(
        subscribe,
        () => currentAppearance,
        () => DEFAULT_APPEARANCE,
    );

    const resolvedAppearance: ResolvedAppearance = resolveAppearanceMode(appearance);

    const updateAppearance = (mode: Appearance): void => {
        currentAppearance = mode;

        safeLocalStorageSet('appearance', mode);
        setCookie('appearance', mode);
        applyTheme(mode);
        notify();
    };

    return { appearance, resolvedAppearance, updateAppearance } as const;
}
