package de.dhbwstuttgart.target.generate;

import de.dhbwstuttgart.core.JavaTXCompiler;
import de.dhbwstuttgart.exceptions.DebugException;
import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.parser.SyntaxTreeGenerator.AssignToLocal;
import de.dhbwstuttgart.parser.scope.JavaClassName;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.statement.*;
import de.dhbwstuttgart.syntaxtree.type.*;
import de.dhbwstuttgart.target.tree.MethodParameter;
import de.dhbwstuttgart.target.tree.TargetMethod;
import de.dhbwstuttgart.target.tree.expression.*;
import de.dhbwstuttgart.target.tree.type.*;

import java.lang.reflect.Modifier;
import java.sql.Array;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

public class StatementToTargetExpression implements ASTVisitor {

    public StatementToTargetExpression(ASTToTargetAST converter, IGenerics generics) {
        this.converter = converter;
        this.generics = generics;
    }

    public TargetExpression result;
    private final IGenerics generics;
    private final ASTToTargetAST converter;

    @Override
    public void visit(ArgumentList argumentList) {
        throw new NotImplementedException();
    }

    private class LambdaCaptureFinder extends TracingStatementVisitor {

        // TODO The same mechanism is implemented in Codegen, maybe use it from there?
        final Stack<Set<String>> localVariables = new Stack<>();
        private final List<MethodParameter> parameters;
        private final List<MethodParameter> captures;

        LambdaCaptureFinder(List<MethodParameter> parameters, List<MethodParameter> captures) {
            localVariables.push(new HashSet<>());
            this.parameters = parameters;
            this.captures = captures;
        }

        boolean hasLocalVar(String name) {
            for (var localVariables : this.localVariables) {
                if (localVariables.contains(name))
                    return true;
            }
            return false;
        }

        @Override
        public void visit(Block block) {
            localVariables.push(new HashSet<>());
            super.visit(block);
            localVariables.pop();
        }

        @Override
        public void visit(LocalVar localVar) {
            super.visit(localVar);
            var capture = new MethodParameter(new TargetTypePattern(converter.convert(localVar.getType(), generics), localVar.name));
            if (!hasLocalVar(localVar.name) && !parameters.contains(capture) && !captures.contains(capture))
                captures.add(capture);
        }

        @Override
        public void visit(LocalVarDecl varDecl) {
            var localVariables = this.localVariables.peek();
            localVariables.add(varDecl.getName());
        }

        @Override
        public void visit(LambdaExpression lambda) {
            var newCaptures = new ArrayList<MethodParameter>();
            var captureFinder = new LambdaCaptureFinder(createParameters(lambda), newCaptures);
            lambda.methodBody.accept(captureFinder);
            newCaptures.removeAll(parameters);
            captures.addAll(newCaptures);
        }
    }

    private List<MethodParameter> createParameters(LambdaExpression lambda) {
        return StreamSupport.stream(lambda.params.spliterator(), false)
            .map(p -> (FormalParameter) p)
            .map(p -> new MethodParameter(new TargetTypePattern(converter.convert(p.getType(), generics), p.getName())))
            .toList();
    }

    @Override
    public void visit(LambdaExpression lambdaExpression) {
        var parameters = createParameters(lambdaExpression);
        List<MethodParameter> captures = new ArrayList<>();
        var visitor = new LambdaCaptureFinder(parameters, captures);
        lambdaExpression.methodBody.accept(visitor);

        TargetMethod.Signature signature = new TargetMethod.Signature(Set.of(), parameters, converter.convert(lambdaExpression.getReturnType(), generics));;
        var tpe = converter.convert(lambdaExpression.getType(), generics);
        result = new TargetLambdaExpression(tpe, captures, signature, converter.convert(lambdaExpression.methodBody, this.generics));
    }

    @Override
    public void visit(Assign assign) {
        TargetExpression left;
        if (assign.lefSide instanceof AssignToLocal) {
            left = converter.convert(((AssignToLocal) assign.lefSide).localVar, this.generics);
        } else {
            left = converter.convert(((AssignToField) assign.lefSide).field, this.generics);
        }

        result = new TargetAssign(converter.convert(assign.getType(), generics), left, converter.convert(assign.rightSide, this.generics));
    }

