import React, { useState, useEffect, useCallback, ReactNode } from 'react';
import SongTable from './SongTable';
import NowPlaying from './NowPlaying';
import AddSongPanel from './AddSongPanel';
import RenameFavoriteModal from './RenameFavoriteModal';
import { TabView, Tab } from './TabView';
import { API, getDisplayTitle, PlaylistItem, ServerEvent } from '../api/player';
import { FaMusic, FaHeart, FaPlus, FaEdit } from 'react-icons/fa';
import useWebSocket from 'react-use-websocket';
import classNames from 'classnames';

enum Tabs {
  Playlist = "playlist",
  Favorites = "favorites",
}

const EmptyContent: React.FC<{ label: string}> = ({label}) => (
  <div className="flex items-center justify-center h-full">
    <div className="text-white text-2xl font-bold">{label}</div>
  </div>
);

interface SonglistContentProps {
  songs: PlaylistItem[];
  isPlaying: boolean;
  auxControlProvider?: (song: PlaylistItem) => ReactNode;
  onNeedsRefresh: () => void;
}

const PlaylistContent: React.FC<SonglistContentProps> = ({ songs, isPlaying, auxControlProvider, onNeedsRefresh }) => {
  const handleDelete = (index: number) => {
    API.removeFromPlaylist(index);
    onNeedsRefresh();
  };

  const handleSkipTo = (index: number) => {
    API.skipTo(index);
    onNeedsRefresh();
  };

  return (
    songs.length > 0 ? (
      <SongTable 
        songs={songs} 
        isPlaying={isPlaying} 
        auxControlProvider={auxControlProvider}
        onDelete={handleDelete} 
        onSkipTo={handleSkipTo} 
      />
    ) : (
      <EmptyContent label="Playlist is empty" />
    )
  );
};

const FavoritesContent: React.FC<SonglistContentProps> = ({ songs, isPlaying, auxControlProvider, onNeedsRefresh }) => {
  const handleDelete = (index: number) => {
    API.removeFromFavorites(songs[index].filename);
    onNeedsRefresh();
  };

  const handleSkipTo = (index: number) => {
    API.replaceCurrentFile(songs[index].filename);
    API.play();
    onNeedsRefresh();
  };

  return (
    songs.length > 0 ? (
      <SongTable 
        songs={songs} 
        isPlaying={isPlaying} 
        auxControlProvider={auxControlProvider}
        onDelete={handleDelete} 
        onSkipTo={handleSkipTo} 
      />
    ) : (
      <EmptyContent label="Favorites are empty" />
    )
  );
};

