using System;
using System.Collections.Generic;

namespace Unity.VisualScripting
{
    public sealed class GraphNest<TGraph, TMacro> : IGraphNest
        where TGraph : class, IGraph, new()
        where TMacro : Macro<TGraph>
    {
        [DoNotSerialize]
        public IGraphNester nester { get; set; }

        [DoNotSerialize]
        private GraphSource _source = GraphSource.Macro;

        [DoNotSerialize]
        private TMacro _macro;

        [DoNotSerialize]
        private TGraph _embed;

        [Serialize]
        public GraphSource source
        {
            get => _source;
            set
            {
                if (value == source)
                {
                    return;
                }

                BeforeGraphChange();

                _source = value;

                AfterGraphChange();
            }
        }

        [Serialize]
        public TMacro macro
        {
            get => _macro;
            set
            {
                if (value == macro)
                {
                    return;
                }

                BeforeGraphChange();

                _macro = value;

                AfterGraphChange();
            }
        }

        [Serialize]
        public TGraph embed
        {
            get => _embed;
            set
            {
                if (value == embed)
                {
                    return;
                }

                BeforeGraphChange();

                _embed = value;

                AfterGraphChange();
            }
        }

        [DoNotSerialize]
        public TGraph graph
        {
            get
            {
                switch (source)
                {
                    case GraphSource.Embed:
                        return embed;

                    case GraphSource.Macro:
                        return macro?.graph;

                    default:
                        throw new UnexpectedEnumValueException<GraphSource>(source);
                }
            }
        }

        IMacro IGraphNest.macro
        {
            get => macro;
            set => macro = (TMacro)value;
        }

        IGraph IGraphNest.embed
        {
            get => embed;
            set => embed = (TGraph)value;
        }

        IGraph IGraphNest.graph => graph;

        Type IGraphNest.graphType => typeof(TGraph);

        Type IGraphNest.macroType => typeof(TMacro);

        // TODO: Use these in the editor when appropriate to minimize change events
        public void SwitchToEmbed(TGraph embed)
        {
            if (source == GraphSource.Embed && this.embed == embed)
            {
                return;
            }

            BeforeGraphChange();

            _source = GraphSource.Embed;
            _embed = embed;
            _macro = null;

            AfterGraphChange();
        }

        public void SwitchToMacro(TMacro macro)
        {
            if (source == GraphSource.Macro && this.macro == macro)
            {
                return;
            }

            BeforeGraphChange();

            _source = GraphSource.Macro;
            _embed = null;
            _macro = macro;

            AfterGraphChange();
        }

        public event Action beforeGraphChange;

        public event Action afterGraphChange;

        private void BeforeGraphChange()
        {
            if (graph != null)
            {
                nester.UninstantiateNest();
            }

            beforeGraphChange?.Invoke();
        }

        private void AfterGraphChange()
        {
            afterGraphChange?.Invoke();

            if (graph != null)
            {
                nester.InstantiateNest();
            }
        }

        #region Serialization

        public IEnumerable<ISerializationDependency> deserializationDependencies
        {
            get
            {
                if (macro != null)
                {
                    yield return macro;
                }
            }
        }

        #endregion


        #region Poutine

        public IEnumerable<object> GetAotStubs(HashSet<object> visited)
        {
            return LinqUtility.Concat<object>(graph?.GetAotStubs(visited));
        }

        [DoNotSerialize]
        public bool hasBackgroundEmbed => source == GraphSource.Macro && embed != null;

        #endregion
    }
}