using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;

namespace Unity.VisualScripting
{
    public abstract class Widget<TCanvas, TItem> : IWidget
        where TCanvas : class, ICanvas
        where TItem : class, IGraphItem
    {
        #region Context Shortcuts

        protected IGraphContext context => LudiqGraphsEditorUtility.editedContext.value;

        protected GraphReference reference => context.reference;

        protected IGraph graph => canvas.graph;

        protected TCanvas canvas { get; }

        ICanvas IWidget.canvas => canvas;

        protected GraphSelection selection => canvas.selection;

        protected ICanvasWindow window => canvas.window;

        protected Vector2 mousePosition => canvas.mousePosition;

        #endregion

        protected Widget(TCanvas canvas, TItem item)
        {
            Ensure.That(nameof(canvas)).IsNotNull(canvas);
            Ensure.That(nameof(item)).IsNotNull(item);

            this.canvas = canvas;

            this.item = item;

            e = new EventWrapper(GetType());

            // Micro optimization
            hasDescriptor = item.HasDescriptor();

            if (hasDescriptor)
            {
                DescriptorProvider.instance.AddListener(item, CacheDescription);
            }
        }

        public bool disposed { get; private set; }

        public virtual void Dispose()
        {
            if (hasDescriptor)
            {
                DescriptorProvider.instance.RemoveListener(item, CacheDescription);
            }

            disposed = true;
        }

        protected EventWrapper e { get; }

        public override string ToString()
        {
            return GetType().Name + "\nCID: " + e.control;
        }

        public virtual IEnumerable<IWidget> subWidgets => Enumerable.Empty<IWidget>();

        protected void SubWidgetsChanged()
        {
            canvas.Recollect();
        }

        #region Model

        protected TItem item { get; }

        IGraphItem IWidget.item => item;

        protected readonly bool hasDescriptor;

        public Metadata metadata { get; private set; }

        private Metadata lastFetchedMetadata;

        private bool cachedItemOnce = false;

        public virtual Metadata FetchMetadata()
        {
            return null;
        }

        protected virtual void CacheItemFirstTime()
        {
            CacheDescription();
        }

        public virtual void CacheItem()
        {
            if (!cachedItemOnce)
            {
                CacheItemFirstTime();
                cachedItemOnce = true;
            }

            // Note: on UnitPortWidget, FetchMetadata depends on the description, so it's important to validate it first.

            if (hasDescriptor)
            {
                item.Descriptor().Validate();
            }

            metadata = FetchMetadata();

            if (metadata != lastFetchedMetadata)
            {
                lastFetchedMetadata = metadata;
                CacheMetadata();
            }
        }

        protected virtual void CacheMetadata() { }

        protected virtual void CacheDescription() { }

        #endregion


        #region Lifecycle

        public void RegisterControl()
        {
            e.RegisterControl(FocusType.Passive);
        }

        public virtual void BeforeFrame()
        {
            if (BoltCore.Configuration.developerMode && BoltCore.Configuration.debug)
            {
                debug = $"CID: {e.control}";
            }

            if (isVisible || !dimInitialized)
            {
                UpdateDim();
            }
        }

        public virtual void HandleCapture()
        {
            e.HandleCapture(isMouseOver, false);
        }

        public virtual void HandleInput()
        {
            HandleContext();
        }

        public virtual void HandleRelease()
        {
            e.HandleRelease();
        }

        public virtual void Update() { }

        #endregion


        #region Positioning

        protected virtual bool snapToGrid => false;

        public bool isPositionValid { get; set; }

        public virtual IEnumerable<IWidget> positionDependers => Enumerable.Empty<IWidget>();

        public virtual IEnumerable<IWidget> positionDependencies => Enumerable.Empty<IWidget>();

        public abstract Rect position { get; set; }

        public abstract float zIndex { get; set; }

        public void Reposition()
        {
            isPositionValid = false;

            foreach (var positionDepender in positionDependers)
            {
                positionDepender.Reposition();
            }
        }

        public virtual void CachePositionFirstPass() { }

        public virtual void CachePosition() { }

        public void BringToFront()
        {
            var frontmostWidget = canvas.widgets.OrderByDescending(widget => widget.zIndex).FirstOrDefault();

            if (frontmostWidget != null)
            {
                zIndex = frontmostWidget.zIndex + 1;
            }
        }

        public void SendToBack()
        {
            var backmostWidget = canvas.widgets.OrderBy(widget => widget.zIndex).FirstOrDefault();

            if (backmostWidget != null)
            {
                zIndex = backmostWidget.zIndex - 1;
            }
        }

        #endregion


        #region Viewing

        public virtual bool canClip => !e.controlsMouse;

        public virtual Rect clippingPosition => position;

        public virtual void OnViewportChange() { }

        public bool isVisible { get; set; }

        #endregion


        #region Hovering

        public virtual Rect hotArea => position;

        public bool isMouseThrough => hotArea.Contains(mousePosition);

        public bool isMouseOver => canvas.hoveredWidget == this;

        #endregion


        #region Context

        protected virtual void OnContext()
        {
            var contextOptions = this.contextOptions.ToArray();

            canvas.delayCall += () =>
            {
                LudiqGUI.Dropdown
                    (
                        e.mousePosition,
                        action => canvas.delayCall += (Action)action,
                        contextOptions,
                        null
                    );
            };
        }

        protected void HandleContext()
        {
            if (e.IsContextClick)
            {
                OnContext();
                e.Use();
            }
        }

        protected virtual IEnumerable<DropdownOption> contextOptions
        {
            get
            {
                yield break;
            }
        }

        #endregion


        #region Drawing

        protected string debug { get; set; }

        public virtual bool foregroundRequiresInput => false;

        public virtual bool backgroundRequiresInput => false;

        public virtual bool overlayRequiresInput => false;

        public virtual void DrawForeground()
        {
            if (BoltCore.Configuration.developerMode && BoltCore.Configuration.debug && !string.IsNullOrEmpty(debug))
            {
                var debugContent = new GUIContent(debug);

                var debugLabelPosition = new Rect
                    (
                    position.x,
                    position.yMax + 5,
                    EditorStyles.whiteMiniLabel.CalcSize(debugContent).x,
                    EditorStyles.whiteMiniLabel.CalcSize(debugContent).y
                    );

                GUI.Label(debugLabelPosition, debugContent, EditorStyles.whiteMiniLabel);
            }
        }

        public virtual void DrawBackground()
        {
            if (BoltCore.Configuration.developerMode && BoltCore.Configuration.debug)
            {
                EditorGUI.DrawRect(clippingPosition, new Color(0, 0, 0, 0.1f));
            }
        }

        public virtual void DrawOverlay()
        {
            if (BoltCore.Configuration.developerMode && BoltCore.Configuration.debug)
            {
                if (e.controlsMouse && e.controlsKeyboard)
                {
                    EditorGUI.DrawRect(position, Color.cyan.WithAlpha(0.25f));
                }
                else if (e.controlsMouse)
                {
                    EditorGUI.DrawRect(position, Color.green.WithAlpha(0.25f));
                }
                else if (e.controlsKeyboard)
                {
                    EditorGUI.DrawRect(position, Color.blue.WithAlpha(0.25f));
                }
                else if (isMouseOver)
                {
                    EditorGUI.DrawRect(position, Color.yellow.WithAlpha(0.25f));
                }
            }
        }

        #endregion


        #region Dimming

        private float dimAlpha = 1;

        private float dimTarget = 1;

        private bool dimInitialized = false;

        private const float dimFadeDuration = 0.075f;

        protected virtual bool dim => false;

        protected void BeginDim()
        {
            LudiqGUI.color.BeginOverride(LudiqGUI.color.value.WithAlphaMultiplied(dimAlpha));
        }

        protected void EndDim()
        {
            LudiqGUI.color.EndOverride();
        }

        protected void UpdateDim()
        {
            dimTarget = dim ? GraphGUI.Styles.dimAlpha : 1;

            if (!dimInitialized)
            {
                dimAlpha = dimTarget;
                dimInitialized = true;
            }

            dimAlpha = Mathf.MoveTowards(dimAlpha, dimTarget, (1 / dimFadeDuration) * canvas.repaintDeltaTime);
        }

        #endregion
    }
}