using System.Reflection;
using Jint.Native;
using Jint.Runtime.Interop;
using Jint.Runtime.Interop.Reflection;

namespace Jint.Runtime.Descriptors.Specialized;

internal sealed class ReflectionDescriptor : PropertyDescriptor
{
    private readonly Engine _engine;
    private readonly ReflectionAccessor _reflectionAccessor;
    private readonly object _target;
    private readonly string _propertyName;

    private JsValue? _get;
    private JsValue? _set;

    public ReflectionDescriptor(
        Engine engine,
        ReflectionAccessor reflectionAccessor,
        object target,
        string propertyName,
        bool enumerable)
        : base((enumerable ? PropertyFlag.Enumerable : PropertyFlag.None) | PropertyFlag.CustomJsValue)
    {
        _flags |= PropertyFlag.NonData;
        _engine = engine;
        _reflectionAccessor = reflectionAccessor;
        _target = target;
        _propertyName = propertyName;
    }

    public override JsValue? Get
    {
        get
        {
            if (_reflectionAccessor.Readable)
            {
                return _get ??= new GetterFunction(_engine, DoGet);
            }

            return null;
        }
    }

    public override JsValue? Set
    {
        get
        {
            if (_reflectionAccessor.Writable && _engine.Options.Interop.AllowWrite)
            {
                return _set ??= new SetterFunction(_engine, DoSet);
            }

            return null;
        }
    }

    protected internal override JsValue? CustomValue
    {
        get => DoGet(thisObj: null);
        set => DoSet(thisObj: null, value);
    }

    private JsValue DoGet(JsValue? thisObj)
    {
        var value = _reflectionAccessor.GetValue(_engine, _target, _propertyName);
        var type = _reflectionAccessor.MemberType;
        return JsValue.FromObjectWithType(_engine, value, type);
    }

    private void DoSet(JsValue? thisObj, JsValue? v)
    {
        try
        {
            _reflectionAccessor.SetValue(_engine, _target, _propertyName, v!);
        }
        catch (TargetInvocationException exception)
        {
            ExceptionHelper.ThrowMeaningfulException(_engine, exception);
        }
    }
}
