import { createContext, useEffect, useRef, useState } from "react";

export interface PlayerContextValue {
    playAudio: (
        artist: string,
        title: string,
        audio: string | Blob | File,
        messageId?: string | null
    ) => void;
    playing: boolean;
    pause: () => void;
    resume: () => void;
    stop: () => void;
    setDuration: (duration: number) => void;
    duration: number;
    totalDuration: number;
    currentMessageId: string | null;
    lastMessageId: string | null;
    lastError: string | null;
}

export const PlayerContext = createContext<PlayerContextValue | null>(null);

interface PlayerProviderProps {
    children: React.ReactNode;
}

export function PlayerProvider(props: PlayerProviderProps) {
    const audioRef = useRef<HTMLAudioElement>(null);

    const objectUrlRef = useRef<string | null>(null);
    const rafTimeUpdateRef = useRef<number | null>(null);

    const isLoadingRef = useRef(false);
    const isSeekingRef = useRef(false);

    const durationRef = useRef(0);
    const totalDurationRef = useRef(0);

    const isPlayingRef = useRef(false);
    const [isPlaying, setIsPlaying] = useState(false);
    const [duration, setDurationState] = useState(0);
    const [totalDuration, setTotalDuration] = useState(0);
    const [lastError, setLastError] = useState<string | null>(null);

    const [currentMessageId, setCurrentMessageId] = useState<string | null>(null);
    const [lastMessageId, setLastMessageId] = useState<string | null>(null);

    const commitPlaying = (next: boolean) => {
        if (isPlayingRef.current === next) return;
        isPlayingRef.current = next;
        setIsPlaying(next);
    };

    const commitDuration = (next: number) => {
        const safe = Number.isFinite(next) && next >= 0 ? next : 0;
        if (Math.abs(safe - durationRef.current) < 0.033) return;
        durationRef.current = safe;
        setDurationState(safe);
    };

    const commitTotalDuration = (next: number) => {
        const safe = Number.isFinite(next) && next > 0 ? next : 0;
        if (Math.abs(safe - totalDurationRef.current) < 0.05) return;
        totalDurationRef.current = safe;
        setTotalDuration(safe);
    };

    const decodeMediaError = (err: MediaError | null) => {
        if (!err) return "Unknown media error";
        switch (err.code) {
            case MediaError.MEDIA_ERR_ABORTED:
                return "Playback aborted";
            case MediaError.MEDIA_ERR_NETWORK:
                return "Network error while loading audio";
            case MediaError.MEDIA_ERR_DECODE:
                return "Audio decode error";
            case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:
                return "Audio source is not supported";
            default:
                return `Unknown media error (${err.code})`;
        }
    };

    useEffect(() => {
        const audio = audioRef.current;
        if (!audio) return;

        const onPlay = () => {
            if (isLoadingRef.current) return;
            commitPlaying(true);
        };

        const onPause = () => {
            if (isLoadingRef.current) return;
            commitPlaying(false);
        };

        const onEnded = () => {
            commitPlaying(false);
            durationRef.current = 0;
            setDurationState(0);
            setCurrentMessageId(null);
        };

        const onTimeUpdate = () => {
            if (isLoadingRef.current) return;
            if (isSeekingRef.current) return;
            if (rafTimeUpdateRef.current != null) return;

            rafTimeUpdateRef.current = requestAnimationFrame(() => {
                rafTimeUpdateRef.current = null;
                if (!isLoadingRef.current && !isSeekingRef.current) {
                    commitDuration(audio.currentTime || 0);
                }
            });
        };

        const onLoadedMetadata = () => commitTotalDuration(audio.duration);
        const onDurationChange = () => commitTotalDuration(audio.duration);

        const onSeeked = () => {
            if (isSeekingRef.current) {
                isSeekingRef.current = false;
                if (!isLoadingRef.current) commitDuration(audio.currentTime || 0);
                return;
            }
            if (isLoadingRef.current) return;
            commitDuration(audio.currentTime || 0);
        };

        const onCanPlay = () => {
            if (isLoadingRef.current) isLoadingRef.current = false;
        };

        const onError = (_e: Event) => {
            const message = decodeMediaError(audio.error);
            setLastError(message);

            console.error("Audio playback error", {
                message,
                mediaError: audio.error,
                currentSrc: audio.currentSrc,
                readyState: audio.readyState,
                networkState: audio.networkState,
            });
        };

        audio.addEventListener("play", onPlay);
        audio.addEventListener("pause", onPause);
        audio.addEventListener("ended", onEnded);
        audio.addEventListener("timeupdate", onTimeUpdate);
        audio.addEventListener("loadedmetadata", onLoadedMetadata);
        audio.addEventListener("durationchange", onDurationChange);
        audio.addEventListener("seeked", onSeeked);
        audio.addEventListener("canplay", onCanPlay);
        audio.addEventListener("error", onError);

        return () => {
            audio.removeEventListener("play", onPlay);
            audio.removeEventListener("pause", onPause);
            audio.removeEventListener("ended", onEnded);
            audio.removeEventListener("timeupdate", onTimeUpdate);
            audio.removeEventListener("loadedmetadata", onLoadedMetadata);
            audio.removeEventListener("durationchange", onDurationChange);
            audio.removeEventListener("seeked", onSeeked);
            audio.removeEventListener("canplay", onCanPlay);
            audio.removeEventListener("error", onError);

            if (rafTimeUpdateRef.current != null) {
                cancelAnimationFrame(rafTimeUpdateRef.current);
                rafTimeUpdateRef.current = null;
            }
        };
    }, []);

    useEffect(() => {
        return () => {
            if (objectUrlRef.current) {
                URL.revokeObjectURL(objectUrlRef.current);
                objectUrlRef.current = null;
            }
        };
    }, []);

    const playAudio = (
        artist: string,
        title: string,
        audio: string | Blob | File,
        messageId?: string | null
    ) => {
        const el = audioRef.current;
        if (!el) return;

        // чтобы не было warning о неиспользуемых args при строгих правилах
        void artist;
        void title;

        setLastError(null);

        if (objectUrlRef.current) {
            URL.revokeObjectURL(objectUrlRef.current);
            objectUrlRef.current = null;
        }

        const audioSrc = typeof audio === "string" ? audio : URL.createObjectURL(audio);
        if (typeof audio !== "string") {
            objectUrlRef.current = audioSrc;
        }

        isLoadingRef.current = true;
        isSeekingRef.current = false;

        el.src = audioSrc;
        durationRef.current = 0;

        const msgId = messageId ?? null;
        setCurrentMessageId(msgId);
        if (msgId) setLastMessageId(msgId);

        isPlayingRef.current = true;
        setIsPlaying(true);

        const prevDuration = durationRef.current;
        requestAnimationFrame(() => {
            if (durationRef.current === prevDuration) {
                setDurationState(0);
            }
        });

        void el.play().catch((err) => {
            isLoadingRef.current = false;
            commitPlaying(false);
            setLastError(err instanceof Error ? err.message : "play() failed");
        });
    };

    const pause = () => {
        const el = audioRef.current;
        if (!el) return;
        el.pause();
    };

    const resume = () => {
        const el = audioRef.current;
        if (!el) return;

        commitPlaying(true);

        void el.play().catch((err) => {
            commitPlaying(false);
            setLastError(err instanceof Error ? err.message : "resume() failed");
        });
    };

    const stop = () => {
        const el = audioRef.current;
        if (!el) return;

        isLoadingRef.current = true;
        el.pause();
        el.currentTime = 0;
        isLoadingRef.current = false;

        durationRef.current = 0;
        setDurationState(0);
        commitPlaying(false);
        setCurrentMessageId(null);
    };

    const setDuration = (sec: number) => {
        const el = audioRef.current;
        if (!el) return;

        isSeekingRef.current = true;
        el.currentTime = Math.max(0, sec);
        commitDuration(el.currentTime || 0);
    };

    return (
        <PlayerContext.Provider
            value={{
                playAudio,
                playing: isPlaying,
                pause,
                resume,
                stop,
                setDuration,
                duration,
                totalDuration,
                currentMessageId,
                lastMessageId,
                lastError,
            }}
        >
            {props.children}
            <audio ref={audioRef} />
        </PlayerContext.Provider>
    );
}