using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reflection.Emit;
using Avalonia.Markup.Parsers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
using XamlX;
using XamlX.Emit;
using XamlX.IL;
using Avalonia.Utilities;

using XamlIlEmitContext = XamlX.Emit.XamlEmitContextWithLocals<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>;

namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
    static class XamlIlBindingPathHelper
    {
        public static IXamlType UpdateCompiledBindingExtension(AstTransformationContext context, XamlAstConstructableObjectNode binding, Func<IXamlType> startTypeResolver, IXamlType selfType)
        {
            IXamlType bindingResultType = null;
            if (binding.Arguments.Count > 0 && binding.Arguments[0] is ParsedBindingPathNode bindingPath)
            {
                var transformed = TransformBindingPath(
                    context,
                    bindingPath,
                    startTypeResolver,
                    selfType,
                    bindingPath.Path);

                transformed = TransformForTargetTyping(transformed, context);

                bindingResultType = transformed.BindingResultType;
                binding.Arguments[0] = transformed;
            }
            else
            {
                var bindingPathAssignment = binding.Children.OfType<XamlPropertyAssignmentNode>()
                    .FirstOrDefault(v => v.Property.Name == "Path");

                if (bindingPathAssignment is null)
                {
                    return startTypeResolver();
                }

                if (bindingPathAssignment.Values[0] is ParsedBindingPathNode bindingPathNode)
                {
                    var transformed = TransformBindingPath(
                        context,
                        bindingPathNode,
                        startTypeResolver,
                        selfType,
                        bindingPathNode.Path);

                    transformed = TransformForTargetTyping(transformed, context);

                    bindingResultType = transformed.BindingResultType;
                    bindingPathAssignment.Values[0] = transformed;
                }
                else
                {
                    throw new InvalidOperationException();
                }
            }

            return bindingResultType;
        }

        private static XamlIlBindingPathNode TransformForTargetTyping(XamlIlBindingPathNode transformed, AstTransformationContext context)
        {
            var parentNode = context.ParentNodes().OfType<XamlPropertyAssignmentNode>().FirstOrDefault();

            if (parentNode == null)
            {
                return transformed;
            }

            var lastElement =
                transformed.Elements[transformed.Elements.Count - 1];
            
            if (parentNode.Property?.Getter?.ReturnType == context.GetAvaloniaTypes().ICommand && lastElement is XamlIlClrMethodPathElementNode methodPathElement)
            {
                IXamlMethod executeMethod = methodPathElement.Method;
                IXamlMethod canExecuteMethod = executeMethod.DeclaringType.FindMethod(new FindMethodMethodSignature($"Can{executeMethod.Name}", context.Configuration.WellKnownTypes.Boolean, context.Configuration.WellKnownTypes.Object));
                List<string> dependsOnProperties = new();
                if (canExecuteMethod is not null)
                {
                    foreach (var attr in canExecuteMethod.CustomAttributes)
                    {
                        if (attr.Type == context.GetAvaloniaTypes().DependsOnAttribute)
                        {
                            dependsOnProperties.Add((string)attr.Parameters[0]);
                        }
                    }
                }
                transformed.Elements.RemoveAt(transformed.Elements.Count - 1);
                transformed.Elements.Add(new XamlIlClrMethodAsCommandPathElementNode(context.GetAvaloniaTypes().ICommand, executeMethod, canExecuteMethod, dependsOnProperties));
            }

            return transformed;
        }

        private static XamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, Func<IXamlType> startTypeResolver, IXamlType selfType, IEnumerable<BindingExpressionGrammar.INode> bindingExpression)
        {
            List<IXamlIlBindingPathElementNode> transformNodes = new List<IXamlIlBindingPathElementNode>();
            List<IXamlIlBindingPathElementNode> nodes = new List<IXamlIlBindingPathElementNode>();
            foreach (var astNode in bindingExpression)
            {
                var targetTypeResolver = nodes.Count == 0 ? startTypeResolver : () => nodes[nodes.Count - 1].Type;
                switch (astNode)
                {
                    case BindingExpressionGrammar.EmptyExpressionNode _:
                        break;
                    case BindingExpressionGrammar.NotNode _:
                        transformNodes.Add(new XamlIlNotPathElementNode(context.Configuration.WellKnownTypes.Boolean));
                        break;
                    case BindingExpressionGrammar.StreamNode _:
                        {
                            IXamlType targetType = targetTypeResolver();
                            IXamlType observableType;
                            if (targetType.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) == true)
                            {
                                observableType = targetType;
                            }
                            else
                            {
                                observableType = targetType.GetAllInterfaces().FirstOrDefault(i => i.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) ?? false);
                            }

                            if (observableType != null)
                            {
                                nodes.Add(new XamlIlStreamObservablePathElementNode(observableType.GenericArguments[0]));
                                break;
                            }
                            bool foundTask = false;
                            for (var currentType = targetType; currentType != null; currentType = currentType.BaseType)
                            {
                                if (currentType.GenericTypeDefinition.Equals(context.Configuration.TypeSystem.GetType("System.Threading.Tasks.Task`1")))
                                {
                                    foundTask = true;
                                    nodes.Add(new XamlIlStreamTaskPathElementNode(currentType.GenericArguments[0]));
                                    break;
                                }
                            }
                            if (foundTask)
                            {
                                break;
                            }
                            throw new XamlX.XamlParseException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
                        }
                    case BindingExpressionGrammar.PropertyNameNode propName:
                        {
                            IXamlType targetType = targetTypeResolver();
                            var avaloniaPropertyFieldNameMaybe = propName.PropertyName + "Property";
                            var avaloniaPropertyFieldMaybe = targetType.GetAllFields().FirstOrDefault(f =>
                                f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldNameMaybe);

                            if (avaloniaPropertyFieldMaybe != null)
                            {
                                nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyFieldMaybe,
                                    XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyFieldMaybe, context.GetAvaloniaTypes(), lineInfo)));
                            }
                            else if (GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName) is IXamlProperty clrProperty)
                            {
                                nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty));
                            }
                            else if (GetAllDefinedMethods(targetType).FirstOrDefault(m => m.Name == propName.PropertyName) is IXamlMethod method)
                            {
                                nodes.Add(new XamlIlClrMethodPathElementNode(method, context.Configuration.WellKnownTypes.Delegate));
                            }
                            else
                            {
                                throw new XamlX.XamlParseException($"Unable to resolve property or method of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
                            }
                            break;
                        }
                    case BindingExpressionGrammar.IndexerNode indexer:
                        {
                            IXamlType targetType = targetTypeResolver();
                            if (targetType.IsArray)
                            {
                                nodes.Add(new XamlIlArrayIndexerPathElementNode(targetType, indexer.Arguments, lineInfo));
                                break;
                            }

                            IXamlProperty property = null;
                            foreach (var currentType in TraverseTypeHierarchy(targetType))
                            {
                                var defaultMemberAttribute = currentType.CustomAttributes.FirstOrDefault(x => x.Type.Namespace == "System.Reflection" && x.Type.Name == "DefaultMemberAttribute");
                                if (defaultMemberAttribute != null)
                                {
                                    property = currentType.GetAllProperties().FirstOrDefault(x => x.Name == (string)defaultMemberAttribute.Parameters[0]);
                                    break;
                                }
                            }
                            if (property is null)
                            {
                                throw new XamlX.XamlParseException($"The type '${targetType}' does not have an indexer.", lineInfo);
                            }

                            IEnumerable<IXamlType> parameters = property.IndexerParameters;

                            List<IXamlAstValueNode> values = new List<IXamlAstValueNode>();
                            int currentParamIndex = 0;
                            foreach (var param in parameters)
                            {
                                var textNode = new XamlAstTextNode(lineInfo, indexer.Arguments[currentParamIndex], type: context.Configuration.WellKnownTypes.String);
                                if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, textNode,
                                        param, out var converted))
                                    throw new XamlX.XamlParseException(
                                        $"Unable to convert indexer parameter value of '{indexer.Arguments[currentParamIndex]}' to {param.GetFqn()}",
                                        textNode);

                                values.Add(converted);
                                currentParamIndex++;
                            }

                            bool isNotifyingCollection = targetType.GetAllInterfaces().Any(i => i.FullName == "System.Collections.Specialized.INotifyCollectionChanged");

                            nodes.Add(new XamlIlClrIndexerPathElementNode(property, values, string.Join(",", indexer.Arguments), isNotifyingCollection));
                            break;
                        }
                    case BindingExpressionGrammar.AttachedPropertyNameNode attachedProp:
                        var avaloniaPropertyFieldName = attachedProp.PropertyName + "Property";
                        var avaloniaPropertyField = GetType(attachedProp.Namespace, attachedProp.TypeName).GetAllFields().FirstOrDefault(f =>
                            f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldName);
                        nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyField,
                            XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyField, context.GetAvaloniaTypes(), lineInfo)));
                        break;
                    case BindingExpressionGrammar.SelfNode _:
                        nodes.Add(new SelfPathElementNode(selfType));
                        break;
                    case VisualAncestorBindingExpressionNode visualAncestor:
                        nodes.Add(new FindVisualAncestorPathElementNode(visualAncestor.Type, visualAncestor.Level));
                        break;
                    case TemplatedParentBindingExpressionNode templatedParent:
                        var templatedParentField = context.GetAvaloniaTypes().StyledElement.GetAllFields()
                            .FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == "TemplatedParentProperty");
                        nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(
                            templatedParentField,
                            templatedParent.Type));
                        break;
                    case BindingExpressionGrammar.AncestorNode ancestor:
                        if (ancestor.Namespace is null && ancestor.TypeName is null)
                        {
                            var styledElementType = context.GetAvaloniaTypes().StyledElement;
                            var ancestorType = context
                                .ParentNodes()
                                .OfType<XamlAstConstructableObjectNode>()
                                .Where(x => styledElementType.IsAssignableFrom(x.Type.GetClrType()))
                                .Skip(1)
                                .ElementAtOrDefault(ancestor.Level)
                                ?.Type.GetClrType();

                            if (ancestorType is null)
                            {
                                throw new XamlX.XamlParseException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo);
                            }

                            nodes.Add(new FindAncestorPathElementNode(ancestorType, ancestor.Level));
                        }
                        else
                        {
                            nodes.Add(new FindAncestorPathElementNode(GetType(ancestor.Namespace, ancestor.TypeName), ancestor.Level));
                        }
                        break;
                    case BindingExpressionGrammar.NameNode elementName:
                        IXamlType elementType = null;
                        foreach (var deferredContent in context.ParentNodes().OfType<NestedScopeMetadataNode>())
                        {
                            elementType = ScopeRegistrationFinder.GetTargetType(deferredContent, elementName.Name);
                            if (!(elementType is null))
                            {
                                break;
                            }
                        }
                        if (elementType is null)
                        {
                            elementType = ScopeRegistrationFinder.GetTargetType(context.ParentNodes().Last(), elementName.Name);
                        }

                        if (elementType is null)
                        {
                            throw new XamlX.XamlParseException($"Unable to find element '{elementName.Name}' in the current namescope. Unable to use a compiled binding with a name binding if the name cannot be found at compile time.", lineInfo);
                        }
                        nodes.Add(new ElementNamePathElementNode(elementName.Name, elementType));
                        break;
                    case RawSourceBindingExpressionNode rawSource:
                        nodes.Add(new RawSourcePathElementNode(rawSource.RawSource));
                        break;
                    case BindingExpressionGrammar.TypeCastNode typeCastNode:
                        var castType = GetType(typeCastNode.Namespace, typeCastNode.TypeName);

                        if (castType is null)
                        {
                            throw new XamlX.XamlParseException($"Unable to resolve cast to type {typeCastNode.Namespace}:{typeCastNode.TypeName} based on XAML tree.", lineInfo);
                        }

                        nodes.Add(new TypeCastPathElementNode(castType));
                        break;
                }
            }

            return new XamlIlBindingPathNode(lineInfo, context.GetAvaloniaTypes().CompiledBindingPath, transformNodes, nodes);

            IXamlType GetType(string ns, string name)
            {
                return TypeReferenceResolver.ResolveType(context, $"{ns}:{name}", false,
                    lineInfo, true).GetClrType();
            }

            static IEnumerable<IXamlProperty> GetAllDefinedProperties(IXamlType type)
            {
                foreach (var t in TraverseTypeHierarchy(type))
                {
                    foreach (var p in t.Properties)
                    {
                        yield return p;
                    }
                }
            }

            static IEnumerable<IXamlMethod> GetAllDefinedMethods(IXamlType type)
            {
                foreach (var t in TraverseTypeHierarchy(type))
                {
                    foreach (var m in t.Methods)
                    {
                        yield return m;
                    }
                }
            }

            static IEnumerable<IXamlType> TraverseTypeHierarchy(IXamlType type)
            {
                if (type.IsInterface)
                {
                    yield return type;
                    foreach (var iface in type.Interfaces)
                    {
                        foreach (var h in TraverseTypeHierarchy(iface))
                        {
                            yield return h;
                        }
                    }
                }
                else
                {
                    for (var currentType = type; currentType != null; currentType = currentType.BaseType)
                    {
                        yield return currentType;
                    }
                }
            }
        }

        class ScopeRegistrationFinder : IXamlAstVisitor
        {
            private Stack<IXamlAstNode> _stack = new Stack<IXamlAstNode>();
            private Stack<IXamlAstNode> _childScopesStack = new Stack<IXamlAstNode>();

            private ScopeRegistrationFinder(string name)
            {
                Name = name;
            }

            string Name { get; }

            IXamlType TargetType { get; set; }

            public static IXamlType GetTargetType(IXamlAstNode namescopeRoot, string name)
            {
                var finder = new ScopeRegistrationFinder(name);
                namescopeRoot.Visit(finder);
                return finder.TargetType;
            }

            void IXamlAstVisitor.Pop()
            {
                var node = _stack.Pop();
                if (_childScopesStack.Count > 0 && node == _childScopesStack.Peek())
                {
                    _childScopesStack.Pop();
                }
            }

            void IXamlAstVisitor.Push(IXamlAstNode node)
            {
                _stack.Push(node);
                if (node is NestedScopeMetadataNode)
                {
                    _childScopesStack.Push(node);
                }
            }

            IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
            {
                if (_childScopesStack.Count == 0 && node is AvaloniaNameScopeRegistrationXamlIlNode registration)
                {
                    if (registration.Name is XamlAstTextNode text && text.Text == Name)
                    {
                        TargetType = registration.TargetType;
                    }
                }
                return node;
            }
        }

        interface IXamlIlBindingPathElementNode
        {
            IXamlType Type { get; }

            void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen);
        }

        class XamlIlNotPathElementNode : IXamlIlBindingPathElementNode
        {
            public XamlIlNotPathElementNode(IXamlType boolType)
            {
                Type = boolType;
            }

            public IXamlType Type { get; }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "Not"));
            }
        }

        class XamlIlStreamObservablePathElementNode : IXamlIlBindingPathElementNode
        {
            public XamlIlStreamObservablePathElementNode(IXamlType type)
            {
                Type = type;
            }

            public IXamlType Type { get; }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "StreamObservable").MakeGenericMethod(new[] { Type }));
            }
        }

        class XamlIlStreamTaskPathElementNode : IXamlIlBindingPathElementNode
        {
            public XamlIlStreamTaskPathElementNode(IXamlType type)
            {
                Type = type;
            }

            public IXamlType Type { get; }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "StreamTask").MakeGenericMethod(new[] { Type }));
            }
        }

        class SelfPathElementNode : IXamlIlBindingPathElementNode
        {
            public SelfPathElementNode(IXamlType type)
            {
                Type = type;
            }

            public IXamlType Type { get; }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "Self"));
            }
        }

        class FindAncestorPathElementNode : IXamlIlBindingPathElementNode
        {
            private readonly int _level;

            public FindAncestorPathElementNode(IXamlType ancestorType, int level)
            {
                Type = ancestorType;
                _level = level;
            }

            public IXamlType Type { get; }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                codeGen.Ldtype(Type)
                    .Ldc_I4(_level)
                    .EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "Ancestor"));
            }
        }

        class FindVisualAncestorPathElementNode : IXamlIlBindingPathElementNode
        {
            private readonly int _level;

            public FindVisualAncestorPathElementNode(IXamlType ancestorType, int level)
            {
                Type = ancestorType;
                _level = level;
            }

            public IXamlType Type { get; }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                codeGen.Ldtype(Type)
                    .Ldc_I4(_level)
                    .EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "VisualAncestor"));
            }
        }

        class ElementNamePathElementNode : IXamlIlBindingPathElementNode
        {
            private readonly string _name;

            public ElementNamePathElementNode(string name, IXamlType elementType)
            {
                _name = name;
                Type = elementType;
            }

            public IXamlType Type { get; }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
                    f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);

                codeGen
                    .Ldloc(context.ContextLocal)
                    .Ldfld(scopeField)
                    .Ldstr(_name)
                    .EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "ElementName"));
            }
        }

        class XamlIlAvaloniaPropertyPropertyPathElementNode : IXamlIlBindingPathElementNode
        {
            private readonly IXamlField _field;

            public XamlIlAvaloniaPropertyPropertyPathElementNode(IXamlField field, IXamlType propertyType)
            {
                _field = field;
                Type = propertyType;
            }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                codeGen.Ldsfld(_field);
                context.Configuration.GetExtra<XamlIlPropertyInfoAccessorFactoryEmitter>()
                    .EmitLoadAvaloniaPropertyAccessorFactory(context, codeGen);
                codeGen.EmitCall(context.GetAvaloniaTypes()
                    .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
            }

            public IXamlType Type { get; }
        }

        class XamlIlClrPropertyPathElementNode : IXamlIlBindingPathElementNode
        {
            private readonly IXamlProperty _property;

            public XamlIlClrPropertyPathElementNode(IXamlProperty property)
            {
                _property = property;
            }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
                    .Emit(context, codeGen, _property);

                context.Configuration.GetExtra<XamlIlPropertyInfoAccessorFactoryEmitter>()
                    .EmitLoadInpcPropertyAccessorFactory(context, codeGen);

                codeGen
                    .EmitCall(context.GetAvaloniaTypes()
                        .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
            }

            public IXamlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
        }

        class XamlIlClrMethodPathElementNode : IXamlIlBindingPathElementNode
        {

            public XamlIlClrMethodPathElementNode(IXamlMethod method, IXamlType systemDelegateType)
            {
                Method = method;
                Type = systemDelegateType;
            }
            public IXamlMethod Method { get; }

            public IXamlType Type { get; }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                IXamlTypeBuilder<IXamlILEmitter> newDelegateTypeBuilder = null;
                IXamlType specificDelegateType;
                if (Method.ReturnType == context.Configuration.WellKnownTypes.Void && Method.Parameters.Count == 0)
                {
                    specificDelegateType = context.Configuration.TypeSystem
                        .GetType("System.Action");
                }
                else if (Method.ReturnType == context.Configuration.WellKnownTypes.Void && Method.Parameters.Count <= 16)
                {
                    specificDelegateType = context.Configuration.TypeSystem
                        .GetType($"System.Action`{Method.Parameters.Count}")
                        .MakeGenericType(Method.Parameters);
                }
                else if (Method.Parameters.Count <= 16)
                {
                    List<IXamlType> genericParameters = new();
                    genericParameters.AddRange(Method.Parameters);
                    genericParameters.Add(Method.ReturnType);
                    specificDelegateType = context.Configuration.TypeSystem
                        .GetType($"System.Func`{Method.Parameters.Count + 1}")
                        .MakeGenericType(genericParameters);
                }
                else
                {
                    // In this case, we need to emit our own delegate type.
                    string delegateTypeName = context.Configuration.IdentifierGenerator.GenerateIdentifierPart();
                    specificDelegateType = newDelegateTypeBuilder = context.DefineDelegateSubType(delegateTypeName, Method.ReturnType, Method.Parameters);
                }

                codeGen
                    .Ldtoken(Method)
                    .Ldtoken(specificDelegateType)
                    .EmitCall(context.GetAvaloniaTypes()
                        .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Method"));

                newDelegateTypeBuilder?.CreateType();
            }
        }

        class XamlIlClrMethodAsCommandPathElementNode : IXamlIlBindingPathElementNode
        {
            private readonly IXamlMethod _executeMethod;
            private readonly IXamlMethod _canExecuteMethod;
            private readonly IReadOnlyList<string> _dependsOnProperties;

            public XamlIlClrMethodAsCommandPathElementNode(IXamlType iCommandType, IXamlMethod executeMethod, IXamlMethod canExecuteMethod, IReadOnlyList<string> dependsOnProperties)
            {
                Type = iCommandType;
                _executeMethod = executeMethod;
                _canExecuteMethod = canExecuteMethod;
                _dependsOnProperties = dependsOnProperties;
            }


            public IXamlType Type { get; }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                var trampolineBuilder = context.Configuration.GetExtra<XamlIlTrampolineBuilder>();
                var objectType = context.Configuration.WellKnownTypes.Object;
                codeGen
                    .Ldstr(_executeMethod.Name)
                    .Ldnull()
                    .Ldftn(trampolineBuilder.EmitCommandExecuteTrampoline(context, _executeMethod))
                    .Newobj(context.Configuration.TypeSystem.GetType("System.Action`2")
                        .MakeGenericType(objectType, objectType)
                        .GetConstructor(new() { objectType, context.Configuration.TypeSystem.GetType("System.IntPtr") }));

                if (_canExecuteMethod is null)
                {
                    codeGen.Ldnull();
                }
                else
                {
                    codeGen
                        .Ldnull()
                        .Ldftn(trampolineBuilder.EmitCommandCanExecuteTrampoline(context, _canExecuteMethod))
                        .Newobj(context.Configuration.TypeSystem.GetType("System.Func`3")
                            .MakeGenericType(objectType, objectType, context.Configuration.WellKnownTypes.Boolean)
                            .GetConstructor(new() { objectType, context.Configuration.TypeSystem.GetType("System.IntPtr") }));
                }

                if (_dependsOnProperties is { Count:> 1 })
                {
                    using var dependsOnPropertiesArray = context.GetLocalOfType(context.Configuration.WellKnownTypes.String.MakeArrayType(1));
                    codeGen
                        .Ldc_I4(_dependsOnProperties.Count)
                        .Newarr(context.Configuration.WellKnownTypes.String)
                        .Stloc(dependsOnPropertiesArray.Local);

                    for (int i = 0; i < _dependsOnProperties.Count; i++)
                    {
                        codeGen
                            .Ldloc(dependsOnPropertiesArray.Local)
                            .Ldc_I4(i)
                            .Ldstr(_dependsOnProperties[i])
                            .Stelem_ref();
                    }
                    codeGen.Ldloc(dependsOnPropertiesArray.Local);
                }
                else
                {
                    codeGen.Ldnull();
                }

                codeGen
                    .EmitCall(context.GetAvaloniaTypes()
                        .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Command"));
            }
        }

        class XamlIlClrIndexerPathElementNode : IXamlIlBindingPathElementNode
        {
            private readonly IXamlProperty _property;
            private readonly List<IXamlAstValueNode> _values;
            private readonly string _indexerKey;
            private readonly bool _isNotifyingCollection;

            public XamlIlClrIndexerPathElementNode(IXamlProperty property, List<IXamlAstValueNode> values, string indexerKey, bool isNotifyingCollection)
            {
                _property = property;
                _values = values;
                _indexerKey = indexerKey;
                _isNotifyingCollection = isNotifyingCollection;
            }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                var intType = context.Configuration.TypeSystem.GetType("System.Int32");
                context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
                    .Emit(context, codeGen, _property, _values, _indexerKey);

                if (_isNotifyingCollection
                    &&
                    _values.Count == 1
                    && _values[0].Type.GetClrType().Equals(intType))
                {
                    context.Configuration.GetExtra<XamlIlPropertyInfoAccessorFactoryEmitter>()
                        .EmitLoadIndexerAccessorFactory(context, codeGen, _values[0]);
                }
                else
                {
                    context.Configuration.GetExtra<XamlIlPropertyInfoAccessorFactoryEmitter>()
                        .EmitLoadInpcPropertyAccessorFactory(context, codeGen);
                }

                codeGen.EmitCall(context.GetAvaloniaTypes()
                    .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
            }

            public IXamlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
        }

        class XamlIlArrayIndexerPathElementNode : IXamlIlBindingPathElementNode
        {
            private readonly IXamlType _arrayType;
            private readonly List<int> _values;

            public XamlIlArrayIndexerPathElementNode(IXamlType arrayType, IList<string> values, IXamlLineInfo lineInfo)
            {
                _arrayType = arrayType;
                _values = new List<int>(values.Count);
                foreach (var item in values)
                {
                    if (!int.TryParse(item, out var index))
                    {
                        throw new XamlX.XamlParseException($"Unable to convert '{item}' to an integer.", lineInfo.Line, lineInfo.Position);
                    }
                    _values.Add(index);
                }
            }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                var intType = context.Configuration.TypeSystem.GetType("System.Int32");
                var indices = codeGen.DefineLocal(intType.MakeArrayType(1));
                codeGen.Ldc_I4(_values.Count)
                    .Newarr(intType)
                    .Stloc(indices);
                for (int i = 0; i < _values.Count; i++)
                {
                    codeGen.Ldloc(indices)
                        .Ldc_I4(i)
                        .Ldc_I4(_values[i])
                        .Emit(OpCodes.Stelem_I4);
                }

                codeGen.Ldloc(indices)
                    .Ldtype(Type)
                    .EmitCall(context.GetAvaloniaTypes()
                    .CompiledBindingPathBuilder.FindMethod(m => m.Name == "ArrayElement"));
            }

            public IXamlType Type => _arrayType.ArrayElementType;
        }

        class RawSourcePathElementNode : XamlAstNode, IXamlIlBindingPathElementNode
        {
            private readonly IXamlAstValueNode _rawSource;

            public RawSourcePathElementNode(IXamlAstValueNode rawSource)
                : base(rawSource)
            {
                _rawSource = rawSource;

            }

            public IXamlType Type => _rawSource.Type.GetClrType();

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                context.Emit(_rawSource, codeGen, Type);
                codeGen
                    .EmitCall(context.GetAvaloniaTypes()
                    .CompiledBindingPathBuilder.FindMethod(m => m.Name == "SetRawSource"));
            }
        }

        class TypeCastPathElementNode : IXamlIlBindingPathElementNode
        {
            public TypeCastPathElementNode(IXamlType ancestorType)
            {
                Type = ancestorType;
            }

            public IXamlType Type { get; }

            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "TypeCast").MakeGenericMethod(new[] { Type }));
            }
        }

        class XamlIlBindingPathNode : XamlAstNode, IXamlIlBindingPathNode, IXamlAstLocalsEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
        {
            private readonly List<IXamlIlBindingPathElementNode> _transformElements;

            public XamlIlBindingPathNode(IXamlLineInfo lineInfo,
                IXamlType bindingPathType,
                List<IXamlIlBindingPathElementNode> transformElements,
                List<IXamlIlBindingPathElementNode> elements) : base(lineInfo)
            {
                Type = new XamlAstClrTypeReference(lineInfo, bindingPathType, false);
                _transformElements = transformElements;
                Elements = elements;
            }

            public IXamlType BindingResultType
                => _transformElements.Count > 0
                    ? _transformElements[0].Type
                    : Elements[Elements.Count - 1].Type;

            public IXamlAstTypeReference Type { get; }

            public List<IXamlIlBindingPathElementNode> Elements { get; }

            public XamlILNodeEmitResult Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
            {
                var types = context.GetAvaloniaTypes();
                codeGen.Newobj(types.CompiledBindingPathBuilder.FindConstructor());

                foreach (var transform in _transformElements)
                {
                    transform.Emit(context, codeGen);
                }

                foreach (var element in Elements)
                {
                    element.Emit(context, codeGen);
                }

                codeGen.EmitCall(types.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Build"));
                return XamlILNodeEmitResult.Type(0, types.CompiledBindingPath);
            }

            public override void VisitChildren(IXamlAstVisitor visitor)
            {
                for (int i = 0; i < _transformElements.Count; i++)
                {
                    if (_transformElements[i] is IXamlAstNode ast)
                    {
                        _transformElements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor);
                    }
                }
                for (int i = 0; i < Elements.Count; i++)
                {
                    if (Elements[i] is IXamlAstNode ast)
                    {
                        Elements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor);
                    }
                }
            }
        }
    }

    interface IXamlIlBindingPathNode : IXamlAstValueNode
    {
        IXamlType BindingResultType { get; }
    }
}
