using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace Cryville.Common.Unity.UI {
	public delegate void LoadItemHandler(int index, GameObject gameObject);
	[RequireComponent(typeof(RectTransform))]
	public class RecyclerView : UIBehaviour {
		[SerializeField]
		private GameObject m_itemTemplate;
		public GameObject ItemTemplate {
			get { return m_itemTemplate; }
			set { m_itemTemplate = value; /*OnTemplateUpdate();*/ }
		}
		public LoadItemHandler LoadItem { private get; set; }

		public enum Axis {
			Horizontal = 0,
			Vertical = 1,
		}
		[SerializeField]
		private Axis m_direction;
		public Axis Direction {
			get { return m_direction; }
			set { m_direction = value; /*OnFrameUpdate();*/ }
		}

		[SerializeField]
		RectOffset m_padding;
		public RectOffset Padding {
			get => m_padding;
			set => m_padding = value;
		}

		[SerializeField]
		float m_spacing;
		public float Spacing {
			get => m_spacing;
			set => m_spacing = value;
		}

		[SerializeField]
		private int m_itemCount = 3;
		public int ItemCount {
			get { return m_itemCount; }
			set { m_itemCount = value; /*OnRefresh();*/ }
		}

		RectTransform _rectTransform;
		protected override void Awake() {
			_rectTransform = GetComponent<RectTransform>();
		}

		const float _placeholderLength = 100;
		int _firstIndex, _lastIndex;
		readonly Stack<GameObject> _pool = new();
		void Update() {
			int axis = (int)m_direction;
			int sign = m_direction == 0 ? 1 : -1;
			float padding = axis == 0 ? m_padding.left : m_padding.top;
			if (_rectTransform.parent is not RectTransform parentTransform)
				throw new InvalidOperationException("Parent transform is not RectTransform");

			Vector2 apos = _rectTransform.anchoredPosition;
			float pos = apos[axis] + padding;
			float childPos = _firstIndex * _placeholderLength + padding;
			float visibleLength = parentTransform.rect.size[axis];

			//// Add preceding
			//while (_firstIndex > 0 && childPos > pos) {
			//	var child = Rent();
			//	child.transform.SetAsFirstSibling();
			//	LoadItem(--_firstIndex, child);
			//	pos += GetLength(axis, child.transform) - _placeholderLength;
			//	childPos -= _placeholderLength;
			//}

			//// Remove preceding
			//while (_firstIndex < _lastIndex) {
			//	var child = transform.GetChild(0);
			//	float len = GetLength(axis, child.transform);
			//	if (childPos + len > pos) break;
			//	Return(child.gameObject);
			//	_firstIndex++;
			//	pos += _placeholderLength - len;
			//	childPos += _placeholderLength;
			//}

			//apos[axis] = pos;
			//_rectTransform.anchoredPosition = apos;

			// Layout existing
			int index = _firstIndex;
			float layoutToPos = pos + visibleLength;
			for (; index < _lastIndex && childPos < layoutToPos; index++) {
				var child = (RectTransform)transform.GetChild(index - _firstIndex);
				LayoutChild(axis, sign, ref childPos, child);
			}

			// Remove following
			for (; _lastIndex > index; --_lastIndex) {
				var child = (RectTransform)transform.GetChild(index - _firstIndex);
				Return(child.gameObject);
			}

			// Add following (and layout)
			while (_lastIndex < m_itemCount && childPos < layoutToPos) {
				var child = Rent();
				if (child.transform is not RectTransform childTransform)
					throw new InvalidOperationException("Child transform is not RectTransform");
				childTransform.SetSiblingIndex(_lastIndex - _firstIndex);
				LoadItem(_lastIndex++, child);
				LayoutChild(axis, sign, ref childPos, childTransform);
			}

			Vector2 gsize = _rectTransform.sizeDelta;
			gsize[axis] = childPos + (m_itemCount - _lastIndex) * _placeholderLength + (axis == 0 ? m_padding.horizontal : m_padding.vertical);
			_rectTransform.sizeDelta = gsize;
		}
		static float GetLength(int axis, Transform child) {
			if (child is not RectTransform childTransform)
				throw new InvalidOperationException("Child transform is not RectTransform");
			return LayoutUtility.GetPreferredSize(childTransform, axis);
		}
		void LayoutChild(int axis, int sign, ref float childPos, RectTransform childTransform) {
			Vector2 cpos = childTransform.anchoredPosition;
			cpos[axis] = childPos * sign;
			cpos[axis ^ 1] = axis == 1 ? m_padding.left : m_padding.top;
			childTransform.anchoredPosition = cpos;

			Vector2 size = childTransform.sizeDelta;
			size[axis ^ 1] = _rectTransform.rect.size[axis ^ 1] - (axis == 1 ? m_padding.horizontal : m_padding.vertical);
			childTransform.sizeDelta = size;

			childPos += GetLength(axis, childTransform) + m_spacing;
		}

		GameObject Rent() {
			if (_pool.TryPop(out var ret)) {
				ret.SetActive(true);
				return ret;
			}
			return Instantiate(m_itemTemplate, transform, false);
		}
		void Return(GameObject child) {
			child.transform.SetAsLastSibling();
			child.SetActive(false);
			_pool.Push(child);
		}

		public void InvalidateAll() {
			for (; _lastIndex > _firstIndex; --_lastIndex) {
				var child = (RectTransform)transform.GetChild(0);
				Return(child.gameObject);
			}
		}
	}
}
