import React, { useEffect, useMemo, useState } from "react";
import {
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
  FlatList,
  ScrollView,
} from "react-native";
import Slider from "@react-native-community/slider";
import { VideoView, useVideoPlayer, VideoPlayer } from "expo-video";
import { useEventListener } from "expo";
import {
  useMedia,
  resolveMediaUri,
  type MediaItem,
  type RepeatMode,
} from "../../context/MediaContext";
import { Ionicons } from "@expo/vector-icons";

function formatTime(seconds: number) {
  if (!isFinite(seconds) || seconds < 0) return "0:00";
  const m = Math.floor(seconds / 60);
  const s = Math.floor(seconds % 60);
  return `${m}:${s.toString().padStart(2, "0")}`;
}

function VideoContainer({
  item,
  repeatMode,
  onPlayingChange,
  onProgress,
  requestToggle,
  pauseRequest,
  resumeRequest,
  seekRequest,
  onEnded,
}: {
  item: MediaItem;
  repeatMode: RepeatMode;
  onPlayingChange: (playing: boolean) => void;
  onProgress: (currentTime: number, duration: number) => void;
  requestToggle: number;
  /** Bumped (from MediaContext, e.g. by the in-app YouTube browser opening) to explicitly pause — unlike requestToggle, this doesn't flip play into pause. */
  pauseRequest: number;
  /** Same idea as pauseRequest, but explicitly resumes. */
  resumeRequest: number;
  seekRequest: number | null;
  onEnded: () => void;
}) {
  // expo-video hook initializes native video player; re-created whenever `item.uri` changes.
  const videoPlayer = useVideoPlayer(
    {
      uri: resolveMediaUri(item.uri),
      metadata: {
        title: item.title,
        artist: item.channel ?? undefined,
        artwork: item.thumbnail ?? undefined,
      },
    },
    (player: VideoPlayer) => {
      // Background playback / Now Playing notification require the
      // expo-video config plugin's native capability, only present in a
      // custom dev client — guard so this never crashes Expo Go.
      try {
        player.staysActiveInBackground = true;
        player.showNowPlayingNotification = true;
      } catch {
        // no-op: not available in this build
      }
      player.play();
    },
  );

  // Safety net for the "multiple tracks playing at once" bug: if this
  // player is ever orphaned (its item is no longer the active track, e.g.
  // the user tapped a different track before this component re-rendered
  // away), stop it immediately instead of relying solely on unmount timing.
  useEffect(() => {
    return () => {
      try {
        videoPlayer.pause();
      } catch {
        // no-op: player may already be released
      }
    };
  }, [videoPlayer]);

  useEventListener(videoPlayer, "playingChange", ({ isPlaying }) => {
    onPlayingChange(isPlaying);
  });

  useEventListener(videoPlayer, "timeUpdate", ({ currentTime }) => {
    onProgress(currentTime, videoPlayer.duration);
  });

  // useEventListener always calls the latest listener passed in (see its
  // internal ref pattern), so this closure reading `repeatMode` directly is
  // safe even though the subscription itself is only set up once.
  useEventListener(videoPlayer, "playToEnd", () => {
    if (repeatMode === "one") {
      videoPlayer.replay();
    } else {
      onEnded();
    }
  });

  // Bumping requestToggle from the parent (bottom bar) toggles this player.
  useEffect(() => {
    if (requestToggle === 0) return;
    if (videoPlayer.playing) videoPlayer.pause();
    else videoPlayer.play();
  }, [requestToggle, videoPlayer]);

  // pauseRequest/resumeRequest come from MediaContext — e.g. the in-app
  // YouTube browser pausing this on the way in and resuming it on the way
  // out. Explicit pause/play rather than a toggle, since a caller here has
  // already checked whether it should act at all.
  useEffect(() => {
    if (pauseRequest === 0) return;
    try {
      videoPlayer.pause();
    } catch {
      // no-op: player may already be released
    }
  }, [pauseRequest, videoPlayer]);

  useEffect(() => {
    if (resumeRequest === 0) return;
    try {
      videoPlayer.play();
    } catch {
      // no-op: player may already be released
    }
  }, [resumeRequest, videoPlayer]);

  // seekRequest carries the target second; bumping it (even to the same
  // value) is detected via a paired nonce from the parent.
  useEffect(() => {
    if (seekRequest == null) return;
    // expo-video's seek API is assigning to this native shared object's
    // `.currentTime`, not a React-tracked value — safe despite the lint rule.
    // eslint-disable-next-line
    videoPlayer.currentTime = seekRequest;
  }, [seekRequest, videoPlayer]);

  return (
    <VideoView
      style={styles.videoPlayer}
      player={videoPlayer}
      allowsPictureInPicture
    />
  );
}

