using Harmonia.Core.Extensions;
using Harmonia.Core.Models;

namespace Harmonia.Core.Playlists;

public class Playlist
{
    public string UID { get; init; } = Guid.NewGuid().ToString();
    public string? Name { get; set; }
    public List<PlaylistSong> Songs { get; init; } = []; // TODO: Change to "private init" once deserialization is fixed
    public List<GroupOption> GroupOptions { get; set; } = [];
    public List<SortOption> SortOptions { get; set; } = [];
    public bool IsLocked { get; set; }

    public event EventHandler<PlaylistUpdatedEventArgs>? PlaylistUpdated;

    public void AddSong(Song song, int? index = null)
    {
        AddSongs([song], index);
    }

    public void AddSongs(Song[] songs, int? index = null)
    {
        PlaylistSong[] playlistSongs = [.. songs.Select(song => new PlaylistSong(song))];

        AddSongs(playlistSongs, index);
    }

    private void AddSongs(PlaylistSong[] playlistSongs, int? index = null)
    {
        if (IsLocked)
            return;

        if (playlistSongs.Length == 0)
            return;

        int insertIndex = index ?? Songs.Count;

        Songs.InsertRange(insertIndex, playlistSongs);

        PlaylistUpdatedEventArgs eventArgs = new()
        {
            Action = PlaylistUpdateAction.Add,
            Index = insertIndex,
            Count = playlistSongs.Length,
            Songs = playlistSongs
        };

        PlaylistUpdated?.Invoke(this, eventArgs);
    }

    public void MoveSong(PlaylistSong playlistSong, int newIndex)
    {
        int currentIndex = Songs.IndexOf(playlistSong);

        MoveSong(currentIndex, newIndex);
    }

    public void MoveSong(int oldIndex, int newIndex)
    {
        if (IsLocked)
            return;

        if (oldIndex == newIndex)
            return;

        PlaylistSong playlistSong = Songs[oldIndex];

        Songs.Remove(playlistSong);
        Songs.Insert(newIndex, playlistSong);

        PlaylistUpdatedEventArgs eventArgs = new()
        {
            Action = PlaylistUpdateAction.Move,
            Index = oldIndex,
            Count = 1,
            Songs = [playlistSong],
            NewIndex = newIndex
        };

        PlaylistUpdated?.Invoke(this, eventArgs);
    }

    public void SortSongs(PlaylistSong[] playlistSongs, SortOption[] sortOptions)
    {
        Dictionary<int, PlaylistSong> oldPlaylistSongs = playlistSongs
            .OrderBy(Songs.IndexOf)
            .ToDictionary(Songs.IndexOf, playlistSong => playlistSong);

        Song[] songs = [.. playlistSongs.Select(playlistSong => playlistSong.Song)];
        Song[] sortedSongs = [.. songs.SortBy(sortOptions)];

        int currentSortIndex = 0;

        foreach (int index in oldPlaylistSongs.Keys)
        {
            PlaylistSong playlistSong = oldPlaylistSongs[index];
            Song sortedSong = sortedSongs[currentSortIndex++];
            PlaylistSong newPlaylistSong = playlistSongs.First(ps => ps.Song == sortedSong);

            if (newPlaylistSong == playlistSong)
                continue;

            Songs.RemoveAt(index);
            Songs.Insert(index, newPlaylistSong);
        }

        PlaylistUpdatedEventArgs eventArgs = new()
        {
            Action = PlaylistUpdateAction.Reset,
            Index = -1,
            Count = playlistSongs.Length,
            Songs = playlistSongs
        };

        PlaylistUpdated?.Invoke(this, eventArgs);
    }

    public void RemoveSong(int index)
    {
        RemoveSongs(index, 1);
    }

    public void RemoveSongs(int index, int count)
    {
        PlaylistSong[] playlistSongs = [.. Songs.GetRange(index, count)];

        RemoveSongs(playlistSongs);
    }

    public void RemoveSong(PlaylistSong playlistSong)
    {
        RemoveSongs([playlistSong]);
    }

    public void RemoveSongs(PlaylistSong[] playlistSongs)
    {
        if (IsLocked)
            return;

        List<PlaylistSong> removedSongs = [];

        foreach (PlaylistSong playlistSong in playlistSongs)
        {
            if (Songs.Remove(playlistSong))
            {
                removedSongs.Add(playlistSong);
            }
        }

        if (removedSongs.Count == 0)
            return;

        PlaylistUpdatedEventArgs eventArgs = new()
        {
            Action = PlaylistUpdateAction.Remove,
            Index = -1,
            Count = removedSongs.Count,
            Songs = playlistSongs
        };

        PlaylistUpdated?.Invoke(this, eventArgs);
    }

    public void ImportTags(Song[] songs)
    {
        foreach (Song song in songs)
        {
            PlaylistSong[] playlistSongs = [.. Songs.Where(playlistSong =>
                string.Equals(playlistSong.Song.FileName, song.FileName, StringComparison.OrdinalIgnoreCase))];

            foreach (PlaylistSong playlistSong in playlistSongs)
            {
                //playlistSong.Song = song;
                //playlistSong.Song.Update(song);
            }
        }
    }

    public void RemoveMissingSongs()
    {
        PlaylistSong[] missingSongs = [.. Songs.Where(playlistSong =>
            File.Exists(playlistSong.Song.FileName) == false)];

        if (missingSongs.Length == 0)
            return;

        RemoveSongs(missingSongs);
    }

    public void RemoveDuplicateSongs()
    {
        List<PlaylistSong> songsToRemove = [];

        string[] fileNames = Songs
            .GroupBy(x => x.Song.FileName)
            .Where(x => x.Count() > 1)
            .Select(x => x.Key)
            .ToArray();

        foreach (string fileName in fileNames)
        {
            List<PlaylistSong> songs = Songs.Where(x =>
                string.Equals(x.Song.FileName, fileName, StringComparison.OrdinalIgnoreCase)).ToList();

            for (int i = songs.Count - 1; i > 0; i--)
                songsToRemove.Add(songs[i]);
        }

        if (songsToRemove.Count == 0)
            return;

        RemoveSongs(songsToRemove.ToArray());
    }
}