import {
  createContext,
  useContext,
  useRef,
  useState,
  useEffect,
  useCallback,
  type ReactNode,
} from "react";
import { type Track } from "./playlist";

// ---------------------------------------------------------------------------
// 타입
// ---------------------------------------------------------------------------

type PlayerState = {
  currentIndex: number;
  currentTrack: Track | null;
  isPlaying: boolean;
  progress: number;
  currentTime: number;
  duration: number;
  volume: number;
  muted: boolean;
  userClosed: boolean;
  hasTrack: boolean;
};

type PlayerActions = {
  play: () => void;
  pause: () => void;
  toggle: () => void;
  next: () => void;
  prev: () => void;
  seek: (ratio: number) => void;
  setVolume: (v: number) => void;
  toggleMute: () => void;
  close: () => void;
  open: () => void;
  goTo: (index: number) => void;
  loadPlaylist: (tracks: Track[]) => void;
};

type PlayerContextValue = PlayerState & PlayerActions;

// ---------------------------------------------------------------------------
// Context
// ---------------------------------------------------------------------------

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

// ---------------------------------------------------------------------------
// Provider
// ---------------------------------------------------------------------------

export function PlayerProvider({ children }: { children: ReactNode }) {
  const audioRef = useRef<HTMLAudioElement | null>(null);
  const currentSrcRef = useRef<string>("");
  const autoPlayRef = useRef(false);
  const isPlayingRef = useRef(false);
  const [playlist, setPlaylistState] = useState<Track[]>([]);
  const [currentIndex, setCurrentIndex] = useState(0);
  const [loadKey, setLoadKey] = useState(0);
  const [isPlaying, setIsPlaying] = useState(false);
  const [progress, setProgress] = useState(0);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const [volume, setVolumeState] = useState(0.4);
  const [muted, setMuted] = useState(false);
  const [userClosed, setUserClosed] = useState(false);

  const hasTrack = playlist.length > 0;
  const currentTrack = hasTrack ? playlist[currentIndex] : null;

  // 오디오 인스턴스 초기화 (마운트 시 1회)
  useEffect(() => {
    const audio = new Audio();
    audio.preload = "metadata";
    audio.volume = 0.4;
    audioRef.current = audio;

    const onTimeUpdate = () => {
      setCurrentTime(audio.currentTime);
      setProgress(audio.duration ? audio.currentTime / audio.duration : 0);
    };
    const onLoaded = () => setDuration(audio.duration || 0);
    const onPlay = () => { setIsPlaying(true); isPlayingRef.current = true; };
    const onPause = () => { setIsPlaying(false); isPlayingRef.current = false; };

    audio.addEventListener("timeupdate", onTimeUpdate);
    audio.addEventListener("loadedmetadata", onLoaded);
    audio.addEventListener("durationchange", onLoaded);
    audio.addEventListener("play", onPlay);
    audio.addEventListener("pause", onPause);

    return () => {
      audio.pause();
      audio.removeEventListener("timeupdate", onTimeUpdate);
      audio.removeEventListener("loadedmetadata", onLoaded);
      audio.removeEventListener("durationchange", onLoaded);
      audio.removeEventListener("play", onPlay);
      audio.removeEventListener("pause", onPause);
      audioRef.current = null;
    };
  }, []);

  // ended 시 순환 처리 (playlist 길이 반영)
  const playlistLenRef = useRef(playlist.length);
  useEffect(() => { playlistLenRef.current = playlist.length; }, [playlist.length]);

  useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    const onEnded = () => {
      autoPlayRef.current = true;
      setCurrentIndex((prev) =>
        playlistLenRef.current > 0 ? (prev + 1) % playlistLenRef.current : 0
      );
    };
    audio.addEventListener("ended", onEnded);
    return () => audio.removeEventListener("ended", onEnded);
  }, []);

  // 트랙 변경 시 src 교체
  useEffect(() => {
    const audio = audioRef.current;
    if (!audio || !currentTrack) return;
    const shouldPlay = isPlayingRef.current || autoPlayRef.current;
    autoPlayRef.current = false;
    currentSrcRef.current = currentTrack.src;
    audio.src = currentTrack.src;
    audio.load();
    setProgress(0);
    setCurrentTime(0);
    setDuration(0);
    if (shouldPlay) audio.play().catch(() => {});
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentIndex, currentTrack?.src, loadKey]);

  // ---------------------------------------------------------------------------
  // Actions
  // ---------------------------------------------------------------------------

  const play = useCallback(() => {
    const audio = audioRef.current;
    if (!audio) return;
    if (currentTrack && currentSrcRef.current !== currentTrack.src) {
      currentSrcRef.current = currentTrack.src;
      audio.src = currentTrack.src;
      audio.load();
    }
    audio.play().catch(() => {});
  }, [currentTrack]);

  const pause = useCallback(() => { audioRef.current?.pause(); }, []);

  const toggle = useCallback(() => {
    if (isPlaying) {
      audioRef.current?.pause();
    } else {
      const audio = audioRef.current;
      if (!audio) return;
      if (currentTrack && currentSrcRef.current !== currentTrack.src) {
        currentSrcRef.current = currentTrack.src;
        audio.src = currentTrack.src;
        audio.load();
      }
      audio.play().catch(() => {});
    }
  }, [isPlaying, currentTrack]);

  const next = useCallback(() => {
    if (!hasTrack) return;
    setCurrentIndex((prev) => (prev + 1) % playlist.length);
  }, [hasTrack, playlist.length]);

  const prev = useCallback(() => {
    if (!hasTrack) return;
    setCurrentIndex((prev) => (prev - 1 + playlist.length) % playlist.length);
  }, [hasTrack, playlist.length]);

  const seek = useCallback((ratio: number) => {
    const audio = audioRef.current;
    if (!audio || !audio.duration) return;
    audio.currentTime = ratio * audio.duration;
  }, []);

  const setVolume = useCallback((v: number) => {
    const clamped = Math.max(0, Math.min(1, v));
    setVolumeState(clamped);
    if (audioRef.current) {
      audioRef.current.volume = clamped;
      if (clamped > 0) { audioRef.current.muted = false; setMuted(false); }
    }
  }, []);

  const toggleMute = useCallback(() => {
    const audio = audioRef.current;
    if (!audio) return;
    const next = !audio.muted;
    audio.muted = next;
    setMuted(next);
  }, []);

  const close = useCallback(() => setUserClosed(true), []);
  const open = useCallback(() => setUserClosed(false), []);

  const goTo = useCallback((index: number) => {
    if (index < 0 || index >= playlist.length) return;
    setCurrentIndex(index);
  }, [playlist.length]);

  // 경로 변경 시 새 플레이리스트 로드
  const loadPlaylist = useCallback((tracks: Track[]) => {
    const audio = audioRef.current;
    if (audio) {
      audio.pause();
      audio.removeAttribute("src");
    }
    currentSrcRef.current = "";
    autoPlayRef.current = true;
    setPlaylistState([...tracks]);
    setLoadKey((k) => k + 1);
    setCurrentIndex(0);
    setProgress(0);
    setCurrentTime(0);
    setDuration(0);
    setIsPlaying(false);
    isPlayingRef.current = false;
  }, []);

  const value: PlayerContextValue = {
    currentIndex,
    currentTrack,
    isPlaying,
    progress,
    currentTime,
    duration,
    volume,
    muted,
    userClosed,
    hasTrack,
    play,
    pause,
    toggle,
    next,
    prev,
    seek,
    setVolume,
    toggleMute,
    close,
    open,
    goTo,
    loadPlaylist,
  };

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

// ---------------------------------------------------------------------------
// Hook
// ---------------------------------------------------------------------------

export function usePlayer(): PlayerContextValue {
  const ctx = useContext(PlayerContext);
  if (!ctx) throw new Error("usePlayer must be used inside <PlayerProvider>");
  return ctx;
}