const App: React.FC = () => {
  const [isPlaying, setIsPlaying] = useState(false);
  const [nowPlayingSong, setNowPlayingSong] = useState<string | null>(null);
  const [nowPlayingFileName, setNowPlayingFileName] = useState<string | null>(null);
  const [volume, setVolume] = useState(100);
  const [volumeSettingIsLocked, setVolumeSettingIsLocked] = useState(false);
  const [playlist, setPlaylist] = useState<PlaylistItem[]>([]);
  const [favorites, setFavorites] = useState<PlaylistItem[]>([]);
  const [selectedTab, setSelectedTab] = useState<Tabs>(Tabs.Playlist);
  const [isRenameModalOpen, setIsRenameModalOpen] = useState(false);
  const [favoriteToRename, setFavoriteToRename] = useState<PlaylistItem | null>(null);

  const fetchPlaylist = useCallback(async () => {
    const playlist = await API.getPlaylist();
    setPlaylist(playlist);
  }, []);

  const fetchFavorites = useCallback(async () => {
    const favorites = await API.getFavorites();
    setFavorites(favorites);
  }, []);

  const fetchNowPlaying = useCallback(async () => {
    if (volumeSettingIsLocked) {
      // We are actively changing the volume, which we do actually want to send events
      // continuously to the server, but we don't want to refresh our state while doing that. 
      return;
    }

    const nowPlaying = await API.getNowPlaying();
    setNowPlayingSong(getDisplayTitle(nowPlaying.playingItem));
    setNowPlayingFileName(nowPlaying.playingItem.filename);
    setIsPlaying(!nowPlaying.isPaused);
    setVolume(nowPlaying.volume);
  }, [volumeSettingIsLocked]);

  const handleAddURL = async (url: string) => {
    const urlToAdd = url.trim();
    if (urlToAdd) {
      if (selectedTab === Tabs.Favorites) {
        await API.addToFavorites(urlToAdd);
        fetchFavorites();
      } else {
        await API.addToPlaylist(urlToAdd);
        fetchPlaylist();
      }

      if (!isPlaying) {
        await API.play();
      }
    }
  };

  const togglePlayPause = async () => {
    if (isPlaying) {
      await API.pause();
    } else {
      await API.play();
    }

    fetchNowPlaying();
  };

  const handleSkip = async () => {
    await API.skip();
    fetchNowPlaying();
  };

  const handlePrevious = async () => {
    await API.previous();
    fetchNowPlaying();
  };

  const handleVolumeSettingChange = async (volume: number) => {
    setVolume(volume);
    await API.setVolume(volume);
  };

  const handleWebSocketEvent = useCallback((message: MessageEvent) => {
    const event = JSON.parse(message.data);
    switch (event.event) {
      case ServerEvent.PlaylistUpdate:
      case ServerEvent.NowPlayingUpdate:
      case ServerEvent.MetadataUpdate:
      case ServerEvent.MPDUpdate:
        fetchPlaylist();
        fetchNowPlaying();
        break;
      case ServerEvent.VolumeUpdate:
        if (!volumeSettingIsLocked) {
          fetchNowPlaying();
        }

        break;
      case ServerEvent.FavoritesUpdate:
        fetchFavorites();
        break;
    }
  }, [fetchPlaylist, fetchNowPlaying, fetchFavorites]);

  const wsUrl = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/api/events`;
  console.log('Connecting to WebSocket at', wsUrl);
  useWebSocket(wsUrl, {
    onOpen: () => {
      console.log('WebSocket connected');
    },
    onClose: () => {
      console.log('WebSocket disconnected');
    },
    onError: (error) => {
      console.error('WebSocket error:', error);
    },
    onMessage: handleWebSocketEvent,
    shouldReconnect: () => true,
  });

  // Handle visibility changes
  useEffect(() => {
    const handleVisibilityChange = () => {
      if (document.visibilityState === 'visible') {
        fetchPlaylist();
        fetchNowPlaying();
      }
    };

    document.addEventListener('visibilitychange', handleVisibilityChange);
    return () => {
      document.removeEventListener('visibilitychange', handleVisibilityChange);
    };
  }, [fetchPlaylist, fetchNowPlaying]);

  const refreshContent = () => {
    fetchPlaylist();
    fetchNowPlaying();
    fetchFavorites();
  }

  // Initial data fetch
  useEffect(() => {
    fetchPlaylist();
    fetchNowPlaying();
    fetchFavorites();
  }, [fetchPlaylist, fetchNowPlaying, fetchFavorites]);

  const AuxButton: React.FC<{ children: ReactNode, className: string, title: string, onClick: () => void }> = (props) => (
    <button 
      className={
        classNames("hover:text-white transition-colors px-3 py-1 rounded", props.className)
      } 
      title={props.title} 
      onClick={props.onClick}
    >
      {props.children}
    </button>
  );

  const playlistAuxControlProvider = (song: PlaylistItem) => {
    const isFavorite = favorites.some(f => f.filename === song.filename);
    return (
      <AuxButton
        className={classNames({
          "text-red-500": isFavorite,
          "text-white/40": !isFavorite,
        })}
        title={isFavorite ? "Remove from favorites" : "Add to favorites"}
        onClick={() => {
          if (isFavorite) {
            API.removeFromFavorites(song.filename);
          } else {
            API.addToFavorites(song.filename);
          }
        }}
      >
        <FaHeart />
      </AuxButton>
    );
  };

  const favoritesAuxControlProvider = (song: PlaylistItem) => {
    const isInPlaylist = playlist.some(p => p.filename === song.filename);
    return (
      <div className="flex">
        <AuxButton
          className="text-white hover:text-white"
          title="Rename favorite"
          onClick={() => {
            setFavoriteToRename(song);
            setIsRenameModalOpen(true);
          }}
        >
          <FaEdit />
        </AuxButton>
        
        <AuxButton
          className={classNames({
            "text-white/40": isInPlaylist,
            "text-white": !isInPlaylist,
          })}
          title={isInPlaylist ? "Remove from playlist" : "Add to playlist"}
          onClick={() => {
            if (isInPlaylist) {
              API.removeFromPlaylist(playlist.findIndex(p => p.filename === song.filename));
            } else {
              API.addToPlaylist(song.filename);
            }
          }}
        >
          <FaPlus />
        </AuxButton>
      </div>
    );
  };

  return (
    <div className="flex items-center justify-center h-screen w-screen bg-black md:py-10">
      <div className="bg-violet-900 w-full md:max-w-2xl h-full md:max-h-xl md:border md:rounded-2xl flex flex-col">
        <NowPlaying 
          className="flex flex-row md:rounded-t-2xl" 
          songName={nowPlayingSong || "(Not Playing)"} 
          fileName={nowPlayingFileName || ""} 
          isPlaying={isPlaying} 
          onPlayPause={togglePlayPause} 
          onSkip={handleSkip} 
          onPrevious={handlePrevious} 
          volume={volume}
          onVolumeSettingChange={handleVolumeSettingChange}
          onVolumeWillChange={() => setVolumeSettingIsLocked(true)}
          onVolumeDidChange={() => setVolumeSettingIsLocked(false)}
        />

        <TabView selectedTab={selectedTab} onTabChange={setSelectedTab}>
          <Tab label="Playlist" identifier={Tabs.Playlist} icon={<FaMusic />}>
            <PlaylistContent 
              songs={playlist} 
              isPlaying={isPlaying} 
              onNeedsRefresh={refreshContent} 
              auxControlProvider={playlistAuxControlProvider}
            />
          </Tab>
          <Tab label="Favorites" identifier={Tabs.Favorites} icon={<FaHeart />}>
            <FavoritesContent 
              songs={favorites.map(f => ({ ...f, playing: f.filename === nowPlayingFileName }))} 
              isPlaying={isPlaying} 
              onNeedsRefresh={refreshContent} 
              auxControlProvider={favoritesAuxControlProvider}
            />
          </Tab>
        </TabView>

        <AddSongPanel onAddURL={handleAddURL} />
        
        <RenameFavoriteModal 
          isOpen={isRenameModalOpen}
          onClose={() => setIsRenameModalOpen(false)}
          favorite={favoriteToRename}
        />
      </div>
    </div>
  );
};

export default App;
