using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Linq;

namespace Radzen;

#nullable enable

/// <summary>
/// Parse lambda expressions from strings.
/// </summary>
[RequiresUnreferencedCode(TrimMessages.ExpressionParserReflection)]
public class ExpressionParser
{
    /// <summary>
    /// Parses a lambda expression that returns a boolean value.
    /// </summary>
    public static Expression<Func<T, bool>> ParsePredicate<T>(string expression, Func<string, Type?>? typeResolver = null)
    {
        return ParseLambda<T, bool>(expression, typeResolver);
    }

    /// <summary>
    /// Parses a lambda expression that returns a typed result.
    /// </summary>
    public static Expression<Func<T, TResult>> ParseLambda<T, TResult>(string expression, Func<string, Type?>? typeResolver = null)
    {
        var lambda = ParseLambda<T>(expression, typeResolver);

        return Expression.Lambda<Func<T, TResult>>(lambda.Body, lambda.Parameters[0]);
    }

    /// <summary>
    /// Parses a lambda expression that returns untyped result.
    /// </summary>
    public static LambdaExpression ParseLambda<T>(string expression, Func<string, Type?>? typeLocator = null)
    {
        return ParseLambda(expression, typeof(T), typeLocator);
    }

    /// <summary>
    /// Parses a lambda expression that returns untyped result.
    /// </summary>
    public static LambdaExpression ParseLambda(string expression, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type, Func<string, Type?>? typeResolver = null)
    {
        var parser = new ExpressionParser(expression, typeResolver);

        return parser.ParseLambda(type);
    }

    private readonly List<Token> tokens;
    private int position;
    private readonly Func<string, Type?>? typeResolver;
    private readonly Stack<ParameterExpression> parameterStack = new();

    private ExpressionParser(string expression, Func<string, Type?>? typeResolver = null)
    {
        this.typeResolver = typeResolver;
        tokens = ExpressionLexer.Scan(expression);
    }

    Token Expect(TokenType type)
    {
        if (position >= tokens.Count)
        {
            throw new InvalidOperationException($"Unexpected end of expression. Expected token: {type}");
        }

        var token = tokens[position];

        if (token.Type != type)
        {
            throw new InvalidOperationException($"Unexpected token: {token.Type}. Expected: {type}");
        }

        position++;

        return token;
    }

    void Advance(int count)
    {
        position += count;
    }

    Token Peek(int offset = 0)
    {
        if (position + offset >= tokens.Count)
        {
            return new Token(TokenType.None, string.Empty);
        }

        return tokens[position + offset];
    }

