using System;
using System.Collections.Generic;
using System.Linq;
using eagle.Notifications;
using Net.Eagle0.Eagle.Views;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

namespace eagle {
    using ProvinceId = Int32;

    public class NotificationPanel : PopupPanelController {
        public Button dismissAllButton;
        public TMP_Text dismissOneButtonText;
        public GameObject spacer;
        public RectTransform beastVisualContainer;

        public Action OnAllDismissed;

        private readonly Queue<Notification> _notes = new();
        private readonly Dictionary<string, GameObject> _beastEffectPrefabs = new();
        private string _pendingBeastVisualName;
        private GameObject _activeBeastVisual;
        private Coroutine _spawnBeastVisualCoroutine;
        private bool _isDestroying;
        private const float BeastVisualForwardZ = -25f;
        private const float BeastVisualNotificationScale = 2f;

        // Incremented when DismissAll is clicked; pending AddNote calls check this
        // to skip adding if a dismiss happened since they were enqueued
        private int _dismissGeneration = 0;

        private void OnNoteTextChanged() {
            if (_isDestroying) { return; }
            SetPopupInfos();
        }

        private void TrackNote(Notification note) { note.TextChanged += OnNoteTextChanged; }

        private void ReleaseNote(Notification note) {
            note.TextChanged -= OnNoteTextChanged;
            if (note is IDisposable disposable) { disposable.Dispose(); }
        }

        private void SetPopupInfos() {
            if (_isDestroying) { return; }

            PopupInfos = _notes.Select(note => new PopupInfo {
                                   titleText = note.Title,
                                   mainText = note.Text,
                                   mainTextLlmId = note.LlmId,
                                   affectedProvinceIds = note.ProvinceIds,
                                   displayedHeroes = note.DisplayedHeroes,
                                   beastVisualName = note.BeastVisualName
                               })
                                 .ToList();

            // If there are no images, add a spacer to the panel
            spacer.gameObject.SetActive(!imagesAndNamesContainer.gameObject.activeSelf);
            if (PopupInfos.Count == 1) {
                dismissAllButton.gameObject.SetActive(false);
                dismissOneButtonText.text = "Done";
            } else {
                dismissAllButton.gameObject.SetActive(true);
                dismissOneButtonText.text = "Dismiss";
            }
        }

        void Awake() { HideBeastVisual(); }

        void OnDestroy() {
            _isDestroying = true;
            _dismissGeneration++;
            foreach (var note in _notes) { ReleaseNote(note); }
            _notes.Clear();
            ClearBeastVisual();
            OnAllDismissed = null;
        }

        void OnEnable() {
            if (HasInfo) { UpdateBeastVisual(); }
        }

        protected override void SetUpPanel() {
            base.SetUpPanel();
            UpdateBeastVisual();
        }

        // Compare hero lists by ID since HeroView is a protobuf message with reference equality
        private static bool HeroListsMatch(List<HeroView> a, List<HeroView> b) {
            if (a == null && b == null) return true;
            if (a == null || b == null) return false;
            if (a.Count != b.Count) return false;
            return a.Select(h => h.Id).SequenceEqual(b.Select(h => h.Id));
        }

        public void AddNote(Notification notification) {
            // Capture current generation - if DismissAll is clicked before this executes,
            // we'll skip adding the note
            var capturedGeneration = _dismissGeneration;
            MainQueue.Q.Enqueue(() => {
                if (_isDestroying || this == null) {
                    ReleaseNote(notification);
                    return;
                }

                // Skip if DismissAll was clicked since this was enqueued
                if (capturedGeneration != _dismissGeneration) {
                    ReleaseNote(notification);
                    return;
                }

                var existingNote = _notes.FirstOrDefault(
                        n => n.Title == notification.Title && !n.IsTextDynamic &&
                             !notification.IsTextDynamic &&
                             HeroListsMatch(n.DisplayedHeroes, notification.DisplayedHeroes));

                if (existingNote == null) {
                    TrackNote(notification);
                    _notes.Enqueue(notification);
                } else {
                    existingNote.Append(notification.Text, notification.ProvinceIds);
                    ReleaseNote(notification);
                }
                SetPopupInfos();
            });
        }

        private void UpdateBeastVisual() {
            var beastVisualName = HasInfo && DisplayedIndex < PopupInfos.Count
                                          ? PopupInfos[DisplayedIndex].beastVisualName
                                          : null;
            if (string.IsNullOrEmpty(beastVisualName)) {
                HideBeastVisual();
                return;
            }

            ShowBeastVisual(beastVisualName);
        }

        private void ShowBeastVisual(string beastName) {
            if (beastVisualContainer == null) { return; }

            beastVisualContainer.gameObject.SetActive(true);
            _pendingBeastVisualName = beastName;

            if (!isActiveAndEnabled) { return; }

            QueueSpawnBeastVisual(beastName);
        }

        private void QueueSpawnBeastVisual(string beastName) {
            CancelQueuedBeastVisualSpawn();
            _spawnBeastVisualCoroutine = StartCoroutine(SpawnBeastVisualWhenReady(beastName));
        }

