package p.studio.compiler.pbs.semantics;

import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.*;
import p.studio.compiler.source.diagnostics.DiagnosticSink;

final class PbsFlowExpressionAnalyzer {
    @FunctionalInterface
    interface BlockAnalyzer {
        TypeView analyze(
                PbsAst.Block block,
                Scope outerScope,
                TypeView returnType,
                String resultErrorName,
                TypeView receiverType,
                Model model,
                DiagnosticSink diagnostics,
                boolean valueContext);
    }

    private final PbsFlowTypeOps typeOps;
    private final PbsFlowStructuralExpressionAnalyzer structuralExpressionAnalyzer;
    private final PbsFlowCallableResolutionAnalyzer callableResolutionAnalyzer;
    private final PbsFlowControlExpressionAnalyzer controlExpressionAnalyzer;
    private final PbsFlowBranchExpressionAnalyzer branchExpressionAnalyzer;

    PbsFlowExpressionAnalyzer(final PbsFlowTypeOps typeOps) {
        this.typeOps = typeOps;
        this.structuralExpressionAnalyzer = new PbsFlowStructuralExpressionAnalyzer(
                typeOps,
                this::analyzeExpressionInternal,
                this::analyzeBlock);
        this.callableResolutionAnalyzer = new PbsFlowCallableResolutionAnalyzer(
                typeOps,
                this::analyzeExpressionInternal);
        this.controlExpressionAnalyzer = new PbsFlowControlExpressionAnalyzer(
                typeOps,
                this::analyzeExpressionInternal,
                this::analyzeBlock);
        this.branchExpressionAnalyzer = new PbsFlowBranchExpressionAnalyzer(
                typeOps,
                this::analyzeExpressionInternal,
                this::analyzeBlock);
    }

    ExprResult analyzeExpression(
            final PbsAst.Expression expression,
            final Scope scope,
            final TypeView expectedType,
            final TypeView returnType,
            final String resultErrorName,
            final TypeView receiverType,
            final Model model,
            final DiagnosticSink diagnostics,
            final ExprUse use,
            final boolean valueContext,
            final BlockAnalyzer blockAnalyzer) {
        return analyzeExpressionInternal(
                expression,
                new PbsFlowExpressionContext(
                        scope,
                        expectedType,
                        returnType,
                        resultErrorName,
                        receiverType,
                        model,
                        diagnostics,
                        use,
                        valueContext,
                        blockAnalyzer));
    }

    private ExprResult analyzeExpressionInternal(
            final PbsAst.Expression expression,
            final PbsFlowExpressionContext context) {
        final var structural = structuralExpressionAnalyzer.analyze(expression, context);
        if (structural != null) {
            return structural;
        }
        if (expression instanceof PbsAst.MemberExpr memberExpr) {
            return callableResolutionAnalyzer.analyzeMemberExpression(memberExpr, context);
        }
        if (expression instanceof PbsAst.CallExpr callExpr) {
            return callableResolutionAnalyzer.analyzeCallExpression(callExpr, context);
        }
        if (expression instanceof PbsAst.ApplyExpr applyExpr) {
            return callableResolutionAnalyzer.analyzeApplyExpression(applyExpr, context);
        }
        if (expression instanceof PbsAst.IfExpr ifExpr) {
            return branchExpressionAnalyzer.analyzeIfExpression(ifExpr, context);
        }
        if (expression instanceof PbsAst.SwitchExpr switchExpr) {
            return ExprResult.type(controlExpressionAnalyzer.analyzeSwitchExpression(switchExpr, context));
        }
        if (expression instanceof PbsAst.ElseExpr elseExpr) {
            return branchExpressionAnalyzer.analyzeElseExpression(elseExpr, context);
        }
        if (expression instanceof PbsAst.PropagateExpr propagateExpr) {
            return branchExpressionAnalyzer.analyzePropagateExpression(propagateExpr, context);
        }
        if (expression instanceof PbsAst.HandleExpr handleExpr) {
            return ExprResult.type(controlExpressionAnalyzer.analyzeHandleExpression(handleExpr, context));
        }
        if (expression instanceof PbsAst.BindExpr bindExpr) {
            return callableResolutionAnalyzer.analyzeBindExpression(bindExpr, context);
        }

        return ExprResult.type(TypeView.unknown());
    }

    private TypeView analyzeBlock(
            final PbsAst.Block block,
            final PbsFlowExpressionContext context,
            final boolean valueContext) {
        return context.blockAnalyzer().analyze(
                block,
                context.scope(),
                context.returnType(),
                context.resultErrorName(),
                context.receiverType(),
                context.model(),
                context.diagnostics(),
                valueContext);
    }

}
