using System;
using System.Collections.Generic;
using Jovian.PopupSystem.UI;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using Object = UnityEngine.Object;

namespace Jovian.PopupSystem {
    /// <summary>
    /// Core implementation of <see cref="IPopupSystem"/>. Created per game state, not as a singleton.
    /// Manages category registration, trigger discovery, popup lifecycle, priority dismissal, and
    /// tick-driven delay timers and animations. Pass a canvasParent to auto-scan triggers on construction.
    /// </summary>
    public sealed class PopupSystem : IPopupSystem {
        readonly PopupSettings settings;
        readonly PopupReference viewPrefab;
        readonly Func<IPopupAnimator> animatorFactory;
        readonly Transform canvasParent;
        readonly Dictionary<PopupCategory, ViewState> categories = new();
        readonly List<PopupTrigger> registeredTriggers = new();

        /// <summary>
        /// Creates a new popup system instance.
        /// </summary>
        /// <param name="settings">Configuration ScriptableObject with delays, priorities, and display settings.</param>
        /// <param name="viewPrefab">The PopupReference prefab to instantiate per category.</param>
        /// <param name="canvasParent">Optional parent Canvas transform. When provided, popup views are parented
        /// here (inheriting CanvasScaler) and all PopupTrigger components are auto-scanned.</param>
        /// <param name="animatorFactory">Optional factory for custom animators. Defaults to FadePopupAnimator.</param>
        public PopupSystem(PopupSettings settings, PopupReference viewPrefab, Transform canvasParent = null, Func<IPopupAnimator> animatorFactory = null) {
            this.settings = settings;
            this.viewPrefab = viewPrefab;
            this.canvasParent = canvasParent;
            this.animatorFactory = animatorFactory ?? (() => new FadePopupAnimator());

            // Auto-scan if a parent was provided
            if(canvasParent != null) {
                ScanTriggers(canvasParent);
            }
        }

        /// <inheritdoc />
        public void RegisterCategory(PopupCategory category, int priority = 0) {
            if(categories.ContainsKey(category)) {
                return;
            }
            var effectivePriority = settings.GetPriority(category);
            if(effectivePriority == 0) {
                effectivePriority = priority;
            }
            categories[category] = new ViewState {
                priority = effectivePriority,
                delay = settings.GetDelay(category),
                animator = animatorFactory()
            };
        }

        /// <inheritdoc />
        public void Show(PopupCategory category, Action<PopupContentBuilder> buildContent,
                         RectTransform anchor = null, AnchorSide? anchorSide = null) {
            if(!categories.TryGetValue(category, out var state)) {
                return;
            }

            DismissLowerPriority(state.priority);

            state.pendingBuild = buildContent;
            state.pendingAnchor = anchor;
            state.pendingAnchorSide = anchorSide ?? settings.defaultAnchorSide;
            state.pendingScreenPos = null;
            state.delayTimer = state.delay;
            state.isPending = true;
        }

        /// <inheritdoc />
        public void ShowAtPosition(PopupCategory category, Action<PopupContentBuilder> buildContent,
                                   Vector2 screenPosition) {
            if(!categories.TryGetValue(category, out var state)) {
                return;
            }

            DismissLowerPriority(state.priority);

            state.pendingBuild = buildContent;
            state.pendingAnchor = null;
            state.pendingScreenPos = screenPosition;
            state.delayTimer = state.delay;
            state.isPending = true;
        }

        /// <inheritdoc />
        public void Hide(PopupCategory category) {
            if(!categories.TryGetValue(category, out var state)) {
                return;
            }

            state.isPending = false;
            if(state.view != null && state.view.IsVisible) {
                state.animator.Hide(state.view.CanvasGroup, settings.fadeDuration, () => {
                    state.view.SetVisible(false);
                });
            }
        }

        /// <inheritdoc />
        public void HideAll() {
            foreach(var kvp in categories) {
                Hide(kvp.Key);
            }
        }

        /// <inheritdoc />
        public void Tick(float deltaTime) {
            foreach(var kvp in categories) {
                kvp.Value.animator.Tick(deltaTime);
            }

            foreach(var state in categories.Select(kvp => kvp.Value).Where(state => state.isPending)) {
                state.delayTimer -= deltaTime;
                if(state.delayTimer > 0f) {
                    continue;
                }

                state.isPending = false;
                ShowImmediate(state);
            }

            foreach(var state in categories.Select(kvp => kvp.Value).Where(state => state.view is { IsVisible: true } && state.isFollowMouse)) {
                state.view.UpdatePosition();
            }
        }