    private LambdaExpression ParseLambda([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type paramType)
    {
        var parameterIdentifier = Expect(TokenType.Identifier);

        var parameter = Expression.Parameter(paramType, parameterIdentifier.Value);

        parameterStack.Push(parameter);

        Expect(TokenType.EqualsGreaterThan);

        var body = ParseExpression(parameter);

        parameterStack.Pop();

        return Expression.Lambda(body, parameter);
    }

    private Expression ParseExpression(ParameterExpression parameter)
    {
        var left = ParseBinary(parameter);
        var token = Peek();

        if (token.Type is TokenType.AmpersandAmpersand)
        {
            Advance(1);

            var right = ParseExpression(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");

            left = Expression.AndAlso(left, right);
        }
        else if (token.Type is TokenType.BarBar)
        {
            Advance(1);

            var right = ParseExpression(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");

            left = Expression.OrElse(left, right);
        }

        return left;
    }

    private Expression ParseBinary(ParameterExpression parameter)
    {
        var left = ParseNullCoalescing(parameter);
        var token = Peek();

        if (token.Type is TokenType.EqualsEquals or TokenType.NotEquals or TokenType.GreaterThan or TokenType.LessThan or TokenType.LessThanOrEqual or TokenType.GreaterThanOrEqual)
        {
            Advance(1);
            var right = ParseBinary(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");
            AlignBinaryOperandTypes(ref left, ref right);
            left = Expression.MakeBinary(token.Type.ToExpressionType(), left, ConvertIfNeeded(right, left.Type));
        }

        return left;
    }

    private Expression ParseNullCoalescing(ParameterExpression parameter)
    {
        var left = ParseTernary(parameter);
        var token = Peek();

        while (token.Type == TokenType.QuestionMarkQuestionMark)
        {
            Advance(1);

            var right = ParseTernary(parameter) ?? throw new InvalidOperationException($"Expected expression after ?? at position {position}");

            if (right.Type == typeof(object))
            {
                right = ConvertIfNeeded(right, left.Type);
            }

            left = Expression.Coalesce(left, right);

            token = Peek();
        }

        return left;
    }

    private Expression ParseTernary(ParameterExpression parameter)
    {
        var condition = ParseOr(parameter);

        if (Peek().Type == TokenType.QuestionMark)
        {
            Advance(1);

            var trueExpression = ParseOr(parameter);

            Expect(TokenType.Colon);

            var falseExpression = ParseOr(parameter);

            if (trueExpression is ConstantExpression trueConst && trueConst.Value == null && falseExpression is not ConstantExpression)
            {
                trueExpression = Expression.Constant(null, falseExpression.Type);
            }
            else if (falseExpression is ConstantExpression falseConst && falseConst.Value == null && trueExpression is not ConstantExpression)
            {
                falseExpression = Expression.Constant(null, trueExpression.Type);
            }

            var ternary = Expression.Condition(condition, trueExpression, falseExpression);

            return ParseMemberAccess(ternary, parameter);
        }

        return ParseMemberAccess(condition, parameter);
    }

    private Expression ParseMemberAccess(Expression expression, ParameterExpression parameter)
    {
        var token = Peek();
        while (token.Type is TokenType.Dot or TokenType.QuestionDot or TokenType.OpenBracket)
        {
            if (token.Type == TokenType.Dot)
            {
                Advance(1);
                token = Expect(TokenType.Identifier);
                if (Peek().Type == TokenType.OpenParen)
                {
                    expression = ParseInvocation(expression, token.Value, parameter);
                }
                else
                {
                    expression = Expression.PropertyOrField(expression, token.Value);
                }
            }
            else if (token.Type == TokenType.QuestionDot)
            {
                Advance(1);
                token = Expect(TokenType.Identifier);

                var check = Expression.Equal(expression, Expression.Constant(null));

                if (Peek().Type == TokenType.OpenParen)
                {
                    var call = ParseInvocation(expression, token.Value, parameter);
                    expression = Expression.Condition(check, Expression.Constant(null, call.Type), call);
                }
                else
                {
                    var access = Expression.PropertyOrField(expression, token.Value);

                    expression = Expression.Condition(check, Expression.Default(access.Type), access);

                    var nextToken = Peek();

                    if (nextToken.Type == TokenType.Dot || nextToken.Type == TokenType.QuestionDot)
                    {
                        var nextAccess = ParseMemberAccess(access, parameter);

                        expression = Expression.Condition(check, Expression.Default(nextAccess.Type), nextAccess);
                    }
                }
            }
            else if (token.Type == TokenType.OpenBracket)
            {
                Advance(1);
                var index = ParseExpression(parameter);
                Expect(TokenType.CloseBracket);

                if (expression.Type.IsArray)
                {
                    expression = Expression.ArrayIndex(expression, index);
                }
                else
                {
                    var indexer = expression.Type.GetProperty("Item") ?? throw new InvalidOperationException($"Type {expression.Type} does not have an indexer property");

                    expression = Expression.Property(expression, indexer, index);
                }
            }

            token = Peek();
        }

        return expression;
    }

    private MethodCallExpression ParseInvocation(Expression expression, string methodName, ParameterExpression parameter)
    {
        Advance(1);

        var arguments = new List<Expression>();

        if (Peek().Type != TokenType.CloseParen)
        {
            while (Peek().Type != TokenType.CloseParen)
            {
                var token = Peek();

                if (token.Type == TokenType.Identifier && Peek(1).Type == TokenType.EqualsGreaterThan)
                {
                    var lambdaParameterName = token.Value;

                    Advance(2);

                    Type? lambdaParameterType = null;

                    var extensionMethod = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static)
                        .FirstOrDefault(m => m.Name == methodName && m.GetParameters().Length == 2);

                    if (extensionMethod != null)
                    {
                        lambdaParameterType = GetItemType(expression.Type);
                    }

                    if (lambdaParameterType == null)
                    {
                        throw new InvalidOperationException($"Could not infer type for lambda parameter {lambdaParameterName}");
                    }

                    var lambdaParameter = Expression.Parameter(lambdaParameterType, lambdaParameterName);
                    parameterStack.Push(lambdaParameter);
                    var lambdaBody = ParseExpression(lambdaParameter);
                    parameterStack.Pop();
                    arguments.Add(Expression.Lambda(lambdaBody, lambdaParameter));
                }
                else
                {
                    arguments.Add(ParseExpression(parameter));
                }

                if (Peek().Type == TokenType.Comma)
                {
                    Advance(1);
                }
            }
        }

        Expect(TokenType.CloseParen);

        var argumentTypes = arguments.Select(a => a.Type).ToArray();

        var method = expression.Type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance, null, argumentTypes, null);

        if (method != null)
        {
            return Expression.Call(expression, method, arguments);
        }

        method = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static)
                                   .FirstOrDefault(m => m.Name == methodName && m.GetParameters().Length == arguments.Count + 1);

        if (method != null)
        {
            var argumentType = GetItemType(expression.Type);

            if (argumentType == null)
            {
                throw new InvalidOperationException($"Cannot determine item type for {expression.Type}");
            }

            if (method.IsGenericMethodDefinition)
            {
                method = method.MakeGenericMethod(argumentType);
            }

            var parameters = method.GetParameters();

            var argumentsWithInstance = new[] { expression }.Concat(arguments).ToArray();

            return Expression.Call(method, argumentsWithInstance.Select((a, index) => ConvertIfNeeded(a, parameters[index].ParameterType)));
        }

        throw new InvalidOperationException($"No suitable method '{methodName}' found for type '{expression.Type}'");
    }

    private static Type? GetItemType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type enumerableOrArray)
    {
        return enumerableOrArray.IsArray ? enumerableOrArray.GetElementType() : enumerableOrArray.GetGenericArguments()[0];
    }

