using FNZCM.BlazorWasm.Models.Fe;
using System.Collections;
using System.Collections.ObjectModel;

namespace FNZCM.BlazorWasm.Helpers
{
    public class PlaylistHelper
    {
        public event Action Changed;

        private readonly LocalStorageHelper localStorage;

        public FePlaylist[] PlaylistTableStore
        {
            get => localStorage.GetByCallerMemberName<FePlaylist[]>() ?? Array.Empty<FePlaylist>();
            private set => localStorage.SetByCallerMemberName(value);
        }

        public IReadOnlyDictionary<Guid, FePlaylist> PlayListLoadSave { get; }

        public PlaylistHelper(LocalStorageHelper localStorage)
        {
            this.localStorage = localStorage;

            PlayListLoadSave = new ListTableDictAdapter(this);

            //import old data and remove
            var oldTable = localStorage.Get<Dictionary<Guid, string>>("PlayListLoadSave");
            if (oldTable is not { Count: > 0 }) return;

            var newTable = PlaylistTableStore.ToList();

            var index = 0;
            foreach (var item in oldTable)
            {
                newTable.Insert(index++, new FePlaylist { Id = item.Key, Name = item.Value });
            }

            PlaylistTableStore = newTable.ToArray();
            localStorage.Remove("PlayListLoadSave");
        }

        public class ListTableDictAdapter : IReadOnlyDictionary<Guid, FePlaylist>
        {
            private readonly PlaylistHelper context;

            public ListTableDictAdapter(PlaylistHelper context) => this.context = context;

            public int Count => context.PlaylistTableStore.Length;

            public IEnumerator<KeyValuePair<Guid, FePlaylist>> GetEnumerator() => context.PlaylistTableStore.Select(p => new KeyValuePair<Guid, FePlaylist>(p.Id, p)).GetEnumerator();

            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

            public bool ContainsKey(Guid key) => context.PlaylistTableStore.Any(p => p.Id == key);

            public bool TryGetValue(Guid key, out FePlaylist value) => null != (value = context.PlaylistTableStore.FirstOrDefault(p => p.Id == key));

            public FePlaylist this[Guid key] => context.PlaylistTableStore.FirstOrDefault(p => p.Id == key);

            public IEnumerable<Guid> Keys => context.PlaylistTableStore.Select(p => p.Id);
            public IEnumerable<FePlaylist> Values => context.PlaylistTableStore;
        }

        public List<string> this[Guid playlistId]
        {
            get => localStorage.GetByCallerMemberName<List<string>>($"playlist-{playlistId}") ?? new List<string>();
            set => localStorage.SetByCallerMemberName(value, $"playlist-{playlistId}");
        }

        public void CreateNewPlaylist(string name)
        {
            var newItem = new FePlaylist { Name = name, Id = Guid.NewGuid() };
            var newTable = PlaylistTableStore.ToList();
            newTable.Add(newItem);
            PlaylistTableStore = newTable.ToArray();
            Changed?.Invoke();
        }

        public void AddTrackToPlaylist(string trackPath, Guid playlistId)
        {
            var p = this[playlistId];
            p.Add(trackPath);
            this[playlistId] = p;
            Changed?.Invoke();
        }

        public void RemoveTrackInPlaylist(Guid playlistId, int index, string trackPathForConfirm)
        {
            var p = this[playlistId];
            if (index < p.Count && p[index] == trackPathForConfirm) p.RemoveAt(index);
            this[playlistId] = p;
            Changed?.Invoke();
        }

        public void DeletePlaylist(Guid playlistId)
        {
            localStorage.RemoveByCallerMemberName($"playlist-{playlistId}");

            var newTableArray = PlaylistTableStore;
            var item = newTableArray.FirstOrDefault(p => p.Id == playlistId);

            if (item == null) return;

            var newTable = newTableArray.ToList();
            newTable.Remove(item);
            PlaylistTableStore = newTable.ToArray();

            Changed?.Invoke();
        }

        public void UpdatePlaylistName(Guid playlistId, string name)
        {
            var newTableArray = PlaylistTableStore;
            var item = newTableArray.FirstOrDefault(p => p.Id == playlistId);

            if (item == null) return;

            item.Name = name;
            PlaylistTableStore = newTableArray.ToArray();

            Changed?.Invoke();
        }

        public void ListMoveUp(int index)
        {
            if (index <= 0) return;
            var l = new ObservableCollection<FePlaylist>(PlaylistTableStore);
            l.Move(index, index - 1);
            PlaylistTableStore = l.ToArray();
            Changed?.Invoke();
        }

        public void ListMoveDown(int index)
        {
            var l = new ObservableCollection<FePlaylist>(PlaylistTableStore);
            if (index >= l.Count - 1) return;
            l.Move(index, index + 1);
            PlaylistTableStore = l.ToArray();
            Changed?.Invoke();
        }

        public void ListMove(Guid id, int index)
        {
            if (index < 0) return;
            var l = new ObservableCollection<FePlaylist>(PlaylistTableStore);
            if (index >= l.Count) return;
            var item = l.FirstOrDefault(p => p.Id == id);
            if (item == null) return;
            var oldIndex = l.IndexOf(item);
            l.Move(oldIndex, index);
            PlaylistTableStore = l.ToArray();
            Changed?.Invoke();
        }

        public void TrackMoveUp(Guid playlistId, int index)
        {
            if (index <= 0) return;
            var l = new ObservableCollection<string>(this[playlistId]);
            l.Move(index, index - 1);
            this[playlistId] = l.ToList();
            Changed?.Invoke();
        }

        public void TrackMoveDown(Guid playlistId, int index)
        {
            var l = new ObservableCollection<string>(this[playlistId]);
            if (index >= l.Count - 1) return;
            l.Move(index, index + 1);
            this[playlistId] = l.ToList();
            Changed?.Invoke();
        }

        public void TrackMove(Guid fromListId, int fromTrackIndex, string fromTrackPath, Guid toListId, int toTrackIndex)
        {
            var fromList = this[fromListId];
            if (fromList == null) return;
            if (fromTrackIndex > fromList.Count - 1) return;
            if (fromList[fromTrackIndex] != fromTrackPath) return;

            fromList.RemoveAt(fromTrackIndex);

            if (fromListId != toListId)
            {
                var toList = this[toListId];
                if (toList == null) return;
                if (toTrackIndex > toList.Count) return;

                toList.Insert(toTrackIndex, fromTrackPath);

                this[toListId] = toList;
            }
            else
            {
                fromList.Insert(toTrackIndex, fromTrackPath);
            }

            this[fromListId] = fromList;

            Changed?.Invoke();
        }
    }
}
