using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using KitsuneCafe.UI.MVVM;
using Unity.Properties;
using UnityEngine;
using UnityEngine.UIElements;

namespace KitsuneCafe.UI
{
  public class RecyclerViewModel
  {
    public record RotateEvent(Direction Direction, int FirstIndex);

    public enum Direction
    {
      None = 0,
      Clockwise = -1,
      CounterClockwise = 1
    }

    private int displayCount;
    public int DisplayCount
    {
      get => displayCount;
      set
      {
        if (displayCount != value)
        {
          displayCount = value;
          CreateIndices();
        }
      }
    }

    private IList itemSource;
    public IList ItemSource
    {
      get => itemSource;
      set
      {
        if (itemSource != value)
        {
          itemSource = value;
          CreateIndices();
        }
      }
    }

    public int Count => Math.Min(DisplayCount, ItemSource.Count);

    private List<int> indices;
    public IReadOnlyList<int> Indices => indices;

    public readonly ICommand<Direction> RotateCommand;

    public event EventHandler<RotateEvent> Rotated = delegate { };

    public RecyclerViewModel()
    {
      indices = new();
      RotateCommand = new RelayCommand<Direction>(CanRotate, Rotate);
    }

    private void CreateIndices()
    {
      if (itemSource == null || displayCount == 0) { return; }
      indices = Enumerable.Range(0, Math.Min(DisplayCount, ItemSource.Count)).ToList();
    }

    private bool CanRotate(Direction direction)
    {
      return direction == Direction.Clockwise || direction == Direction.CounterClockwise;
    }

    public void Rotate(Direction direction)
    {
      if (direction == Direction.Clockwise)
      {
        indices.RemoveAt(0);
        var next = indices.Last() + 1;
        next %= ItemSource.Count;
        indices.Add(next);
        Notify(direction);
      }
      else if (direction == Direction.CounterClockwise)
      {
        indices.RemoveAt(ItemSource.Count - 1);
        var count = ItemSource.Count;
        var previous = indices[0] - 1;
        previous += count % count;
        indices.Insert(0, previous);
        Notify(direction);
      }
    }

    private void Notify(Direction direction)
    {
      Rotated?.Invoke(this, new RotateEvent(direction, indices.First()));
    }
  }

  public class SelectEvent : EventBase<SelectEvent>
  {
    public int ElementIndex { get; set; }
    public int DataIndex { get; set; }
  }

  [UxmlElement]
  public partial class RecyclerView : VisualElement
  {
    public const string BaseClass = "kitsunecafe__recycler-view";

    [UxmlAttribute, CreateProperty]
    public int DisplayCount
    {
      get => viewModel.DisplayCount;
      set
      {
        viewModel.DisplayCount = value;
        CreateItems();
      }
    }

    [CreateProperty]
    public IList ItemSource
    {
      get => viewModel.ItemSource;
      set
      {
        if (ItemSource != value)
        {
          viewModel.ItemSource = value;
          CreateItems();
          Rebind();
        }
      }
    }

    private IBinder binder;

    [CreateProperty]
    public IBinder Binder
    {
      get => binder;
      set
      {
        if (binder != value)
        {
          binder = value;
          CreateItems();
          Rebind();
        }
      }
    }

    private readonly RecyclerViewModel viewModel = new();
    private readonly DragManipulator drag;

    public RecyclerView()
    {
      AddToClassList(BaseClass);

      drag = new DragManipulator(this);
      drag.Dragged += OnDrag;
      this.AddManipulator(drag);

      RegisterCallback<NavigationMoveEvent>(OnNavigationMove);
      viewModel.Rotated += OnRotated;
    }

    private void CreateItems()
    {
      Clear();
      if (DisplayCount == 0 || binder == null) { return; }
      for (int i = 0; i < DisplayCount; i++)
      {
        Add(binder.CreateItem());
      }

      FocusFirst();
    }

    private void Rebind()
    {
      if (viewModel == null || ItemSource == null || DisplayCount == 0 || binder == null) { return; }

      for (int i = 0; i < DisplayCount; i++)
      {
        TryBindItem(this[i], i);
      }
    }

    private bool HasItem(int index)
    {
      return 0 <= index && index < ItemSource.Count;
    }

    private void OnRotated(object sender, RecyclerViewModel.RotateEvent e)
    {
      VisualElement recycled;
      switch (e.Direction)
      {
        case RecyclerViewModel.Direction.Clockwise:
          recycled = this[0];
          RemoveAt(0);
          Insert(DisplayCount - 1, recycled);
          TryBindItem(recycled, DisplayCount - 1);
          NotifySelection();
          break;
        case RecyclerViewModel.Direction.CounterClockwise:
          recycled = this[DisplayCount - 1];
          RemoveAt(DisplayCount - 1);
          Insert(0, recycled);
          TryBindItem(recycled, 0);
          NotifySelection();
          break;
      }

      FocusFirst();
    }

    private void NotifySelection()
    {
      using SelectEvent evt = SelectEvent.GetPooled();
      evt.target = this[0];
      evt.ElementIndex = 0;
      evt.DataIndex = viewModel.Indices[0];
      SendEvent(evt);
    }

    private void FocusFirst()
    {
      if (childCount > 0)
      {
        this[0].Focus();
      }
    }

    private void TryBindItem(VisualElement element, int index)
    {
      if (HasItem(index))
      {
        var idx = viewModel.Indices[index];
        if (0 <= idx && idx < ItemSource.Count)
        {
          binder.BindItem(element, idx);
        }
      }
      else
      {
        binder.UnbindItem(element);
      }
    }

    private void OnDrag(Vector2 vector)
    {
      throw new NotImplementedException();
    }

    private void OnNavigationMove(NavigationMoveEvent evt)
    {
      var direction = evt.direction switch
      {
        NavigationMoveEvent.Direction.Left => RecyclerViewModel.Direction.CounterClockwise,
        NavigationMoveEvent.Direction.Right => RecyclerViewModel.Direction.Clockwise,
        NavigationMoveEvent.Direction.Next => RecyclerViewModel.Direction.Clockwise,
        NavigationMoveEvent.Direction.Previous => RecyclerViewModel.Direction.CounterClockwise,
        _ => RecyclerViewModel.Direction.None
      };

      viewModel.RotateCommand.TryExecute(direction);
    }
  }
}
