using System;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;

namespace AlicizaX.UI
{
    public class RecyclerView : MonoBehaviour
    {
        [SerializeField] private Direction direction;

        public Direction Direction
        {
            get => direction;
            set => direction = value;
        }

        [SerializeField] private Alignment alignment;

        public Alignment Alignment
        {
            get => alignment;
            set => alignment = value;
        }

        [SerializeField] private RectTransform content;

        public RectTransform Content
        {
            get
            {
                if (content == null)
                {
                    content = transform.GetChild(0).GetComponent<RectTransform>();
                }

                return content;
            }
        }

        [SerializeField] private Vector2 spacing;

        public Vector2 Spacing
        {
            get => spacing;
            set => spacing = value;
        }

        [SerializeField] private Vector2 padding;

        public Vector2 Padding
        {
            get => padding;
            set => padding = value;
        }

        [SerializeField] private bool scroll;

        public bool Scroll
        {
            get => scroll;
            set => scroll = value;
        }

        [SerializeField] private bool snap;

        public bool Snap
        {
            get => snap;
            set => snap = value & scroll;
        }

        [SerializeField, Range(1f, 50f)] private float scrollSpeed = 7f;

        public float ScrollSpeed
        {
            get => scrollSpeed;
            set => scrollSpeed = value;
        }

        [SerializeField, Range(10f, 50f)] private float wheelSpeed = 30f;

        public float WheeelSpeed
        {
            get => wheelSpeed;
            set => wheelSpeed = value;
        }

        [SerializeField] private ViewHolder[] templates;

        public ViewHolder[] Templates
        {
            get => templates;
        }


        [SerializeField] private string _scrollerTypeName;

        [SerializeReference] private Scroller _scroller;

        [SerializeField] private bool _showScrollBar;
        [SerializeReference] private Scrollbar _scrollbar;


        private ViewProvider viewProvider;

        [SerializeField] private string _layoutManagerTypeName;

        [SerializeReference] private LayoutManager _layoutManager;


        private int startIndex, endIndex;
        private int currentIndex;

        public Scroller Scroller => _scroller;

        public int CurrentIndex
        {
            get => currentIndex;
        }

        public bool CanScroll => true;


        public ViewProvider ViewProvider
        {
            get
            {
                viewProvider ??= templates.Length > 1 ? new MixedViewProvider(this, templates) : new SimpleViewProvider(this, templates);
                return viewProvider;
            }
        }

        public Scrollbar Scrollbar => _scrollbar;

        private IAdapter _adapter;

        public Action<int> OnIndexChanged;
        public Action OnScrollValueChanged;
        public Action OnMoveingChanged;

        private void OnValidate()
        {
            if (_scroller != null)
            {
                _scroller.ScrollSpeed = scrollSpeed;
                _scroller.WheelSpeed = wheelSpeed;
            }
        }

        private void OnScrollChanged(float pos)
        {
            _layoutManager.UpdateLayout();

            if (Scrollbar != null)
            {
                Scrollbar.SetValueWithoutNotify(pos / _scroller.MaxPosition);
            }

            if (_layoutManager.IsFullInvisibleStart(startIndex))
            {
                viewProvider.RemoveViewHolder(startIndex);
                startIndex += _layoutManager.Unit;
            }
            else if (_layoutManager.IsFullVisibleStart(startIndex))
            {
                if (startIndex == 0)
                {
                    // TODO Do something, eg: Refresh
                }
                else
                {
                    startIndex -= _layoutManager.Unit;
                    viewProvider.CreateViewHolder(startIndex);
                }
            }

            if (_layoutManager.IsFullInvisibleEnd(endIndex))
            {
                viewProvider.RemoveViewHolder(endIndex);
                endIndex -= _layoutManager.Unit;
            }
            else if (_layoutManager.IsFullVisibleEnd(endIndex))
            {
                if (endIndex >= viewProvider.GetItemCount() - _layoutManager.Unit)
                {
                    // TODO Do something, eg: Load More
                }
                else
                {
                    endIndex += _layoutManager.Unit;
                    viewProvider.CreateViewHolder(endIndex);
                }
            }

            // 使用滚动条快速定位时，刷新整个列表
            if (!_layoutManager.IsVisible(startIndex) || !_layoutManager.IsVisible(endIndex))
            {
                Refresh();
            }

            _layoutManager.DoItemAnimation();

            OnScrollValueChanged?.Invoke();
        }

        private void OnMoveing()
        {
            OnMoveingChanged?.Invoke();
        }

        private void OnMoveStoped()
        {
            if (Snap)
            {
                SnapTo();
            }
        }