        private System.Collections.IEnumerator SpawnBeastVisualWhenReady(string beastName) {
            yield return null;

            Canvas.ForceUpdateCanvases();

            if (!isActiveAndEnabled || _pendingBeastVisualName != beastName) {
                _spawnBeastVisualCoroutine = null;
                yield break;
            }

            var prefabName = ProvinceBeastsController.GetEffectPrefabName(beastName);
            if (!_beastEffectPrefabs.TryGetValue(prefabName, out var prefab) || prefab == null) {
                var loadFailed = false;
                yield return AddressableLoader.LoadAssetWithRetry<GameObject>(
                        ProvinceBeastsController.GetEffectPrefabAddress(beastName),
                        loadedPrefab => {
                            prefab = loadedPrefab;
                            if (loadedPrefab != null) {
                                _beastEffectPrefabs[prefabName] = loadedPrefab;
                            }
                        },
                        () => { loadFailed = true; });
                if (loadFailed) {
                    _spawnBeastVisualCoroutine = null;
                    HideBeastVisual();
                    yield break;
                }
            }

            if (!isActiveAndEnabled || _pendingBeastVisualName != beastName) {
                _spawnBeastVisualCoroutine = null;
                yield break;
            }

            _spawnBeastVisualCoroutine = null;
            SpawnBeastVisual(beastName, prefab);
        }

        private void SpawnBeastVisual(string beastName, GameObject prefab) {
            ClearBeastVisual();

            if (prefab == null) {
                HideBeastVisual();
                return;
            }

            _activeBeastVisual = Instantiate(prefab, beastVisualContainer);
            _activeBeastVisual.name = $"NotificationBeast_{beastName}";

            ConfigureContainedSingleBeast(_activeBeastVisual);

            var rectTransform = _activeBeastVisual.GetComponent<RectTransform>();
            if (rectTransform != null) {
                rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
                rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
                rectTransform.pivot = new Vector2(0.5f, 0.5f);
                rectTransform.anchoredPosition3D = new Vector3(0f, 0f, BeastVisualForwardZ);
                rectTransform.sizeDelta = ResolveBeastVisualSize(beastVisualContainer);
                rectTransform.localScale = Vector3.one;
            }
        }

        private static Vector2 ResolveBeastVisualSize(RectTransform container) {
            var size = container.rect.size;
            if (size.x >= 1f && size.y >= 1f) { return size; }

            var layoutElement = container.GetComponent<LayoutElement>();
            if (layoutElement == null) { return size; }

            return new Vector2(
                    layoutElement.preferredWidth > 0f ? layoutElement.preferredWidth : size.x,
                    layoutElement.preferredHeight > 0f ? layoutElement.preferredHeight : size.y);
        }

        private static void ConfigureContainedSingleBeast(GameObject effect) {
            var beastsEffect = effect.GetComponent<BeastsEffect>();
            if (beastsEffect != null) {
                beastsEffect.birdCount = 1f;
                beastsEffect.birdSize *= 2.0f * BeastVisualNotificationScale;
                beastsEffect.circleRadius = Mathf.Min(beastsEffect.circleRadius, 16f);
                beastsEffect.radiusVariation = Mathf.Min(beastsEffect.radiusVariation, 5f);
                beastsEffect.verticalOscillation = Mathf.Min(beastsEffect.verticalOscillation, 5f);
            }

            var monsterEffect = effect.GetComponent<MonsterEffect>();
            if (monsterEffect != null) {
                monsterEffect.clipToViewport = false;
                monsterEffect.monsterCount = 1;
                monsterEffect.monsterScale *= 1.65f * BeastVisualNotificationScale;
                monsterEffect.wanderRadius = Mathf.Min(monsterEffect.wanderRadius, 8f);
            }

            var animalEffect = effect.GetComponent<AnimalEffect>();
            if (animalEffect != null) {
                animalEffect.clipToViewport = false;
                animalEffect.animalCount = 1;
                animalEffect.animalScale *= 1.65f * BeastVisualNotificationScale;
                animalEffect.wanderRadius = Mathf.Min(animalEffect.wanderRadius, 8f);
            }

            var dragonEffect = effect.GetComponent<DragonEffect>();
            if (dragonEffect != null) {
                dragonEffect.clipToViewport = false;
                dragonEffect.dragonScale *= 1.35f * BeastVisualNotificationScale;
                dragonEffect.circleRadius = Mathf.Min(dragonEffect.circleRadius, 16f);
                dragonEffect.groundWanderRadius = Mathf.Min(dragonEffect.groundWanderRadius, 8f);
                dragonEffect.flyHeight = Mathf.Min(dragonEffect.flyHeight, 12f);
                dragonEffect.verticalBob = Mathf.Min(dragonEffect.verticalBob, 2f);
            }

            DisableViewportClipping(effect);
        }

        private static void DisableViewportClipping(GameObject effect) {
            foreach (var clipper in effect.GetComponentsInChildren<RendererViewportClipper>()) {
                clipper.enabled = false;
                Destroy(clipper);
            }
            foreach (var renderer in effect.GetComponentsInChildren<Renderer>()) {
                renderer.enabled = true;
            }
        }

        private void HideBeastVisual() {
            ClearBeastVisual();
            if (beastVisualContainer != null) { beastVisualContainer.gameObject.SetActive(false); }
            _pendingBeastVisualName = null;
        }

        private void ClearBeastVisual() {
            CancelQueuedBeastVisualSpawn();
            if (_activeBeastVisual != null) {
                Destroy(_activeBeastVisual);
                _activeBeastVisual = null;
            }
        }

        private void CancelQueuedBeastVisualSpawn() {
            if (_spawnBeastVisualCoroutine == null) { return; }

            StopCoroutine(_spawnBeastVisualCoroutine);
            _spawnBeastVisualCoroutine = null;
        }

        public void DismissButtonClicked() {
            ReleaseNote(_notes.Dequeue());
            SetPopupInfos();
            if (_notes.Count == 0) OnAllDismissed?.Invoke();
        }

        public void DismissAllClicked() {
            // Increment generation immediately so pending AddNote calls will skip
            _dismissGeneration++;
            foreach (var note in _notes) { ReleaseNote(note); }
            _notes.Clear();
            SetPopupInfos();
            OnAllDismissed?.Invoke();
        }
    }
}