    @Override
    public void visit(BinaryExpr binary) {
        result = switch (binary.operation) {
        case ADD -> new TargetBinaryOp.Add(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        case SUB -> new TargetBinaryOp.Sub(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        case MUL -> new TargetBinaryOp.Mul(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        case MOD -> new TargetBinaryOp.Rem(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        case AND -> new TargetBinaryOp.BAnd(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        case OR -> new TargetBinaryOp.BOr(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        case XOR -> new TargetBinaryOp.XOr(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        case DIV -> new TargetBinaryOp.Div(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        case LESSTHAN -> new TargetBinaryOp.Less(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        case BIGGERTHAN -> new TargetBinaryOp.Greater(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        case LESSEQUAL -> new TargetBinaryOp.LessOrEqual(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        case BIGGEREQUAL -> new TargetBinaryOp.GreaterOrEqual(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        case EQUAL -> new TargetBinaryOp.Equal(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        case NOTEQUAL -> new TargetBinaryOp.NotEqual(converter.convert(binary.getType(), generics), converter.convert(binary.lexpr, this.generics), converter.convert(binary.rexpr, this.generics));
        };
    }

    @Override
    public void visit(BoolExpression bool) {
        result = switch(bool.operation) {
        case OR -> new TargetBinaryOp.Or(converter.convert(bool.getType(), generics), converter.convert(bool.lexpr, generics), converter.convert(bool.rexpr, generics));
        case AND -> new TargetBinaryOp.And(converter.convert(bool.getType(), generics), converter.convert(bool.lexpr, generics), converter.convert(bool.rexpr, generics));
        };
    }

    @Override
    public void visit(Block block) {
        result = converter.convert(block, generics);
    }

    @Override
    public void visit(CastExpr castExpr) {
        result = new TargetCast(converter.convert(castExpr.getType(), generics), converter.convert(castExpr.expr, generics));
    }

    @Override
    public void visit(EmptyStmt emptyStmt) {
        result = null;
    }

    @Override
    public void visit(FieldVar fieldVar) {
        var isStatic = false;
        var type = converter.convert(fieldVar.receiver.getType(), generics);
        var clazz = converter.compiler.getClass(new JavaClassName(type.name()));
        var field = clazz.getField(fieldVar.fieldVarName).orElseThrow();
        result = new TargetFieldVar(converter.convert(fieldVar.getType(), generics), type, Modifier.isStatic(field.modifier), converter.convert(fieldVar.receiver, this.generics), fieldVar.fieldVarName);
    }

    @Override
    public void visit(ForStmt forStmt) {
        result = new TargetFor(
                forStmt.initializer.stream().map(c -> converter.convert(c, generics)).toList(),
                forStmt.condition != null ? converter.convert(forStmt.condition, generics) : null,
                forStmt.loopExpr.stream().map(e -> converter.convert(e, generics)).toList(),
                converter.convertWrapInBlock(forStmt.block, generics)
        );
    }

    @Override
    public void visit(ForEachStmt forEachStmt) {
        result = new TargetForEach(converter.convert(forEachStmt.statement, generics), converter.convert(forEachStmt.expression, generics), converter.convertWrapInBlock(forEachStmt.block, generics));
    }

    @Override
    public void visit(IfStmt ifStmt) {
        result = new TargetIf(converter.convert(ifStmt.expr, generics), converter.convertWrapInBlock(ifStmt.then_block, generics), ifStmt.else_block != null ? converter.convertWrapInBlock(ifStmt.else_block, generics) : null);
    }

    @Override
    public void visit(InstanceOf instanceOf) {
        result = new TargetInstanceOf(converter.convert(instanceOf.getExpression(), generics), converter.convert(instanceOf.getPattern(), this.generics));
    }

    @Override
    public void visit(LocalVar localVar) {
        result = new TargetLocalVar(converter.convert(localVar.getType(), generics), localVar.name);
    }

    @Override
    public void visit(LocalVarDecl localVarDecl) {
        // TODO No value, is this correct?
        result = new TargetVarDecl(converter.convert(localVarDecl.getType(), generics), localVarDecl.getName(), null);
    }

    static boolean convertsTo(TargetType from, TargetType to) {
        if (to.equals(TargetType.Object))
            return true; // TODO Consider type coercion and subtyping
        return to.equals(from);
    }

    Optional<Method> findMethod(JavaClassName className, String name, List<TargetType> args, IGenerics generics, JavaTXCompiler compiler) {
        return ASTToTargetAST.findMethod(converter.compiler.getClass(className), name, args, generics, compiler);
    }

    Optional<Method> findMethod(JavaClassName className, String name, List<TargetType> args, JavaTXCompiler compiler) {
        return ASTToTargetAST.findMethod(converter.compiler.getClass(className), name, args, compiler);
    }

    @Override
    public void visit(MethodCall methodCall) {
        var receiverType = converter.convert(methodCall.receiver.getType(), generics);
        var isFunNType = receiverType instanceof TargetFunNType;

        var returnType = isFunNType ? TargetType.Object : converter.convert(methodCall.signature.getLast(), generics);
        var receiverName = new JavaClassName(converter.convert(methodCall.receiver.getType(), generics).name());
        var argList = methodCall.signature.stream().map(sig -> converter.convert(sig, generics)).toList();
        argList = argList.subList(0, argList.size() - 1);

        Method foundMethod = null;
        var isStatic = false;
        var isInterface = true;
        var isPrivate = false;
        var signature = methodCall.signatureArguments().stream().map(sig -> converter.convert(sig, generics)).toList();

        // Add used TPHs to containing method
        for (var i = 0; i < methodCall.signatureArguments().size(); i++) {
            converter.addSignaturePair(methodCall.signatureArguments().get(i), methodCall.arglist.getArguments().get(i).getType());
        }

        var receiverClass = converter.compiler.getClass(receiverName);
        if (methodCall.receiver instanceof ExpressionReceiver expressionReceiver && expressionReceiver.expr instanceof This) {
            if (receiverClass == null) throw new DebugException("Class " + receiverName + " does not exist!");
            var thisMethod = ASTToTargetAST.findMethod(receiverClass, methodCall.name, signature, generics, converter.compiler);
            ClassOrInterface finalReceiverClass = receiverClass;
            foundMethod = thisMethod.orElseGet(() -> findMethod(finalReceiverClass.getSuperClass().getName(), methodCall.name, signature, generics, converter.compiler).orElseThrow());
        } else if (!isFunNType) {
            receiverClass = converter.compiler.getClass(receiverName);
            if (receiverClass == null) throw new DebugException("Class " + receiverName + " does not exist!");
            foundMethod = findMethod(receiverName, methodCall.name, signature, converter.compiler).orElseThrow();
        }

        if (!isFunNType) {
            returnType = converter.convert(foundMethod.getReturnType(), generics);
            argList = foundMethod.getParameterList().getFormalparalist().stream().map(e -> converter.convert(e.getType(), generics)).toList();
            isStatic = Modifier.isStatic(foundMethod.modifier);
            isPrivate = Modifier.isPrivate(foundMethod.modifier);
            isInterface = receiverClass.isInterface();
        }

        //System.out.println(argList);
        result = new TargetMethodCall(
            converter.convert(methodCall.getType(), generics), returnType, argList,
            converter.convert(methodCall.receiver, generics),
            methodCall.getArgumentList().getArguments().stream().map(arg -> converter.convert(arg, generics)).toList(),
            receiverType, methodCall.name, isStatic, isInterface, isPrivate
        );
    }

    @Override
    public void visit(NewClass newClass) {
        var receiverName = new JavaClassName(newClass.name);
        var ctor = converter.findConstructor(converter.compiler.getClass(receiverName), newClass.signatureArguments().stream().map(arg -> converter.convert(arg, generics)).toList(), generics);
        var signature = ctor.orElseThrow().getParameterList().getFormalparalist().stream().map(e -> converter.convert(e.getType(), generics)).toList();
        result = new TargetNew(new TargetRefType(newClass.name), signature, newClass.getArgumentList().getArguments().stream().map(arg -> converter.convert(arg, generics)).toList());
    }

    @Override
    public void visit(NewArray newArray) {
        // TODO
        throw new NotImplementedException();
    }

    @Override
    public void visit(Return aReturn) {
        result = new TargetReturn(converter.convert(aReturn.retexpr, generics));
    }

    @Override
    public void visit(ReturnVoid aReturn) {
        result = new TargetReturn(null);
    }

    @Override
    public void visit(Break aBreak) {
        result = new TargetBreak();
    }

    @Override
    public void visit(Continue aContinue) {
        result = new TargetContinue();
    }

    @Override
    public void visit(StaticClassName staticClassName) {
        result = new TargetClassName(converter.convert(staticClassName.getType(), generics));
    }

    @Override
    public void visit(Super aSuper) {
        result = new TargetSuper(converter.convert(aSuper.getType(), generics));
    }

    @Override
    public void visit(This aThis) {
        result = new TargetThis(converter.convert(aThis.getType(), generics));
    }

    @Override
    public void visit(WhileStmt whileStmt) {
        result = new TargetWhile(converter.convert(whileStmt.expr, generics), converter.convert(whileStmt.loopBlock, generics));
    }

    @Override
    public void visit(DoStmt whileStmt) {
        result = new TargetDo(converter.convert(whileStmt.expr, generics), converter.convert(whileStmt.loopBlock, generics));
    }

    // TODO These two might not be necessary
    @Override
    public void visit(AssignToField assignLeftSide) {
        result = converter.convert(assignLeftSide.field, generics);
    }

    @Override
    public void visit(AssignToLocal assignLeftSide) {
        result = converter.convert(assignLeftSide.localVar, generics);
    }

    @Override
    public void visit(SuperCall superCall) {
        var aSuper = converter.convert(superCall.receiver.getType(), generics);
        var type = converter.convert(superCall.getType(), generics);
        var receiverName = new JavaClassName(converter.convert(superCall.receiver.getType(), generics).name());
        var clazz = converter.compiler.getClass(receiverName);
        var signature = superCall.signatureArguments().stream().map(arg -> converter.convert(arg, generics)).toList();
        var method = converter.findConstructor(clazz, signature, generics);
        var params = superCall.getArgumentList().getArguments().stream().map(arg -> converter.convert(arg, generics)).toList();

        List<TargetType> argList;
        if (method.isPresent()) {
            argList = method.get().getParameterList().getFormalparalist().stream().map(e -> converter.convert(e.getType(), generics)).toList();
        } else {
            argList = params.stream().map(TargetExpression::type).toList();
        }

        result = new TargetMethodCall(type, null, argList, new TargetSuper(aSuper), params, aSuper, superCall.name, false, false, false);
    }

    @Override
    public void visit(ThisCall thisCall) {
        var aThis = converter.convert(thisCall.receiver.getType(), generics);
        var type = converter.convert(thisCall.getType(), generics);
        var parameters = thisCall.arglist.getArguments().stream().map(par -> converter.convert(par.getType(), generics)).toList();

        result = new TargetMethodCall(type, type, parameters, new TargetThis(aThis), thisCall.getArgumentList().getArguments().stream().map(arg -> converter.convert(arg, generics)).toList(), aThis, thisCall.name, false, false, false);
    }

    @Override
    public void visit(ExpressionReceiver expressionReceiver) {
        result = converter.convert(expressionReceiver.expr, generics);
    }

    @Override
    public void visit(UnaryExpr unaryExpr) {
        result = switch (unaryExpr.operation) {
        case NOT -> new TargetUnaryOp.Not(converter.convert(unaryExpr.getType(), generics), converter.convert(unaryExpr.expr, generics));
        case MINUS -> new TargetUnaryOp.Negate(converter.convert(unaryExpr.getType(), generics), converter.convert(unaryExpr.expr, generics));
        case PREINCREMENT -> new TargetUnaryOp.PreIncrement(converter.convert(unaryExpr.getType(), generics), converter.convert(unaryExpr.expr, generics));
        case PREDECREMENT -> new TargetUnaryOp.PreDecrement(converter.convert(unaryExpr.getType(), generics), converter.convert(unaryExpr.expr, generics));
        case POSTINCREMENT -> new TargetUnaryOp.PostIncrement(converter.convert(unaryExpr.getType(), generics), converter.convert(unaryExpr.expr, generics));
        case PLUS -> new TargetUnaryOp.Add(converter.convert(unaryExpr.getType(), generics), converter.convert(unaryExpr.expr, generics));
        case POSTDECREMENT -> new TargetUnaryOp.PostDecrement(converter.convert(unaryExpr.getType(), generics), converter.convert(unaryExpr.expr, generics));
        };
    }

    @Override
    public void visit(Literal literal) {
        if (literal.value instanceof Integer || literal.value instanceof Short || literal.value instanceof Byte) {

            result = new TargetLiteral.IntLiteral((int) literal.value);
        } else if (literal.value instanceof Float) {
            result = new TargetLiteral.FloatLiteral((float) literal.value);
        } else if (literal.value instanceof Double) {
            result = new TargetLiteral.DoubleLiteral((double) literal.value);
        } else if (literal.value instanceof Long) {
            result = new TargetLiteral.LongLiteral((long) literal.value);
        } else if (literal.value instanceof Character) {
            result = new TargetLiteral.CharLiteral((char) literal.value);
        } else if (literal.value instanceof String) {
            result = new TargetLiteral.StringLiteral((String) literal.value);
        } else if (literal.value instanceof Boolean) {
            result = new TargetLiteral.BooleanLiteral((boolean) literal.value);
        } else if (literal.value == null) {
            result = new TargetLiteral.Null();
        }
    }

    @Override
    public void visit(Throw aThrow) {
        result = new TargetThrow(converter.convert(aThrow.expr, generics));
    }

    @Override
    public void visit(Ternary ternary) {
        result = new TargetTernary(converter.convert(ternary.getType(), generics), converter.convert(ternary.cond, generics), converter.convert(ternary.iftrue, generics), converter.convert(ternary.iffalse, generics));
    }

    record TypeVariants(RefTypeOrTPHOrWildcardOrGeneric in, List<RefTypeOrTPHOrWildcardOrGeneric> types) {}

    private List<TypeVariants> extractAllPatterns(Pattern pattern) {
        return switch (pattern) {
            case GuardedPattern guarded -> extractAllPatterns(guarded.getNestedPattern());
            case RecordPattern recordPattern -> recordPattern.getSubPattern().stream()
                    .map(this::extractAllPatterns)
                    .flatMap(List::stream).toList();
            case FormalParameter param -> List.of(new TypeVariants(param.getType(), converter.findAllVariants(param.getType())));
            default -> List.of();
        };
    }

    record TypePair(RefTypeOrTPHOrWildcardOrGeneric in, RefTypeOrTPHOrWildcardOrGeneric out) {}

    private void cartesianProduct(
            List<TypeVariants> variants, int index,
            List<RefTypeOrTPHOrWildcardOrGeneric> current,
            List<List<RefTypeOrTPHOrWildcardOrGeneric>> result) {

        if (index == variants.size()) {
            result.add(new ArrayList<>(current));
            return;
        }
        var currentSet = variants.get(index).types;
        for (var element: currentSet) {
            current.add(element);
            cartesianProduct(variants, index + 1, current, result);
            current.removeLast();
        }
    }

    private List<List<TypePair>> cartesianProduct(List<TypeVariants> variants) {
        var prod = new ArrayList<List<RefTypeOrTPHOrWildcardOrGeneric>>();
        cartesianProduct(variants, 0, new ArrayList<>(), prod);

        var res = new ArrayList<List<TypePair>>();
        for (var list : prod) {
            var l = new ArrayList<TypePair>();
            for (var i = 0; i < list.size(); i++) {
                l.add(new TypePair(variants.get(i).in, list.get(i)));
            }
            res.add(l);
        }
        return res;
    }

    @Override
    public void visit(Switch switchStmt) {
        var variants = converter.findAllVariants(switchStmt.getSwitch().getType());
        var returns = converter.findAllVariants(switchStmt.getType());
        var canBeOverloaded = variants.size() == 1 && returns.size() == 1;

        var cases = switchStmt.getBlocks().stream().filter(s -> !s.isDefault()).map(case_ -> {
            var overloads = new ArrayList<TargetSwitch.Case>();

            if (canBeOverloaded) {
                for (var label: case_.getLabels()) {
                    var product = cartesianProduct(extractAllPatterns(label.getPattern()));

                    for (var l : product) {
                        // Set the generics to matching result set
                        /*for (var generics : converter.currentMethodOverloads) {
                            var java = generics.javaGenerics();
                            var equals = true;
                            for (var pair : l) {
                                if (!java.getType(pair.in).equals(pair.out)) {
                                    equals = false; break;
                                }
                            }
                            if (equals) {
                                break;
                            }
                        }*/

                        overloads.add(converter.convert(case_, generics));
                    }
                }
            } else {
                overloads.add(converter.convert(case_, generics));
            }

            return overloads;
        }).flatMap(List::stream).toList();

        TargetSwitch.Case default_ = null;
        for (var block : switchStmt.getBlocks()) {
            if (block.isDefault()) {
                default_ = new TargetSwitch.Case(converter.convert((Block) block, generics), block.isExpression);
            }
        }
        result = new TargetSwitch(converter.convert(switchStmt.getSwitch(), generics), cases, default_ , converter.convert(switchStmt.getType(), generics), !switchStmt.getStatement());
    }

    @Override
    public void visit(SwitchBlock switchBlock) {}

    @Override
    public void visit(SwitchLabel switchLabel) {
        result = converter.convert(switchLabel.getPattern(), this.generics);
    }

    @Override
    public void visit(Yield aYield) {
        result = new TargetYield(converter.convert(aYield.retexpr, generics));
    }

    @Override
    public void visit(SourceFile sourceFile) {

    }

    @Override
    public void visit(GenericTypeVar genericTypeVar) {

    }

    @Override
    public void visit(GenericDeclarationList genericTypeVars) {

    }

    @Override
    public void visit(Field field) {

    }

    @Override
    public void visit(de.dhbwstuttgart.syntaxtree.Method field) {

    }

    @Override
    public void visit(Constructor field) {

    }

    @Override
    public void visit(ParameterList formalParameters) {

    }

    @Override
    public void visit(ClassOrInterface classOrInterface) {

    }

    @Override
    public void visit(RefType refType) {

    }

    @Override
    public void visit(SuperWildcardType superWildcardType) {

    }

    @Override
    public void visit(TypePlaceholder typePlaceholder) {

    }

    @Override
    public void visit(ExtendsWildcardType extendsWildcardType) {

    }

    @Override
    public void visit(GenericRefType genericRefType) {

    }

    @Override
    public void visit(FormalParameter aPattern) {
        result = new TargetTypePattern(converter.convert(aPattern.getType(), generics), aPattern.getName());
    }

    @Override
    public void visit(LiteralPattern literalPattern) {
        result = new TargetExpressionPattern(converter.convert(literalPattern.value, generics));
    }

    @Override
    public void visit(ExpressionPattern aPattern) {
        result = converter.convert(aPattern.getExpression(), generics);
    }

    @Override
    public void visit(RecordPattern aRecordPattern) {
        result = new TargetComplexPattern(
            converter.convert(aRecordPattern.getCtor(), generics),
            converter.convert(aRecordPattern.getType(), generics),
            aRecordPattern.getName(),
            aRecordPattern.getSubPattern().stream().map(x -> (TargetPattern) converter.convert(x, generics)).toList()
        );
    }

    @Override
    public void visit(GuardedPattern aGuardedPattern) {
        result = new TargetGuard((TargetPattern) converter.convert(aGuardedPattern.getNestedPattern(), generics), converter.convert(aGuardedPattern.getCondition(), generics));
    }
}
