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

namespace Unity.VisualScripting
{
    public abstract class StateWidget<TState> : NodeWidget<StateCanvas, TState>, IStateWidget
        where TState : class, IState
    {
        protected StateWidget(StateCanvas canvas, TState state) : base(canvas, state)
        {
            minResizeSize = new Vector2(State.DefaultWidth, 0);
        }

        public virtual bool canForceEnter => true;

        public virtual bool canForceExit => true;

        public virtual bool canToggleStart => true;


        #region Model

        protected TState state => element;

        protected IStateDebugData stateDebugData => GetDebugData<IStateDebugData>();

        protected State.Data stateData => reference.hasData ? reference.GetElementData<State.Data>(state) : null;

        IState IStateWidget.state => state;

        protected StateDescription description { get; private set; }

        protected StateAnalysis analysis => state.Analysis<StateAnalysis>(context);

        protected override void CacheDescription()
        {
            description = state.Description<StateDescription>();

            title = description.title;
            summary = description.summary;

            titleContent.text = " " + title;
            titleContent.image = description.icon?[IconSize.Small];
            summaryContent.text = summary;

            Reposition();
        }

        #endregion


        #region Lifecycle

        public override void BeforeFrame()
        {
            base.BeforeFrame();

            if (currentContentOuterHeight != targetContentOuterHeight)
            {
                Reposition();
            }
        }

        public override void HandleInput()
        {
            if (e.IsMouseDrag(MouseButton.Left) &&
                e.ctrlOrCmd &&
                !canvas.isCreatingTransition)
            {
                if (state.canBeSource)
                {
                    canvas.StartTransition(state);
                }
                else
                {
                    Debug.LogWarning("Cannot create a transition from this state.\n");
                }

                e.Use();
            }
            else if (e.IsMouseDrag(MouseButton.Left) && canvas.isCreatingTransition)
            {
                e.Use();
            }
            else if (e.IsMouseUp(MouseButton.Left) && canvas.isCreatingTransition)
            {
                var source = canvas.transitionSource;
                var destination = (canvas.hoveredWidget as IStateWidget)?.state;

                if (destination == null)
                {
                    canvas.CompleteTransitionToNewState();
                }
                else if (destination == source)
                {
                    canvas.CancelTransition();
                }
                else if (destination.canBeDestination)
                {
                    canvas.EndTransition(destination);
                }
                else
                {
                    Debug.LogWarning("Cannot create a transition to this state.\n");
                    canvas.CancelTransition();
                }

                e.Use();
            }

            base.HandleInput();
        }

        #endregion


        #region Contents

        protected virtual string title { get; set; }

        protected virtual string summary { get; set; }

        private GUIContent titleContent { get; } = new GUIContent();

        private GUIContent summaryContent { get; } = new GUIContent();

        #endregion


        #region Positioning

        public override IEnumerable<IWidget> positionDependers => state.transitions.Select(transition => (IWidget)canvas.Widget(transition));

        public Rect titlePosition { get; private set; }

        public Rect summaryPosition { get; private set; }

        public Rect contentOuterPosition { get; private set; }

        public Rect contentBackgroundPosition { get; private set; }

        public Rect contentInnerPosition { get; private set; }

        private float targetContentOuterHeight;

        private float currentContentOuterHeight;

        private bool revealInitialized;

        private Rect _position;

        public override Rect position
        {
            get { return _position; }
            set
            {
                state.position = value.position;
                state.width = value.width;
            }
        }

        public override void CachePosition()
        {
            var edgeOrigin = state.position;
            var edgeX = edgeOrigin.x;
            var edgeY = edgeOrigin.y;
            var edgeWidth = state.width;
            var innerOrigin = EdgeToInnerPosition(new Rect(edgeOrigin, Vector2.zero)).position;
            var innerX = innerOrigin.x;
            var innerY = innerOrigin.y;
            var innerWidth = EdgeToInnerPosition(new Rect(0, 0, edgeWidth, 0)).width;
            var innerHeight = 0f;

            var y = innerY;

            if (showTitle)
            {
                using (LudiqGUIUtility.iconSize.Override(IconSize.Small))
                {
                    titlePosition = new Rect
                        (
                        innerX,
                        y,
                        innerWidth,
                        Styles.title.CalcHeight(titleContent, innerWidth)
                        );

                    y += titlePosition.height;
                    innerHeight += titlePosition.height;
                }
            }

            if (showTitle && showSummary)
            {
                y += Styles.spaceBetweenTitleAndSummary;
                innerHeight += Styles.spaceBetweenTitleAndSummary;
            }

            if (showSummary)
            {
                summaryPosition = new Rect
                    (
                    innerX,
                    y,
                    innerWidth,
                    Styles.summary.CalcHeight(summaryContent, innerWidth)
                    );

                y += summaryPosition.height;
                innerHeight += summaryPosition.height;
            }

            if (showContent)
            {
                var contentInnerWidth = edgeWidth - Styles.contentBackground.padding.left - Styles.contentBackground.padding.right;

                targetContentOuterHeight = revealContent ? (Styles.spaceBeforeContent + Styles.contentBackground.padding.top + GetContentHeight(contentInnerWidth) + Styles.contentBackground.padding.bottom) : 0;

                if (!revealInitialized)
                {
                    currentContentOuterHeight = targetContentOuterHeight;
                    revealInitialized = true;
                }

                currentContentOuterHeight = Mathf.Lerp(currentContentOuterHeight, targetContentOuterHeight, canvas.repaintDeltaTime * Styles.contentRevealSpeed);

                if (Mathf.Abs(targetContentOuterHeight - currentContentOuterHeight) < 1)
                {
                    currentContentOuterHeight = targetContentOuterHeight;
                }

                contentOuterPosition = new Rect
                    (
                    edgeX,
                    y,
                    edgeWidth,
                    currentContentOuterHeight
                    );

                contentBackgroundPosition = new Rect
                    (
                    0,
                    Styles.spaceBeforeContent,
                    edgeWidth,
                    currentContentOuterHeight - Styles.spaceBeforeContent
                    );

                contentInnerPosition = new Rect
                    (
                    Styles.contentBackground.padding.left,
                    Styles.spaceBeforeContent + Styles.contentBackground.padding.top,
                    contentInnerWidth,
                    contentBackgroundPosition.height - Styles.contentBackground.padding.top
                    );

                y += contentOuterPosition.height;
                innerHeight += contentOuterPosition.height;
            }

            var edgeHeight = InnerToEdgePosition(new Rect(0, 0, 0, innerHeight)).height;

            _position = new Rect
                (
                edgeX,
                edgeY,
                edgeWidth,
                edgeHeight
                );
        }

        protected virtual float GetContentHeight(float width) => 0;

        #endregion


        #region Drawing

        protected virtual bool showTitle => true;

        protected virtual bool showSummary => !StringUtility.IsNullOrWhiteSpace(summary);

        protected virtual bool showContent => false;

        protected virtual NodeColorMix baseColor => NodeColor.Gray;

        protected override NodeColorMix color
        {
            get
            {
                if (stateDebugData.runtimeException != null)
                {
                    return NodeColor.Red;
                }

                var color = baseColor;

                if (state.isStart)
                {
                    color = NodeColor.Green;
                }

                if (stateData?.isActive ?? false)
                {
                    color = NodeColor.Blue;
                }
                else if (EditorApplication.isPaused)
                {
                    if (EditorTimeBinding.frame == stateDebugData.lastEnterFrame)
                    {
                        color = NodeColor.Blue;
                    }
                }
                else
                {
                    color.blue = Mathf.Lerp(1, 0, (EditorTimeBinding.time - stateDebugData.lastExitTime) / Styles.enterFadeDuration);
                }

                return color;
            }
        }

        protected override NodeShape shape => NodeShape.Square;

        private bool revealContent
        {
            get
            {
                switch (BoltState.Configuration.statesReveal)
                {
                    case StateRevealCondition.Always:

                        return true;
                    case StateRevealCondition.Never:

                        return false;
                    case StateRevealCondition.OnHover:

                        return isMouseOver;
                    case StateRevealCondition.OnHoverWithAlt:

                        return isMouseOver && e.alt;
                    case StateRevealCondition.WhenSelected:

                        return selection.Contains(state);
                    case StateRevealCondition.OnHoverOrSelected:

                        return isMouseOver || selection.Contains(state);
                    case StateRevealCondition.OnHoverWithAltOrSelected:

                        return isMouseOver && e.alt || selection.Contains(state);
                    default:

                        throw new UnexpectedEnumValueException<StateRevealCondition>(BoltState.Configuration.statesReveal);
                }
            }
        }

        private bool revealedContent;

        private void CheckReveal()
        {
            var revealContent = this.revealContent;

            if (revealContent != revealedContent)
            {
                Reposition();
            }

            revealedContent = revealContent;
        }

        protected override bool dim
        {
            get
            {
                var dim = BoltCore.Configuration.dimInactiveNodes && !analysis.isEntered;

                if (isMouseOver || isSelected)
                {
                    dim = false;
                }

                return dim;
            }
        }

        public override void DrawForeground()
        {
            BeginDim();

            base.DrawForeground();

            if (showTitle)
            {
                DrawTitle();
            }

            if (showSummary)
            {
                DrawSummary();
            }

            if (showContent)
            {
                DrawContentWrapped();
            }

            EndDim();

            CheckReveal();
        }

        private void DrawTitle()
        {
            using (LudiqGUIUtility.iconSize.Override(IconSize.Small))
            {
                GUI.Label(titlePosition, titleContent, invertForeground ? Styles.titleInverted : Styles.title);
            }
        }

        private void DrawSummary()
        {
            GUI.Label(summaryPosition, summaryContent, invertForeground ? Styles.summaryInverted : Styles.summary);
        }

        private void DrawContentWrapped()
        {
            GUI.BeginClip(contentOuterPosition);

            DrawContentBackground();

            DrawContent();

            GUI.EndClip();
        }

        protected virtual void DrawContentBackground()
        {
            if (e.IsRepaint)
            {
                Styles.contentBackground.Draw(contentBackgroundPosition, false, false, false, false);
            }
        }

        protected virtual void DrawContent() { }

        #endregion


        #region Selecting

        public override bool canSelect => true;

        #endregion


        #region Dragging

        protected override bool snapToGrid => BoltCore.Configuration.snapToGrid;

        public override bool canDrag => true;

        public override void ExpandDragGroup(HashSet<IGraphElement> dragGroup)
        {
            if (BoltCore.Configuration.carryChildren)
            {
                foreach (var transition in state.outgoingTransitions)
                {
                    if (dragGroup.Contains(transition.destination))
                    {
                        continue;
                    }

                    dragGroup.Add(transition.destination);

                    canvas.Widget(transition.destination).ExpandDragGroup(dragGroup);
                }
            }
        }

        #endregion


        #region Deleting

        public override bool canDelete => true;

        #endregion


        #region Resizing

        public override bool canResizeHorizontal => true;

        #endregion


        #region Clipboard

        public override void ExpandCopyGroup(HashSet<IGraphElement> copyGroup)
        {
            copyGroup.UnionWith(state.transitions.Cast<IGraphElement>());
        }

        #endregion


        #region Actions

        protected override IEnumerable<DropdownOption> contextOptions
        {
            get
            {
                if (Application.isPlaying && reference.hasData)
                {
                    if (canForceEnter)
                    {
                        yield return new DropdownOption((Action)ForceEnter, "Force Enter");
                    }

                    if (canForceExit)
                    {
                        yield return new DropdownOption((Action)ForceExit, "Force Exit");
                    }
                }

                if (canToggleStart)
                {
                    yield return new DropdownOption((Action)ToggleStart, "Toggle Start");
                }

                if (state.canBeSource)
                {
                    yield return new DropdownOption((Action)MakeTransition, "Make Transition");
                }

                if (state.canBeSource && state.canBeDestination)
                {
                    yield return new DropdownOption((Action)MakeSelfTransition, "Make Self Transition");
                }

                foreach (var baseOption in base.contextOptions)
                {
                    yield return baseOption;
                }
            }
        }

        private void ForceEnter()
        {
            using (var flow = Flow.New(reference))
            {
                state.OnEnter(flow, StateEnterReason.Forced);
            }
        }

        private void ForceExit()
        {
            using (var flow = Flow.New(reference))
            {
                state.OnExit(flow, StateExitReason.Forced);
            }
        }

        protected void MakeTransition()
        {
            canvas.StartTransition(state);
        }

        protected void MakeSelfTransition()
        {
            canvas.StartTransition(state);
            canvas.EndTransition(state);
        }

        protected void ToggleStart()
        {
            UndoUtility.RecordEditedObject("Toggle State Start");

            state.isStart = !state.isStart;
        }

        #endregion


        public static class Styles
        {
            static Styles()
            {
                title = new GUIStyle(BoltCore.Styles.nodeLabel);
                title.fontSize = 12;
                title.alignment = TextAnchor.MiddleCenter;
                title.wordWrap = true;

                summary = new GUIStyle(BoltCore.Styles.nodeLabel);
                summary.fontSize = 10;
                summary.alignment = TextAnchor.MiddleCenter;
                summary.wordWrap = true;

                titleInverted = new GUIStyle(title);
                titleInverted.normal.textColor = ColorPalette.unityBackgroundDark;

                summaryInverted = new GUIStyle(summary);
                summaryInverted.normal.textColor = ColorPalette.unityBackgroundDark;

                contentBackground = new GUIStyle("In BigTitle");
                contentBackground.padding = new RectOffset(0, 0, 4, 4);
            }

            public static readonly GUIStyle title;

            public static readonly GUIStyle summary;

            public static readonly GUIStyle titleInverted;

            public static readonly GUIStyle summaryInverted;

            public static readonly GUIStyle contentBackground;

            public static readonly float spaceBeforeContent = 5;

            public static readonly float spaceBetweenTitleAndSummary = 0;

            public static readonly float enterFadeDuration = 0.5f;

            public static readonly float contentRevealSpeed = 15;
        }
    }
}