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

namespace Jovian.InGameLogging.UI {
    public class GameLogView : MonoBehaviour {
        [SerializeField] ScrollRect scrollRect;
        [SerializeField] RectTransform content;
        [SerializeField] LogEntryView entryPrefab;
        [SerializeField] int poolSize = 20;

        IGameLogStore store;
        LogChannel? channelFilter;
        bool autoScroll = true;
        bool scrollingToBottom;
        Coroutine scrollCoroutine;

        readonly List<LogEntryView> activeEntries = new();
        readonly Stack<LogEntryView> pool = new();

        public void Initialize(IGameLogStore store, LogChannel? channelFilter = null) {
            this.store = store;
            this.channelFilter = channelFilter;

            WarmPool();
            store.OnEntryAdded += HandleEntryAdded;
            store.OnCleared += HandleCleared;
            scrollRect.onValueChanged.AddListener(HandleScrollChanged);

            RebuildFromStore();
        }

        void OnDestroy() {
            if(store != null) {
                store.OnEntryAdded -= HandleEntryAdded;
                store.OnCleared -= HandleCleared;
            }
            if(scrollRect != null) {
                scrollRect.onValueChanged.RemoveListener(HandleScrollChanged);
            }
        }

        void WarmPool() {
            for(int i = 0; i < poolSize; i++) {
                var entry = Instantiate(entryPrefab, content);
                entry.gameObject.SetActive(false);
                pool.Push(entry);
            }
        }

        LogEntryView GetFromPool() {
            LogEntryView entry;
            if(pool.Count > 0) {
                entry = pool.Pop();
            }
            else {
                entry = activeEntries[0];
                activeEntries.RemoveAt(0);
            }
            entry.gameObject.SetActive(true);
            entry.transform.SetAsLastSibling();
            return entry;
        }

        void ReturnToPool(LogEntryView entry) {
            entry.ClearEntry();
            entry.gameObject.SetActive(false);
            pool.Push(entry);
        }

        void HandleEntryAdded(LogEntry entry) {
            if(channelFilter.HasValue && entry.channel != channelFilter.Value) {
                return;
            }

            var view = GetFromPool();
            view.SetEntry(in entry);
            activeEntries.Add(view);

            if(autoScroll) {
                RequestScrollToBottom();
            }
        }

        void RequestScrollToBottom() {
            if(scrollCoroutine != null) {
                StopCoroutine(scrollCoroutine);
            }
            scrollCoroutine = StartCoroutine(ScrollToBottomRoutine());
        }

        IEnumerator ScrollToBottomRoutine() {
            scrollingToBottom = true;
            yield return null;
            LayoutRebuilder.ForceRebuildLayoutImmediate(content);
            scrollRect.verticalNormalizedPosition = 0f;
            yield return null;
            scrollingToBottom = false;
            scrollCoroutine = null;
        }

        void HandleCleared() {
            for(int i = activeEntries.Count - 1; i >= 0; i--) {
                ReturnToPool(activeEntries[i]);
            }
            activeEntries.Clear();

            if(store.Count > 0) {
                RebuildFromStore();
            }
        }

        void RebuildFromStore() {
            var entries = store.GetEntries();
            for(int i = 0; i < entries.Length; i++) {
                if(channelFilter.HasValue && entries[i].channel != channelFilter.Value) {
                    continue;
                }
                var view = GetFromPool();
                view.SetEntry(in entries[i]);
                activeEntries.Add(view);
            }

            if(autoScroll) {
                RequestScrollToBottom();
            }
        }

        void HandleScrollChanged(Vector2 position) {
            if(scrollingToBottom) {
                return;
            }
            autoScroll = position.y <= 0.01f;
        }
    }
}
