using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Harmonia.Core.Playlists;
using Harmonia.UI.ViewModels;
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Harmonia.UI.Views;

public partial class PlaylistView : UserControl
{
    private readonly PlaylistViewModel _viewModel;
    private readonly ConcurrentDictionary<int, CancellationTokenSource> _imageCancellationTokens = [];

    public PlaylistView()
    {
        InitializeComponent();

        _viewModel = (PlaylistViewModel)DataContext!;
        _viewModel.PropertyChanging += OnViewModelPropertyChanging;
        _viewModel.PropertyChanged += OnViewModelPropertyChanged;
    }

    private async void OnBeforeFilteredSongsUpdate(object? sender, System.EventArgs e)
    {
        await Dispatcher.UIThread.InvokeAsync(SlideOutSongs);
    }

    private async void OnAfterFilteredSongsUpdate(object? sender, System.EventArgs e)
    {
        await Dispatcher.UIThread.InvokeAsync(SlideInSongs);
    }

    private void OnViewModelPropertyChanging(object? sender, PropertyChangingEventArgs e)
    {
        switch (e.PropertyName)
        {
            case nameof(_viewModel.PlayingSong):
                RemovePlayingSongClass();
                break;
        }
    }

    private async void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case nameof(_viewModel.FilteredPlaylistSongs):
                await Dispatcher.UIThread.InvokeAsync(SlideInSongs);
                break;
            case nameof(_viewModel.PlayingSong):
                AddPlayingSongClass();
                break;
        }
    }

    private async Task SlideOutSongs()
    {
        await Animations.Animations.SlideToRight(100, duration: 300).RunAsync(PlaylistListBox);
    }

    private async Task SlideInSongs()
    {
        await Animations.Animations.SlideFromRight(100, duration: 300).RunAsync(PlaylistListBox);
    }

    private void RemovePlayingSongClass()
    {
        PlaylistSong? playingSong = _viewModel.PlayingSong;

        if (playingSong == null)
            return;

        Control? control = PlaylistListBox.ContainerFromItem(playingSong);

        if (control is not ListBoxItem listBoxItem)
            return;

        if (listBoxItem.GetVisualChildren().FirstOrDefault() is not ContentPresenter contentPresentor)
            return;

        if (contentPresentor.GetVisualChildren().FirstOrDefault() is not Grid grid)
            return;

        grid.Classes.Remove("Playing");
    }

    private void AddPlayingSongClass()
    {
        PlaylistSong? playingSong = _viewModel.PlayingSong;

        if (playingSong == null)
            return;

        PlaylistListBox.UpdateLayout();
        PlaylistListBox.ScrollIntoView(playingSong);

        Control? control = PlaylistListBox.ContainerFromItem(playingSong);

        if (control is ListBoxItem listBoxItem)
        {
            listBoxItem.BringIntoView();
        }
        else
        {
            PlaylistListBox.ScrollIntoView(playingSong);
        }

        if (control is not ListBoxItem listBoxItem2)
            return;

        if (listBoxItem2.GetVisualChildren().FirstOrDefault() is not ContentPresenter contentPresentor)
            return;

        if (contentPresentor.GetVisualChildren().FirstOrDefault() is not Grid grid)
            return;

        grid.Classes.Add("Playing");
    }

    private async void ListBox_DoubleTapped(object? sender, TappedEventArgs e)
    {
        if (sender is ListBox listBox && listBox.SelectedItem is PlaylistSong playlistSong)
        {
            await _viewModel.PlaySongAsync(playlistSong);
        }
    }

    private void Image_Loaded(object? sender, RoutedEventArgs e)
    {
        if (sender is not Image image)
            return;

        if (image.DataContext is not PlaylistSong playlistSong)
            return;

        Task.Run(() => DoSomethingAsync(image, playlistSong));
    }

    private async Task DoSomethingAsync(Image image, PlaylistSong playlistSong)
    {
        int hashCode = image.GetHashCode();

        _imageCancellationTokens.TryGetValue(hashCode, out CancellationTokenSource? cancellationTokenSource);
        cancellationTokenSource?.Cancel();

        cancellationTokenSource = new();

        Bitmap? bitmap = await _viewModel.GetBitmapAsync(playlistSong, cancellationTokenSource.Token);

        if (bitmap == null)
            return;

        await Dispatcher.UIThread.InvokeAsync(() => SetSongImageSource(image, bitmap));
    }

    private static void SetSongImageSource(Image image, Bitmap bitmap)
    {
        image.Source = bitmap;
    }

    private void Image_Unloaded(object? sender, RoutedEventArgs e)
    {
        if (sender is not Image image)
            return;

        if (image.DataContext is not PlaylistSong)
            return;

        if (image.Source is Bitmap bitmap)
        {
            bitmap.Dispose();
        }

        image.Source = null;

        int hashCode = image.GetHashCode();

        _imageCancellationTokens.TryGetValue(hashCode, out CancellationTokenSource? cancellationTokenSource);
        cancellationTokenSource?.Cancel();
    }

    private void Grid_Loaded(object? sender, RoutedEventArgs e)
    {
        if (sender is not Grid grid)
            return;

        if (grid.Parent is not ListBoxItem listBoxItem)
            return;

        //SetupDragAndDrop(grid, d => d.Set(DataFormats.Text,
        //        $"Text was dragged x times."),
        //    DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link);

        if (PlaylistListBox.ItemFromContainer(listBoxItem) is not PlaylistSong playlistSong)
            return;

        if (playlistSong == _viewModel.PlayingSong)
            grid.Classes.Add("Playing");

        //await Dispatcher.UIThread.InvokeAsync(() => SlideInPlaylistSong(sender));
    }

    private async Task SlideOutPlaylistSong(object sender)
    {
        if (sender is not Animatable animatable)
            return;

        await Animations.Animations.SlideToRight(100, duration: 300).RunAsync(animatable);
    }

    private async Task SlideInPlaylistSong(object sender)
    {
        if (sender is not Animatable animatable)
            return;

        await Animations.Animations.SlideFromRight(100, duration: 300).RunAsync(animatable);
    }

    private void OnPlaylistListBoxDrop(object? sender, DragEventArgs e)
    {

    }

    //private void SetupDragAndDrop(IInputElement draggable, Action<DataObject> factory, DragDropEffects effects)
    //{
    //    draggable.PointerPressed += (sender, e) => StartDrag(sender, factory, e, effects);

    //    AddHandler(DragDrop.DropEvent, Drop);
    //    AddHandler(DragDrop.DragOverEvent, DragOver);
    //    AddHandler(DragDrop.DragEnterEvent, DragEnter);
    //    AddHandler(DragDrop.DragLeaveEvent, DragLeave);
    //}

    private async void StartDrag(object? sender, Action<DataTransfer> factory, PointerPressedEventArgs e, DragDropEffects effects)
    {
        if (e.GetCurrentPoint((Visual)sender!).Properties.IsLeftButtonPressed == false)
        {
            // It was a left click
            Console.WriteLine("Left mouse button pressed!");
            e.Handled = true;
            return;
        }

        var dragData = new DataTransfer();
        factory(dragData);

        var result = await DragDrop.DoDragDropAsync(e, dragData, effects);
    }

    private void DragOver(object? sender, DragEventArgs e)
    {
        if (e.Source == PlaylistListBox)
            return;

        //if (e.Source is Animatable animatable)
        //{
        //    //await Animations.Animations.SlideToRight(200).RunAsync(animatable);
        //    animatable.P
        //}

        //if (e.Source is Grid grid)
        //{
        //    grid.Opacity = 0.5;
        //}

        //if (Equals(e.Source, MoveTarget))
        //{
        //    e.DragEffects &= DragDropEffects.Move;
        //}
        //else
        //{
        //    e.DragEffects &= DragDropEffects.Copy;
        //}

        //// Only allow if the dragged data contains text or filenames.
        //if (!e.Data.Contains(DataFormats.Text)
        //    && !e.Data.Contains(DataFormats.Files)
        //    && !e.Data.Contains(CustomFormat))
        //    e.DragEffects = DragDropEffects.None;
    }

    private void DragEnter(object? sender, DragEventArgs e)
    {
        if (e.Source is ListBoxItem grid)
        {
            grid.Opacity = 0.5;
            //Animations.Animations.SlideToRight(200).RunAsync(grid);
        }
    }

    private void DragLeave(object? sender, DragEventArgs e)
    {
        if (e.Source is ListBoxItem grid)
        {
            grid.Opacity = 1;
            //Animations.Animations.SlideToRight(200).RunAsync(grid);
        }
    }

    private void Drop(object? sender, DragEventArgs e)
    {
        //if (Equals(e.Source, MoveTarget))
        //{
        //    e.DragEffects &= DragDropEffects.Move;
        //}
        //else
        //{
        //    e.DragEffects &= DragDropEffects.Copy;
        //}

        //if (e.Data.Contains(DataFormats.Text))
        //{
        //    DropState.Text = e.Data.GetText();
        //}

        //else if (e.Data.Contains(CustomFormat))
        //{
        //    DropState.Text = e.Data.Get(CustomFormat)?.ToString();
        //}
    }
}