    private Expression? ParseTerm(ParameterExpression parameter)
    {
        var token = Peek();

        if (token.Type == TokenType.None)
        {
            return null;
        }

        if (token.Type == TokenType.OpenParen)
        {
            Advance(1);

            if (TryParseCastExpression(parameter, out var expression))
            {
                return expression;
            }

            expression = ParseExpression(parameter);

            Expect(TokenType.CloseParen);

            return expression;
        }

        if (token.Type == TokenType.Identifier)
        {
            if (token.Value == "default" && Peek(1).Type == TokenType.OpenParen)
            {
                Advance(2);

                if (!TryParseTypeReference(out var defaultType))
                {
                    throw new InvalidOperationException($"Could not resolve type in default expression at position {position}");
                }

                Expect(TokenType.CloseParen);

                return Expression.Default(defaultType);
            }

            var matchingParameter = parameterStack.FirstOrDefault(p => p.Name == token.Value);
            if (matchingParameter != null)
            {
                Advance(1);
                return ParseMemberAccess(matchingParameter, parameter);
            }

            var type = GetWellKnownType(token.Value);

            if (type != null)
            {
                Advance(1);
                return ParseStaticMemberAccess(type, parameter);
            }

            if (TryParseQualifiedType(out var qualifiedType))
            {
                return ParseStaticMemberAccess(qualifiedType, parameter);
            }

            if (Peek(1).Type == TokenType.OpenParen)
            {
                Advance(1);
                return ParseInvocation(parameter, token.Value, parameter);
            }

            throw new InvalidOperationException($"Unexpected identifier: {token.Value}");
        }

        if (token.Type == TokenType.ExclamationMark)
        {
            Advance(1);

            var operand = ParseTerm(parameter) ?? throw new InvalidOperationException($"Expected expression after ! at position {position}");

            operand = ConvertIfNeeded(operand, typeof(bool));

            return Expression.Not(operand);
        }

        if (token.Type == TokenType.Minus)
        {
            Advance(1);

            var operand = ParseTerm(parameter) ?? throw new InvalidOperationException($"Expected expression after - at position {position}");

            return Expression.Negate(operand);
        }

        if (token.Type == TokenType.Plus)
        {
            Advance(1);

            var operand = ParseTerm(parameter) ?? throw new InvalidOperationException($"Expected expression after + at position {position}");

            return operand;
        }

        switch (token.Type)
        {
            case TokenType.CharacterLiteral:
            case TokenType.StringLiteral:
            case TokenType.NullLiteral:
            case TokenType.NumericLiteral:
            case TokenType.TrueLiteral:
            case TokenType.FalseLiteral:
                Advance(1);
                return token.ToConstantExpression();
            case TokenType.New:
                Advance(1);

                token = Peek();

                if (token.Type == TokenType.OpenBrace)
                {
                    Advance(1);

                    var properties = new List<(string Name, Expression Expression)>();

                    if (Peek().Type != TokenType.CloseBrace)
                    {
                        do
                        {
                            token = Peek();
                            string propertyName;
                            Expression propertyExpression;

                            if (token.Type == TokenType.Identifier)
                            {
                                propertyName = token.Value;
                                Advance(1);
                                if (Peek().Type == TokenType.Dot || Peek().Type == TokenType.QuestionDot)
                                {
                                    // Handle nested property access
                                    Expression expr = propertyName == parameter.Name ? (Expression)parameter : Expression.Property(parameter, propertyName);
                                    propertyExpression = ParseMemberAccess(expr, parameter);

                                    // Get the last identifier token's value
                                    var lastToken = tokens[position - 1];
                                    if (lastToken.Type == TokenType.Identifier)
                                    {
                                        propertyName = lastToken.Value;
                                    }
                                }
                                else
                                {
                                    Expect(TokenType.Equals);
                                    propertyExpression = ParseExpression(parameter);
                                }
                            }
                            else
                            {
                                propertyExpression = ParseExpression(parameter);

                                if (propertyExpression is MemberExpression memberExpression)
                                {
                                    propertyName = memberExpression.Member.Name;
                                }
                                else
                                {
                                    throw new InvalidOperationException($"Invalid anonymous type member expression at position {position}");
                                }
                            }

                            properties.Add((propertyName, propertyExpression));

                            if (Peek().Type == TokenType.Comma)
                            {
                                Advance(1);
                            }
                            else
                            {
                                break;
                            }
                        } while (Peek().Type != TokenType.CloseBrace);
                    }

                    Expect(TokenType.CloseBrace);

                    var propertyTypes = properties.Select(p => p.Expression.Type).ToArray();
                    var propertyNames = properties.Select(p => p.Name).ToArray();
                    var dynamicType = DynamicTypeFactory.CreateType(parameter.Type.Name, propertyNames, propertyTypes);
                    var bindings = properties.Select(p => Expression.Bind(dynamicType.GetProperty(p.Name)!, p.Expression));
                    return Expression.MemberInit(Expression.New(dynamicType), bindings);
                }
                else
                {
                    Type? elementType = null;

                    if (TryParseQualifiedArrayType(out var parsedElementType))
                    {
                        elementType = parsedElementType;
                    }

                    Expect(TokenType.OpenBracket);
                    Expect(TokenType.CloseBracket);
                    Expect(TokenType.OpenBrace);

                    var elements = new List<Expression>();
                    if (Peek().Type != TokenType.CloseBrace)
                    {
                        do
                        {
                            elements.Add(ParseExpression(parameter));
                            if (Peek().Type == TokenType.Comma)
                            {
                                Advance(1);
                            }
                            else
                            {
                                break;
                            }
                        } while (Peek().Type != TokenType.CloseBrace);
                    }

                    Expect(TokenType.CloseBrace);

                    if (elementType == null)
                    {
                        elementType = elements.Count > 0 ? elements[0].Type : typeof(object);
                    }

                    return Expression.NewArrayInit(elementType, elements.Select(e => ConvertIfNeeded(e, elementType)));
                }
            default:
                throw new InvalidOperationException($"Unexpected token: {token.Type} at position {position}");
        }
    }

