using System.Diagnostics.CodeAnalysis;
using Jint.Native;
using Jint.Native.Function;
using Jint.Native.Object;

namespace Jint.Runtime.Environments
{
    internal static class JintEnvironment
    {
        internal static bool TryGetIdentifierEnvironmentWithBinding(
            Environment env,
            Environment.BindingName name,
            [NotNullWhen(true)] out Environment? record)
        {
            record = env;

            if (env._outerEnv is null)
            {
                return env.HasBinding(name);
            }

            while (!ReferenceEquals(record, null))
            {
                if (record.HasBinding(name))
                {
                    return true;
                }

                record = record._outerEnv;
            }

            return false;
        }

        internal static bool TryGetIdentifierEnvironmentWithBindingValue(
            Environment env,
            Environment.BindingName name,
            [NotNullWhen(true)] out Environment? record,
            [NotNullWhen(true)] out JsValue? value)
        {
            record = env;
            value = default;

            if (env._outerEnv is null)
            {
                return ((GlobalEnvironment) env).TryGetBinding(name, out value);
            }

            while (!ReferenceEquals(record, null))
            {
                if (record.TryGetBinding(name, out value))
                {
                    return true;
                }

                record = record._outerEnv;
            }

            return false;
        }

        /// <summary>
        /// https://tc39.es/ecma262/#sec-newdeclarativeenvironment
        /// </summary>
        internal static DeclarativeEnvironment NewDeclarativeEnvironment(Engine engine, Environment? outer, bool catchEnvironment = false)
        {
            return new DeclarativeEnvironment(engine, catchEnvironment)
            {
                _outerEnv = outer
            };
        }

        /// <summary>
        /// https://tc39.es/ecma262/#sec-newfunctionenvironment
        /// </summary>
        internal static FunctionEnvironment NewFunctionEnvironment(Engine engine, Function f, JsValue newTarget)
        {
            return new FunctionEnvironment(engine, f, newTarget)
            {
                _outerEnv = f._environment
            };
        }

        /// <summary>
        /// https://tc39.es/ecma262/#sec-newglobalenvironment
        /// </summary>
        internal static GlobalEnvironment NewGlobalEnvironment(Engine engine, ObjectInstance objectInstance, JsValue thisValue)
        {
            return new GlobalEnvironment(engine, objectInstance)
            {
                _outerEnv = null
            };
        }

        /// <summary>
        /// https://tc39.es/ecma262/#sec-newobjectenvironment
        /// </summary>
        internal static ObjectEnvironment NewObjectEnvironment(Engine engine, ObjectInstance objectInstance, Environment outer, bool provideThis, bool withEnvironment = false)
        {
            return new ObjectEnvironment(engine, objectInstance, provideThis, withEnvironment)
            {
                _outerEnv = outer
            };
        }

        /// <summary>
        /// https://tc39.es/ecma262/#sec-newprivateenvironment
        /// </summary>
        internal static PrivateEnvironment NewPrivateEnvironment(Engine engine, PrivateEnvironment? outerPriv)
        {
            return new PrivateEnvironment(outerPriv);
        }

        /// <summary>
        /// https://tc39.es/ecma262/#sec-newmoduleenvironment
        /// </summary>
        internal static ModuleEnvironment NewModuleEnvironment(Engine engine, Environment outer)
        {
            return new ModuleEnvironment(engine)
            {
                _outerEnv = outer
            };
        }
    }
}