        /// <inheritdoc />
        public void ScanTriggers(Transform parent) {
            var triggers = parent.GetComponentsInChildren<PopupTrigger>(true);
            foreach(var trigger in triggers) {
                if(!registeredTriggers.Contains(trigger)) {
                    BindTrigger(trigger);
                }
            }
        }

        /// <inheritdoc />
        public PopupTriggerView GetTriggerHandler(string gameObjectName) {
            foreach(var trigger in registeredTriggers) {
                if(trigger != null && trigger.gameObject.name == gameObjectName) {
                    return trigger.Handler;
                }
            }
            return null;
        }

        /// <inheritdoc />
        public IReadOnlyList<PopupTriggerView> GetTriggerHandlers(PopupCategory category) {
            var result = new List<PopupTriggerView>();
            foreach(var trigger in registeredTriggers) {
                if(trigger != null && trigger.Category == category && trigger.Handler != null) {
                    result.Add(trigger.Handler);
                }
            }
            return result;
        }

        /// <inheritdoc />
        public void InitializeTriggersInChildren(Transform parent, Action<PopupTrigger, PopupTriggerView> configureTrigger) {
            var triggers = parent.GetComponentsInChildren<PopupTrigger>(true);
            foreach(var trigger in triggers) {
                if(!registeredTriggers.Contains(trigger)) {
                    BindTrigger(trigger);
                }
                configureTrigger(trigger, trigger.Handler);
            }
        }

        private void BindTrigger(PopupTrigger trigger) {
            var handler = new PopupTriggerView(this);
            trigger.Bind(handler);
            registeredTriggers.Add(trigger);
        }

        /// <inheritdoc />
        public void Dispose() {
            foreach(var kvp in categories) {
                if(kvp.Value.view?.Reference != null) {
                    Object.Destroy(kvp.Value.view.Reference.gameObject);
                }
            }
            categories.Clear();
        }

        private void ShowImmediate(ViewState state) {
            EnsureView(state);
            state.view.ClearContent();

            var builder = new PopupContentBuilder(state.view);
            state.pendingBuild?.Invoke(builder);

            // Activate before layout rebuild so Unity has an active hierarchy to calculate
            state.view.CanvasGroup.alpha = 0f;
            state.view.SetVisible(true);

            // Force full layout rebuild so positioning has correct size on first show
            Canvas.ForceUpdateCanvases();
            LayoutRebuilder.ForceRebuildLayoutImmediate(state.view.Content);
            LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)state.view.Transform);

            if(state.pendingScreenPos.HasValue) {
                state.view.SetFixedPosition(state.pendingScreenPos.Value, settings.screenEdgePadding);
                state.isFollowMouse = false;
            }
            else if(state.pendingAnchor) {
                state.view.SetAnchorMode(state.pendingAnchor, state.pendingAnchorSide, settings.screenEdgePadding);
                state.isFollowMouse = false;
            }
            else {
                state.view.SetFollowMouseMode(settings.followMouseOffset, settings.screenEdgePadding);
                state.isFollowMouse = true;
            }

            state.view.UpdatePosition();
            state.animator.Show(state.view.CanvasGroup, settings.fadeDuration, null);
        }

        private void EnsureView(ViewState state) {
            if(state.view != null) {
                return;
            }

            PopupReference popupRef;
            popupRef = !canvasParent ? Object.Instantiate(viewPrefab) : Object.Instantiate(viewPrefab, canvasParent);

            var canvas = popupRef.GetComponent<Canvas>();
            if(canvas) {
                canvas.overrideSorting = true;
                canvas.sortingOrder = settings.sortingOrder;
            }

            state.view = new PopupView(popupRef, settings);
            state.view.SetVisible(false);
            state.view.SetMaxWidth(settings.maxPopupWidth);
        }

        private void DismissLowerPriority(int showingPriority) {
            foreach(var kvp in categories) {
                var state = kvp.Value;
                if(state.priority >= showingPriority || state.view is not { IsVisible: true }) {
                    continue;
                }
                state.isPending = false;
                state.animator.Hide(state.view.CanvasGroup, settings.fadeDuration, () => {
                    state.view.SetVisible(false);
                });
            }
        }

        private sealed class ViewState {
            public PopupView view;
            public IPopupAnimator animator;
            public int priority;
            public float delay;
            public float delayTimer;
            public bool isPending;
            public bool isFollowMouse;
            public Action<PopupContentBuilder> pendingBuild;
            public RectTransform pendingAnchor;
            public AnchorSide pendingAnchorSide;
            public Vector2? pendingScreenPos;
        }
    }
}