    private bool TryParseCastExpression(ParameterExpression parameter, out Expression expression)
    {
        expression = null!;

        var token = Peek();

        if (token.Type != TokenType.Identifier)
        {
            return false;
        }

        var typeName = new StringBuilder(token.Value);
        var index = position + 1;
        var typeCast = true;
        var nullable = false;

        while (index < tokens.Count)
        {
            token = tokens[index];

            if (token.Type == TokenType.Dot)
            {
                index++;
                if (index >= tokens.Count || tokens[index].Type != TokenType.Identifier)
                {
                    typeCast = false;
                    break;
                }
                typeName.Append('.').Append(tokens[index].Value);
                index++;
            }
            else if (token.Type == TokenType.QuestionMark)
            {
                nullable = true;
                index++;
                if (index >= tokens.Count || tokens[index].Type != TokenType.CloseParen)
                {
                    typeCast = false;
                    break;
                }
            }
            else if (token.Type == TokenType.CloseParen)
            {
                break;
            }
            else
            {
                typeCast = false;
                break;
            }
        }

        if (typeCast && index < tokens.Count && tokens[index].Type == TokenType.CloseParen)
        {
            var name = typeName.ToString();

            var type = ResolveType(name) ?? throw new InvalidOperationException($"Could not resolve type: {typeName}");

            if (nullable && type.IsValueType)
            {
                type = typeof(Nullable<>).MakeGenericType(type);
            }

            position = index;

            Advance(1);

            if (Peek().Type == TokenType.OpenParen && TryParseCastExpression(parameter, out var innerExpression))
            {
                expression = Expression.Convert(innerExpression, type);
            }
            else
            {
                var source = ParseTerm(parameter) ?? throw new InvalidOperationException($"Expected expression to cast at position {position}");
                expression = Expression.Convert(source, type);
            }

            return true;
        }

        return false;
    }