        private void OnScrollbarChanged(float ratio)
        {
            _scroller.ScrollToRatio(ratio);
        }

        private void OnScrollbarDragEnd()
        {
            if (_scroller.Position < _scroller.MaxPosition)
            {
                if (Snap)
                {
                    SnapTo();
                }
            }
        }

        private void Awake()
        {
            for (int i = 0; i < templates.Length; i++)
            {
                templates[i].gameObject.SetActive(false);
            }

            ConfigScroller();
            ConfigScrollbar();
        }

        private void OnDestroy()
        {
            viewProvider?.Dispose();
        }

        public void Reset()
        {
            viewProvider?.Reset();
            if (_scroller != null)
            {
                _scroller.Position = 0;
            }

            if (_scrollbar != null)
            {
                _scrollbar.SetValueWithoutNotify(0);
            }
        }

        public void SetAdapter(IAdapter adapter)
        {
            _adapter = adapter;
            ViewProvider.Adapter = _adapter;
            ViewProvider.LayoutManager = _layoutManager;
            _layoutManager.RecyclerView = this;
            _layoutManager.Adapter = _adapter;
            _layoutManager.ViewProvider = viewProvider;
            _layoutManager.Direction = direction;
            _layoutManager.Alignment = alignment;
            _layoutManager.Spacing = spacing;
            _layoutManager.Padding = padding;
            _layoutManager.CanScroll = CanScroll;
        }


        private void ConfigScroller()
        {
            if (_scroller != null)
            {
                _scroller.ScrollSpeed = scrollSpeed;
                _scroller.WheelSpeed = wheelSpeed;
                _scroller.Snap = Snap;
                _scroller.OnValueChanged.AddListener(OnScrollChanged);
                _scroller.OnMoveStoped.AddListener(OnMoveStoped);
                _scroller.OnMoveing.AddListener(OnMoveing);
            }
        }

        private void ConfigScrollbar()
        {
            if (_showScrollBar && _scrollbar != null)
            {
                _scrollbar.gameObject.SetActive(scroll);
                _scrollbar.onValueChanged.AddListener(OnScrollbarChanged);
                _scrollbar.gameObject.AddComponent<ScrollbarEx>().OnDragEnd = OnScrollbarDragEnd;
            }
        }

        public void Refresh()
        {
            ViewProvider.Clear();

            startIndex = _layoutManager.GetStartIndex();
            endIndex = _layoutManager.GetEndIndex();
            for (int i = startIndex; i <= endIndex; i += _layoutManager.Unit)
            {
                ViewProvider.CreateViewHolder(i);
            }

            _layoutManager.DoItemAnimation();
        }

        public void RequestLayout()
        {
            _layoutManager.SetContentSize();

            if (_scroller == null) return;

            _scroller.Direction = direction;
            _scroller.ViewSize = _layoutManager.ViewportSize;
            _scroller.ContentSize = _layoutManager.ContentSize;

            if (Scrollbar != null && _scroller.ContentSize != Vector2.zero)
            {
                if ((direction == Direction.Vertical && _layoutManager.ContentSize.y <= _layoutManager.ViewportSize.y) ||
                    (direction == Direction.Horizontal && _layoutManager.ContentSize.x <= _layoutManager.ViewportSize.x) ||
                    (direction == Direction.Custom))
                {
                    Scrollbar.gameObject.SetActive(false);
                }
                else
                {
                    Scrollbar.gameObject.SetActive(true);
                    Scrollbar.direction = direction == Direction.Vertical ? Scrollbar.Direction.TopToBottom : Scrollbar.Direction.LeftToRight;
                    Scrollbar.size = direction == Direction.Vertical ? _scroller.ViewSize.y / _scroller.ContentSize.y : _scroller.ViewSize.x / _scroller.ContentSize.x;
                }
            }
        }

        public float GetScrollPosition()
        {
            return _scroller ? _scroller.Position : 0;
        }

        public void ScrollTo(int index, bool smooth = false)
        {
            if (!scroll) return;

            _scroller.ScrollTo(_layoutManager.IndexToPosition(index), smooth);
            if (!smooth)
            {
                Refresh();
            }

            index %= _adapter.GetItemCount();
            index = index < 0 ? _adapter.GetItemCount() + index : index;

            if (currentIndex != index)
            {
                currentIndex = index;
                OnIndexChanged?.Invoke(currentIndex);
            }
        }


        private void SnapTo()
        {
            var index = _layoutManager.PositionToIndex(GetScrollPosition());
            ScrollTo(index, true);
        }
    }
}