export default function PlayerScreen() {
  const {
    downloads,
    activeTrack,
    activePlaylistId,
    isPlaying,
    audioProgress,
    playlists,
    togglePlayPause,
    seekAudioTo,
    videoPlaying,
    reportVideoPlaying,
    videoPauseToken,
    videoResumeToken,
    playMedia,
    playNext,
    playPrevious,
    shuffleEnabled,
    toggleShuffle,
    repeatMode,
    cycleRepeatMode,
    notifyTrackEnded,
    upcomingQueue,
  } = useMedia();
  const [selectedPlaylistId, setSelectedPlaylistId] = useState<string | null>(
    activePlaylistId,
  );
  // Snap the filter chip to whatever playlist just started playing (from
  // here or anywhere else — the Playlists tab, a playlist's detail screen).
  // Adjusting state during render (React's recommended pattern for deriving
  // state from a prop-like value) rather than in an effect — only reacts to
  // activePlaylistId actually changing, so freely browsing other chips
  // afterward doesn't get overridden while that same queue is still playing.
  const [lastSyncedPlaylistId, setLastSyncedPlaylistId] = useState(activePlaylistId);
  if (activePlaylistId !== lastSyncedPlaylistId) {
    setLastSyncedPlaylistId(activePlaylistId);
    setSelectedPlaylistId(activePlaylistId);
  }
  const [videoToggleRequest, setVideoToggleRequest] = useState(0);
  const [videoProgress, setVideoProgress] = useState({ currentTime: 0, duration: 0 });
  const [videoSeekRequest, setVideoSeekRequest] = useState<number | null>(null);
  const [scrubValue, setScrubValue] = useState<number | null>(null);

  const filteredMedia = useMemo(() => {
    if (!selectedPlaylistId) return downloads;
    const playlist = playlists.find((p) => p.id === selectedPlaylistId);
    if (!playlist) return downloads;
    return playlist.mediaIds
      .map((id) => downloads.find((d) => d.id === id))
      .filter((d): d is MediaItem => Boolean(d));
  }, [downloads, playlists, selectedPlaylistId]);

  const isVideoTrack = activeTrack?.type === "video";
  const barIsPlaying = isVideoTrack ? videoPlaying : isPlaying;
  const upNext = upcomingQueue[0];

  const progress = isVideoTrack ? videoProgress : audioProgress ?? { currentTime: 0, duration: 0 };
  const displayPosition = scrubValue ?? progress.currentTime;

  const handleBarToggle = () => {
    if (isVideoTrack) {
      setVideoToggleRequest((n) => n + 1);
    } else {
      togglePlayPause();
    }
  };

  const handleSeekComplete = (value: number) => {
    if (isVideoTrack) {
      setVideoSeekRequest(value);
    } else {
      seekAudioTo(value);
    }
    setScrubValue(null);
  };

  const repeatColor = repeatMode === "off" ? "#888" : "#1DB954";

  return (
    <View style={styles.container}>
      {/* Native Video Viewport with Picture-in-Picture */}
      {activeTrack?.type === "video" && (
        <VideoContainer
          item={activeTrack}
          repeatMode={repeatMode}
          onPlayingChange={reportVideoPlaying}
          onProgress={(currentTime, duration) => setVideoProgress({ currentTime, duration })}
          requestToggle={videoToggleRequest}
          pauseRequest={videoPauseToken}
          resumeRequest={videoResumeToken}
          seekRequest={videoSeekRequest}
          onEnded={notifyTrackEnded}
        />
      )}

      {/* Playlist Filtering Bar */}
      <View style={{ height: 40, marginVertical: 10 }}>
        <ScrollView horizontal showsHorizontalScrollIndicator={false}>
          <TouchableOpacity
            style={[styles.chip, selectedPlaylistId === null && styles.activeChip]}
            onPress={() => setSelectedPlaylistId(null)}
          >
            <Text style={styles.chipText}>All</Text>
          </TouchableOpacity>
          {playlists.map((pl) => (
            <TouchableOpacity
              key={pl.id}
              style={[styles.chip, selectedPlaylistId === pl.id && styles.activeChip]}
              onPress={() => setSelectedPlaylistId(pl.id)}
            >
              <Text style={styles.chipText}>{pl.name}</Text>
            </TouchableOpacity>
          ))}
        </ScrollView>
      </View>

      {/* Track List */}
      <FlatList
        data={filteredMedia}
        keyExtractor={(item) => item.id}
        ListEmptyComponent={
          <Text style={styles.emptyText}>
            {selectedPlaylistId ? "This playlist is empty." : "No downloads yet."}
          </Text>
        }
        renderItem={({ item }) => (
          <TouchableOpacity
            style={styles.trackRow}
            onPress={() => playMedia(item, filteredMedia, selectedPlaylistId)}
          >
            <Ionicons
              name={
                item.id === activeTrack?.id ? "stats-chart" : "musical-note"
              }
              size={20}
              color={item.id === activeTrack?.id ? "#1DB954" : "#888"}
            />
            <View style={{ flex: 1, marginLeft: 10 }}>
              <Text
                style={[
                  styles.trackTitle,
                  item.id === activeTrack?.id && { color: "#1DB954" },
                ]}
                numberOfLines={1}
              >
                {item.title}
              </Text>
              {item.channel && (
                <Text style={styles.trackSub} numberOfLines={1}>
                  {item.channel}
                </Text>
              )}
            </View>
          </TouchableOpacity>
        )}
      />

      {/* Persistent Bottom Playback Bar (audio or video) */}
      {activeTrack && (
        <View style={styles.playerBar}>
          <View style={styles.nowPlayingRow}>
            <View style={{ flex: 1 }}>
              <Text style={styles.nowPlayingTitle} numberOfLines={1}>
                {activeTrack.title}
              </Text>
              <Text style={styles.nowPlayingSub} numberOfLines={1}>
                {upNext ? `Up next: ${upNext.title}` : isVideoTrack ? "Playing video" : "Playing audio"}
              </Text>
            </View>
          </View>

          <View style={styles.progressRow}>
            <Text style={styles.timeText}>{formatTime(displayPosition)}</Text>
            <Slider
              style={styles.slider}
              minimumValue={0}
              maximumValue={progress.duration > 0 ? progress.duration : 1}
              value={displayPosition}
              minimumTrackTintColor="#1DB954"
              maximumTrackTintColor="#444"
              thumbTintColor="#1DB954"
              onValueChange={setScrubValue}
              onSlidingComplete={handleSeekComplete}
            />
            <Text style={styles.timeText}>{formatTime(progress.duration)}</Text>
          </View>

          <View style={styles.controlsRow}>
            <TouchableOpacity onPress={toggleShuffle} style={styles.sideControl}>
              <Ionicons
                name="shuffle"
                size={20}
                color={shuffleEnabled ? "#1DB954" : "#888"}
              />
            </TouchableOpacity>

            <TouchableOpacity onPress={playPrevious} style={styles.sideControl}>
              <Ionicons name="play-skip-back" size={26} color="#FFF" />
            </TouchableOpacity>

            <TouchableOpacity onPress={handleBarToggle}>
              <Ionicons
                name={barIsPlaying ? "pause-circle" : "play-circle"}
                size={52}
                color="#1DB954"
              />
            </TouchableOpacity>

            <TouchableOpacity onPress={playNext} style={styles.sideControl}>
              <Ionicons name="play-skip-forward" size={26} color="#FFF" />
            </TouchableOpacity>

            <TouchableOpacity onPress={cycleRepeatMode} style={styles.sideControl}>
              <View>
                <Ionicons name="repeat" size={20} color={repeatColor} />
                {repeatMode === "one" && <View style={styles.repeatOneBadge} />}
              </View>
            </TouchableOpacity>
          </View>
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: "#121212", padding: 16 },
  videoPlayer: { width: "100%", height: 200, borderRadius: 8 },
  chip: {
    backgroundColor: "#2A2A2A",
    paddingHorizontal: 14,
    paddingVertical: 8,
    borderRadius: 16,
    marginRight: 8,
  },
  activeChip: { backgroundColor: "#1DB954" },
  chipText: { color: "#FFF", fontSize: 12, fontWeight: "600" },
  trackRow: {
    flexDirection: "row",
    alignItems: "center",
    paddingVertical: 12,
    borderBottomWidth: 1,
    borderBottomColor: "#222",
  },
  trackTitle: { color: "#FFF", fontSize: 14, fontWeight: "500" },
  trackSub: { color: "#666", fontSize: 12 },
  emptyText: { color: "#666", fontSize: 13, textAlign: "center", marginTop: 40 },
  playerBar: {
    backgroundColor: "#222",
    padding: 12,
    borderRadius: 8,
    marginTop: 10,
  },
  nowPlayingRow: { flexDirection: "row", alignItems: "center" },
  nowPlayingTitle: { color: "#FFF", fontWeight: "bold" },
  nowPlayingSub: { color: "#888", fontSize: 12, marginTop: 2 },
  progressRow: { flexDirection: "row", alignItems: "center", marginTop: 4 },
  slider: { flex: 1, height: 32, marginHorizontal: 4 },
  timeText: { color: "#888", fontSize: 11, width: 36, textAlign: "center" },
  controlsRow: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "space-evenly",
    marginTop: 4,
  },
  sideControl: { padding: 4 },
  repeatOneBadge: {
    position: "absolute",
    top: -2,
    right: -4,
    width: 6,
    height: 6,
    borderRadius: 3,
    backgroundColor: "#1DB954",
  },
});