    private Expression ParseStaticMemberAccess([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicMethods)] Type type, ParameterExpression parameter)
    {
        Expect(TokenType.Dot);

        var token = Expect(TokenType.Identifier);

        if (Peek().Type == TokenType.OpenParen)
        {
            return ParseStaticInvocation(type, token.Value, parameter);
        }
        else
        {
            var member = (MemberInfo?)type.GetProperty(token.Value) ?? type.GetField(token.Value);

            if (member == null)
            {
                throw new InvalidOperationException($"Member {token.Value} not found on type {type.Name}");
            }

            return Expression.MakeMemberAccess(null, member);
        }

        throw new InvalidOperationException($"Expected method invocation after {token.Value} at position {position}");
    }

    private Expression ParseStaticInvocation([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string methodName, ParameterExpression parameter)
    {
        Advance(1);

        var arguments = new List<Expression>();

        if (Peek().Type != TokenType.CloseParen)
        {
            arguments.Add(ParseExpression(parameter));

            while (Peek().Type == TokenType.Comma)
            {
                Advance(1);
                arguments.Add(ParseExpression(parameter));
            }
        }

        Expect(TokenType.CloseParen);

        var method = type.GetMethod(methodName, [.. arguments.Select(a => a.Type)]) ?? throw new InvalidOperationException($"Method {methodName} not found on type {type.Name}");

        return Expression.Call(null, method, arguments);
    }

    /// <summary>
    /// Tries to parse a type reference (e.g. bool, int, System.DateTime, System.DateTime?) and returns the resolved type.
    /// Advances position past the type name. Does not backtrack — expects the full token sequence to be a type.
    /// Used for contexts like default(T) where the entire content is a type name.
    /// </summary>
    private bool TryParseTypeReference(out Type type)
    {
        type = null!;

        var token = Peek();
        if (token.Type != TokenType.Identifier)
        {
            return false;
        }

        var startPosition = position;
        var parts = new List<string> { token.Value };
        Advance(1);

        while (Peek().Type == TokenType.Dot)
        {
            Advance(1);
            token = Peek();
            if (token.Type != TokenType.Identifier)
            {
                position = startPosition;
                return false;
            }
            parts.Add(token.Value);
            Advance(1);
        }

        var nullable = false;
        if (Peek().Type == TokenType.QuestionMark)
        {
            nullable = true;
            Advance(1);
        }

        var typeName = string.Join(".", parts);
        var resolvedType = ResolveType(typeName);
        if (resolvedType != null)
        {
            if (nullable && resolvedType.IsValueType)
            {
                resolvedType = typeof(Nullable<>).MakeGenericType(resolvedType);
            }

            type = resolvedType;
            return true;
        }

        position = startPosition;
        return false;
    }

    /// <summary>
    /// Tries to parse a qualified type name (e.g. System.DateTime or System.DateTime?) and returns the resolved type.
    /// Advances position past the type name. The next token will be . for member access or [ for array.
    /// Uses backtracking to find the longest resolvable type prefix (e.g. System.DateTime in System.DateTime.SpecifyKind).
    /// </summary>
    private bool TryParseQualifiedType(out Type type)
    {
        type = null!;

        var token = Peek();
        if (token.Type != TokenType.Identifier)
        {
            return false;
        }

        var startPosition = position;
        var parts = new List<string> { token.Value };
        Advance(1);

        while (Peek().Type == TokenType.Dot)
        {
            Advance(1);
            token = Peek();
            if (token.Type != TokenType.Identifier)
            {
                return false;
            }
            parts.Add(token.Value);
            Advance(1);
        }

        var nullable = false;
        if (Peek().Type == TokenType.QuestionMark)
        {
            nullable = true;
            Advance(1);
        }

        for (var i = parts.Count; i >= 1; i--)
        {
            var typeName = string.Join(".", parts.Take(i));
            var resolvedType = ResolveType(typeName);
            if (resolvedType != null)
            {
                if (nullable && resolvedType.IsValueType)
                {
                    resolvedType = typeof(Nullable<>).MakeGenericType(resolvedType);
                }

                position = startPosition;
                var tokensToConsume = (i * 2) - 1 + (nullable ? 1 : 0);
                for (var t = 0; t < tokensToConsume; t++)
                {
                    Advance(1);
                }

                type = resolvedType;
                return true;
            }
        }

        position = startPosition;
        return false;
    }

    /// <summary>
    /// Tries to parse a qualified array element type (e.g. System.DateTime? or DateTime) before [].
    /// Advances position past the type name to the [. Returns the element type for the array.
    /// </summary>
    private bool TryParseQualifiedArrayType(out Type? elementType)
    {
        elementType = null;

        var token = Peek();
        if (token.Type != TokenType.Identifier)
        {
            return false;
        }

        var startPosition = position;
        var parts = new List<string> { token.Value };
        Advance(1);

        while (Peek().Type == TokenType.Dot)
        {
            Advance(1);
            token = Peek();
            if (token.Type != TokenType.Identifier)
            {
                position = startPosition;
                return false;
            }
            parts.Add(token.Value);
            Advance(1);
        }

        var nullable = false;
        if (Peek().Type == TokenType.QuestionMark)
        {
            nullable = true;
            Advance(1);
        }

        if (Peek().Type != TokenType.OpenBracket)
        {
            position = startPosition;
            return false;
        }

        for (var i = parts.Count; i >= 1; i--)
        {
            var typeName = string.Join(".", parts.Take(i));
            var resolvedType = ResolveType(typeName);
            if (resolvedType != null)
            {
                if (nullable && resolvedType.IsValueType)
                {
                    resolvedType = typeof(Nullable<>).MakeGenericType(resolvedType);
                }
                elementType = resolvedType;
                return true;
            }
        }

        position = startPosition;
        return false;
    }

    /// <summary>
    /// Resolves a type name using well-known types, the optional type resolver, or by searching loaded assemblies.
    /// This allows any type from loaded assemblies to be resolved without hardcoding.
    /// </summary>
    private Type? ResolveType(string typeName)
    {
        return GetWellKnownType(typeName)
            ?? typeResolver?.Invoke(typeName)
            ?? ResolveTypeFromAssemblies(typeName);
    }

    [RequiresUnreferencedCode(TrimMessages.AssemblyScanning)]
    private static Type? ResolveTypeFromAssemblies(string typeName)
    {
        return AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(a => a.GetTypes())
            .FirstOrDefault(t =>
            {
                var fullName = t.FullName;
                return fullName != null && fullName.Replace("+", ".", StringComparison.Ordinal) == typeName;
            });
    }

    private static Type? GetWellKnownType(string typeName)
    {
        return typeName switch
        {
            nameof(DateTime) => typeof(DateTime),
            nameof(DateOnly) => typeof(DateOnly),
            nameof(TimeOnly) => typeof(TimeOnly),
            nameof(DateTimeOffset) => typeof(DateTimeOffset),
            nameof(Guid) => typeof(Guid),
            nameof(CultureInfo) => typeof(CultureInfo),
            nameof(DateTimeStyles) => typeof(DateTimeStyles),
            nameof(DateTimeKind) => typeof(DateTimeKind),
            nameof(Double) or "double" => typeof(double),
            nameof(Single) or "float" => typeof(float),
            nameof(Int32) or "int" => typeof(int),
            nameof(Int64) or "long" => typeof(long),
            nameof(Int16) or "short" => typeof(short),
            nameof(Byte) or "byte" => typeof(byte),
            nameof(SByte) or "sbyte" => typeof(sbyte),
            nameof(UInt32) or "uint" => typeof(uint),
            nameof(UInt64) or "ulong" => typeof(ulong),
            nameof(UInt16) or "ushort" => typeof(ushort),
            nameof(Boolean) or "bool" => typeof(bool),
            nameof(Char) or "char" => typeof(char),
            nameof(Decimal) or "decimal" => typeof(decimal),
            nameof(String) or "string" => typeof(string),
            nameof(Math) => typeof(Math),
            nameof(Convert) => typeof(Convert),
            _ => null
        };
    }

    private Expression ParseOr(ParameterExpression parameter)
    {
        var left = ParseMemberAccess(ParseAnd(parameter), parameter);

        var token = Peek();
        while (token.Type == TokenType.BarBar)
        {
            Advance(1);
            var right = ParseMemberAccess(ParseAnd(parameter) ?? throw new InvalidOperationException($"Expected expression after || at position {position}"), parameter);
            left = Expression.OrElse(left, right);
            token = Peek();
        }

        return left;
    }

    private Expression ParseAnd(ParameterExpression parameter)
    {
        var left = ParseMemberAccess(ParseComparison(parameter), parameter);

        var token = Peek();
        while (token.Type == TokenType.AmpersandAmpersand)
        {
            Advance(1);
            var right = ParseMemberAccess(ParseComparison(parameter) ?? throw new InvalidOperationException($"Expected expression after && at position {position}"), parameter);
            left = Expression.AndAlso(left, right);
            token = Peek();
        }

        return left;
    }

    private Expression ParseComparison(ParameterExpression parameter)
    {
        var left = ParseShift(parameter);

        var token = Peek();
        if (token.Type is TokenType.EqualsEquals or TokenType.NotEquals or TokenType.GreaterThan or TokenType.LessThan or TokenType.LessThanOrEqual or TokenType.GreaterThanOrEqual)
        {
            Advance(1);
            var right = ParseShift(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");
            AlignBinaryOperandTypes(ref left, ref right);
            left = Expression.MakeBinary(token.Type.ToExpressionType(), left, ConvertIfNeeded(right, left.Type));
        }

        return ParseBinaryAnd(left, parameter);
    }

    private Expression ParseBinaryAnd(Expression left, ParameterExpression parameter)
    {
        var token = Peek();
        while (token.Type == TokenType.Ampersand)
        {
            Advance(1);
            var right = ParseShift(parameter) ?? throw new InvalidOperationException($"Expected expression after & at position {position}");
            left = Expression.MakeBinary(ExpressionType.And, left, ConvertIfNeeded(right, left.Type));
            token = Peek();
        }

        return ParseBinaryXor(left, parameter);
    }

    private Expression ParseBinaryXor(Expression left, ParameterExpression parameter)
    {
        var token = Peek();
        while (token.Type == TokenType.Caret)
        {
            Advance(1);
            var right = ParseBinaryAnd(ParseShift(parameter), parameter) ?? throw new InvalidOperationException($"Expected expression after ^ at position {position}");
            left = Expression.MakeBinary(ExpressionType.ExclusiveOr, left, ConvertIfNeeded(right, left.Type));
            token = Peek();
        }

        return ParseBinaryOr(left, parameter);
    }

    private Expression ParseBinaryOr(Expression left, ParameterExpression parameter)
    {
        var token = Peek();
        while (token.Type == TokenType.Bar)
        {
            Advance(1);
            var right = ParseBinaryXor(ParseShift(parameter), parameter) ?? throw new InvalidOperationException($"Expected expression after | at position {position}");
            left = Expression.MakeBinary(ExpressionType.Or, left, ConvertIfNeeded(right, left.Type));
            token = Peek();
        }

        return left;
    }

    private Expression ParseShift(ParameterExpression parameter)
    {
        var left = ParseAdditive(parameter);

        var token = Peek();
        while (token.Type is TokenType.LessThanLessThan or TokenType.GreaterThanGreaterThan)
        {
            Advance(1);
            var right = ParseAdditive(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");
            left = Expression.MakeBinary(token.Type.ToExpressionType(), left, ConvertIfNeeded(right, left.Type));
            token = Peek();
        }

        return left;
    }

    private Expression ParseAdditive(ParameterExpression parameter)
    {
        var left = ParseMultiplicative(parameter);

        var token = Peek();
        while (token.Type is TokenType.Plus or TokenType.Minus)
        {
            Advance(1);
            var right = ParseMultiplicative(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");

            if (token.Type == TokenType.Plus && left.Type == typeof(string))
            {
                left = Expression.Call(null, typeof(string).GetMethod(nameof(string.Concat), [typeof(string), typeof(string)])!, left, ConvertIfNeeded(right, typeof(string)));
            }
            else
            {
                left = Expression.MakeBinary(token.Type.ToExpressionType(), left, ConvertIfNeeded(right, left.Type));
            }

            token = Peek();
        }

        return left;
    }

    private Expression ParseMultiplicative(ParameterExpression parameter)
    {
        var left = ParseTerm(parameter) ?? throw new InvalidOperationException($"Expected expression at position {position}");

        var token = Peek();
        while (token.Type is TokenType.Star or TokenType.Slash)
        {
            Advance(1);
            var right = ParseTerm(parameter) ?? throw new InvalidOperationException($"Expected expression after {token.Value} at position {position}");
            left = Expression.MakeBinary(token.Type.ToExpressionType(), left, ConvertIfNeeded(right, left.Type));
            token = Peek();
        }

        return left;
    }

    private static Expression ConvertIfNeeded(Expression expression, Type targetType)
    {
        if (expression is not LambdaExpression)
        {
            return expression.Type == targetType ? expression : Expression.Convert(expression, targetType);
        }

        return expression;
    }

    private static void AlignBinaryOperandTypes(ref Expression left, ref Expression right)
    {
        if (left.Type == right.Type)
        {
            return;
        }

        var leftUnderlying = Nullable.GetUnderlyingType(left.Type);
        var rightUnderlying = Nullable.GetUnderlyingType(right.Type);

        if (leftUnderlying != null && rightUnderlying == null && leftUnderlying == right.Type)
        {
            right = Expression.Convert(right, left.Type);
        }
        else if (rightUnderlying != null && leftUnderlying == null && rightUnderlying == left.Type)
        {
            left = Expression.Convert(left, right.Type);
        }
    }
}