#     Copyright 2026, Kay Hayen, mailto:kay.hayen@gmail.com find license text at end of file


"""Reformulation of Python3 class statements.

Consult the Developer Manual for information. TODO: Add ability to sync
source code comments with Developer Manual sections.

"""

from nuitka.nodes.AttributeNodes import (
    ExpressionAttributeCheck,
    ExpressionBuiltinGetattr,
    makeExpressionAttributeLookup,
)
from nuitka.nodes.BuiltinIteratorNodes import ExpressionBuiltinIter1
from nuitka.nodes.BuiltinNextNodes import ExpressionBuiltinNext1
from nuitka.nodes.BuiltinRefNodes import makeExpressionBuiltinTypeRef
from nuitka.nodes.BuiltinTypeNodes import ExpressionBuiltinTuple
from nuitka.nodes.CallNodes import makeExpressionCall
from nuitka.nodes.ClassNodes import (
    ExpressionCallMetaclass,
    ExpressionClassDictBody,
    ExpressionClassMappingBody,
    ExpressionSelectMetaclass,
)
from nuitka.nodes.CodeObjectSpecs import CodeObjectSpec
from nuitka.nodes.ComparisonNodes import makeComparisonExpression
from nuitka.nodes.ConditionalNodes import (
    ExpressionConditional,
    makeStatementConditional,
)
from nuitka.nodes.ConstantRefNodes import (
    ExpressionConstantIntRef,
    ExpressionConstantTupleRef,
    makeConstantRefNode,
)
from nuitka.nodes.ContainerMakingNodes import (
    makeExpressionMakeTuple,
    makeExpressionMakeTupleOrConstant,
)
from nuitka.nodes.ContainerOperationNodes import StatementListOperationAppend
from nuitka.nodes.DictionaryNodes import (
    ExpressionDictOperationGet2,
    ExpressionDictOperationIn,
    StatementDictOperationRemove,
)
from nuitka.nodes.ExceptionNodes import (
    ExpressionBuiltinMakeException,
    StatementRaiseException,
)
from nuitka.nodes.FunctionAttributeNodes import ExpressionFunctionQualnameRef
from nuitka.nodes.FunctionNodes import (
    ExpressionFunctionRef,
    makeExpressionFunctionCall,
    makeExpressionFunctionCreation,
)
from nuitka.nodes.ListOperationNodes import ExpressionListOperationExtend
from nuitka.nodes.LocalsDictNodes import (
    ExpressionLocalsDictRef,
    StatementLocalsDictOperationSet,
    StatementReleaseLocals,
    StatementSetLocals,
)
from nuitka.nodes.LoopNodes import StatementLoop, StatementLoopBreak
from nuitka.nodes.ModuleAttributeNodes import ExpressionModuleAttributeNameRef
from nuitka.nodes.NodeMakingHelpers import (
    makeRaiseExceptionExpressionFromTemplate,
    mergeStatementsWithNone,
)
from nuitka.nodes.OperatorNodes import makeBinaryOperationNode
from nuitka.nodes.OutlineNodes import (
    ExpressionOutlineBody,
    ExpressionOutlineFunction,
)
from nuitka.nodes.ReturnNodes import (
    StatementReturn,
    makeStatementReturnConstant,
)
from nuitka.nodes.StatementNodes import StatementExpressionOnly
from nuitka.nodes.SubscriptNodes import makeExpressionIndexLookup
from nuitka.nodes.TypeNodes import (
    ExpressionBuiltinType1,
    ExpressionSubtypeCheck,
    ExpressionTypeCheck,
    ExpressionTypeMakeGeneric,
)
from nuitka.nodes.VariableAssignNodes import makeStatementAssignmentVariable
from nuitka.nodes.VariableNameNodes import StatementAssignmentVariableName
from nuitka.nodes.VariableRefNodes import (
    ExpressionTempVariableRef,
    ExpressionVariableRef,
)
from nuitka.options.Options import isExperimental
from nuitka.plugins.Hooks import onClassBodyParsing
from nuitka.PythonVersions import python_version
from nuitka.specs.ParameterSpecs import ParameterSpec

from .InternalModule import (
    internal_source_ref,
    makeInternalHelperFunctionBody,
    once_decorator,
)
from .ReformulationDictionaryCreation import buildDictionaryUnpacking
from .ReformulationFunctionStatements import makeDeferredAnnotateFunctionBody
from .ReformulationSequenceCreation import buildTupleUnpacking
from .ReformulationTryExceptStatements import makeTryExceptSingleHandlerNode
from .ReformulationTryFinallyStatements import (
    makeTryFinallyReleaseStatement,
    makeTryFinallyStatement,
)
from .TreeHelpers import (
    buildAnnotationNode,
    buildFrameNode,
    buildNode,
    buildNodeTuple,
    extractDocFromBody,
    getKind,
    makeDictCreationOrConstant2,
    makeStatementsSequenceFromStatement,
    makeStatementsSequenceFromStatements,
    mangleName,
)


def _buildBasesTupleCreationNode(provider, elements, source_ref):
    """For use in Python3 classes for the bases."""

    for element in elements:
        if getKind(element) == "Starred":
            return buildTupleUnpacking(
                provider=provider, elements=elements, source_ref=source_ref
            )

    return makeExpressionMakeTupleOrConstant(
        elements=buildNodeTuple(provider, elements, source_ref),
        user_provided=True,
        source_ref=source_ref,
    )


def _selectClassBody(_static_qualname):
    if isExperimental("force-p2-class"):
        return ExpressionClassDictBody
    else:
        return ExpressionClassMappingBody


def _needsOrigBases(_static_qualname):
    if isExperimental("force-p2-class"):
        return False
    elif python_version < 0x370:
        return False
    else:
        return True


def buildClassNode3(provider, node, source_ref):
    # Many variables, due to the huge re-formulation that is going on here,
    # which just has the complexity and optimization checks:
    # pylint: disable=too-many-branches,too-many-locals,too-many-statements

    # This function is the Python3 special case with special re-formulation as
    # according to Developer Manual.

    # First, allow plugins to modify the code if they want to.
    onClassBodyParsing(provider=provider, class_name=node.name, node=node)

    # We need a container for the temporary variables to not pollute the outside.
    helper_name = "class_container"

    # TODO: Make this a statement of some sorts.
    outline_body = ExpressionOutlineBody(
        provider=provider, name=helper_name, source_ref=source_ref
    )

    class_statement_nodes, class_doc = extractDocFromBody(node)

    # We need a scope for the temporary variables, and they might be closured.
    temp_scope = outline_body.allocateTempScope(name="class_creation")

    tmp_class_decl_dict = outline_body.allocateTempVariable(
        temp_scope=temp_scope, name="class_decl_dict", temp_type="object"
    )
    tmp_metaclass = outline_body.allocateTempVariable(
        temp_scope=temp_scope, name="metaclass", temp_type="object"
    )
    tmp_prepared = outline_body.allocateTempVariable(
        temp_scope=temp_scope, name="prepared", temp_type="object"
    )

    type_variables = []
    if python_version >= 0x3C0 and node.type_params:
        for index, type_param in enumerate(node.type_params, start=1):
            temp_var = outline_body.allocateTempVariable(
                temp_scope=temp_scope,
                name="type_variable_%d" % index,
                temp_type="object",
            )
            data = (type_param, temp_var)
            type_variables.append(data)

    # Can be overridden, but for code object creation, we use that.
    static_qualname = provider.getChildQualname(node.name)

    class_body_class = _selectClassBody(static_qualname)

    class_dict_creation_function = class_body_class(
        provider=provider, name=node.name, doc=class_doc, source_ref=source_ref
    )

    class_locals_scope = class_dict_creation_function.getLocalsScope()

    # Only local variable, for provision to methods.
    class_variable = class_locals_scope.getLocalVariable(
        owner=class_dict_creation_function,
        variable_name="__class__",
    )

    class_locals_scope.registerProvidedVariable(class_variable)

    class_variable_ref = ExpressionVariableRef(
        variable=class_variable, source_ref=source_ref
    )

    parent_module = provider.getParentModule()

    code_object = CodeObjectSpec(
        co_name=node.name,
        co_qualname=static_qualname,
        co_kind="Class",
        co_varnames=(),
        co_freevars=(),
        co_argcount=0,
        co_posonlyargcount=0,
        co_kwonlyargcount=0,
        co_has_starlist=False,
        co_has_stardict=False,
        co_filename=parent_module.getRunTimeFilename(),
        co_lineno=source_ref.getLineNumber(),
        future_spec=parent_module.getFutureSpec(),
    )

    body = buildFrameNode(
        provider=class_dict_creation_function,
        nodes=class_statement_nodes,
        code_object=code_object,
        source_ref=source_ref,
    )

    source_ref_orig = source_ref

    if body is not None:
        # The frame guard has nothing to tell its line number to.
        body.source_ref = source_ref

    locals_scope = class_dict_creation_function.getLocalsScope()

    statements = [
        StatementSetLocals(
            locals_scope=locals_scope,
            new_locals=ExpressionTempVariableRef(
                variable=tmp_prepared, source_ref=source_ref
            ),
            source_ref=source_ref,
        ),
        StatementAssignmentVariableName(
            provider=class_dict_creation_function,
            variable_name="__module__",
            source=ExpressionModuleAttributeNameRef(
                variable=parent_module.getVariableForReference("__name__"),
                source_ref=source_ref,
            ),
            source_ref=source_ref,
        ),
    ]

    if class_doc is not None:
        statements.append(
            StatementAssignmentVariableName(
                provider=class_dict_creation_function,
                variable_name="__doc__",
                source=makeConstantRefNode(
                    constant=class_doc, source_ref=source_ref, user_provided=True
                ),
                source_ref=source_ref,
            )
        )

    # The "__qualname__" attribute has a dedicated node.
    qualname_ref = ExpressionFunctionQualnameRef(
        function_body=class_dict_creation_function, source_ref=source_ref
    )

    statements.append(
        StatementLocalsDictOperationSet(
            locals_scope=locals_scope,
            variable_name="__qualname__",
            source=qualname_ref,
            source_ref=source_ref,
        )
    )

    if python_version >= 0x300:
        qualname_assign = statements[-1]

    if python_version >= 0x3D0:
        statements.append(
            StatementAssignmentVariableName(
                provider=class_dict_creation_function,
                variable_name="__firstlineno__",
                source=ExpressionConstantIntRef(
                    constant=node.lineno,
                    source_ref=source_ref,
                ),
                source_ref=source_ref,
            )
        )

    if python_version >= 0x3E0 and isExperimental("deferred-annotations"):
        if class_dict_creation_function.deferred_annotations:
            outer_body, return_statement = makeDeferredAnnotateFunctionBody(
                provider=class_dict_creation_function,
                source_ref=source_ref,
            )

            keys = []
            values = []
            for (
                var_name,
                ast_node,
            ) in class_dict_creation_function.deferred_annotations.items():
                keys.append(var_name)
                values.append(buildAnnotationNode(outer_body, ast_node, source_ref))

            return_statement.subnode_expression = makeDictCreationOrConstant2(
                keys=keys,
                values=values,
                source_ref=source_ref,
            )
            return_statement.subnode_expression.parent = return_statement

            statements.append(
                StatementLocalsDictOperationSet(
                    locals_scope=locals_scope,
                    variable_name="__annotate_func__",
                    source=makeExpressionFunctionCreation(
                        function_ref=ExpressionFunctionRef(
                            function_body=outer_body,
                            source_ref=source_ref,
                        ),
                        defaults=(),
                        kw_defaults=None,
                        annotations=None,
                        source_ref=source_ref,
                    ),
                    source_ref=source_ref,
                )
            )

            class_dict_creation_function.deferred_annotations = None
    elif (
        python_version >= 0x360
        and class_dict_creation_function.needsAnnotationsDictionary()
    ):
        statements.append(
            StatementLocalsDictOperationSet(
                locals_scope=locals_scope,
                variable_name="__annotations__",
                source=makeConstantRefNode(
                    constant={}, source_ref=source_ref, user_provided=True
                ),
                source_ref=source_ref,
            )
        )

    for type_param, type_param_var in type_variables:
        statements.append(
            StatementAssignmentVariableName(
                provider=class_dict_creation_function,
                source=ExpressionTempVariableRef(
                    variable=type_param_var,
                    source_ref=source_ref,
                ),
                variable_name=type_param.name,
                source_ref=source_ref,
            )
        )

    statements.append(body)

    if python_version >= 0x3D0:
        statements.append(
            StatementAssignmentVariableName(
                provider=class_dict_creation_function,
                variable_name="__static_attributes__",
                source=ExpressionConstantTupleRef(
                    constant=class_dict_creation_function.getStaticAttributes(),
                    user_provided=False,
                    source_ref=source_ref,
                ),
                source_ref=source_ref,
            )
        )

    needs_orig_bases = _needsOrigBases(static_qualname)

    has_type_params = bool(type_variables)
    has_bases = node.bases or has_type_params

    if has_bases:
        tmp_bases = outline_body.allocateTempVariable(
            temp_scope=temp_scope, name="bases", temp_type="object"
        )

        if needs_orig_bases:
            tmp_bases_orig = outline_body.allocateTempVariable(
                temp_scope=temp_scope, name="bases_orig", temp_type="object"
            )

        def makeBasesRef():
            return ExpressionTempVariableRef(variable=tmp_bases, source_ref=source_ref)

        if needs_orig_bases:
            statements.append(
                makeStatementConditional(
                    condition=makeComparisonExpression(
                        comparator="NotEq",
                        left=ExpressionTempVariableRef(
                            variable=tmp_bases, source_ref=source_ref
                        ),
                        right=ExpressionTempVariableRef(
                            variable=tmp_bases_orig, source_ref=source_ref
                        ),
                        source_ref=source_ref,
                    ),
                    yes_branch=StatementLocalsDictOperationSet(
                        locals_scope=locals_scope,
                        variable_name="__orig_bases__",
                        source=ExpressionTempVariableRef(
                            variable=tmp_bases_orig, source_ref=source_ref
                        ),
                        source_ref=source_ref,
                    ),
                    no_branch=None,
                    source_ref=source_ref,
                )
            )

    else:

        def makeBasesRef():
            return makeConstantRefNode(constant=(), source_ref=source_ref)

        needs_orig_bases = False

    new_statements = []
    if has_type_params:
        tmp_type_params = outline_body.allocateTempVariable(
            temp_scope=temp_scope, name="type_params", temp_type="object"
        )
        new_statements.append(
            makeStatementAssignmentVariable(
                variable=tmp_type_params,
                source=makeExpressionMakeTuple(
                    elements=tuple(
                        ExpressionTempVariableRef(
                            variable=type_param_var, source_ref=source_ref
                        )
                        for _, type_param_var in type_variables
                    ),
                    source_ref=source_ref,
                ),
                source_ref=source_ref,
            )
        )
        statements.append(
            StatementAssignmentVariableName(
                provider=class_dict_creation_function,
                variable_name="__type_params__",
                source=ExpressionTempVariableRef(
                    variable=tmp_type_params, source_ref=source_ref
                ),
                source_ref=source_ref,
            )
        )

    statements += (
        makeStatementAssignmentVariable(
            variable=class_variable,
            source=ExpressionCallMetaclass(
                metaclass=ExpressionTempVariableRef(
                    variable=tmp_metaclass, source_ref=source_ref
                ),
                name=makeConstantRefNode(
                    constant=node.name,
                    source_ref=source_ref,
                    user_provided=True,
                ),
                bases=makeBasesRef(),
                dict_arg=ExpressionLocalsDictRef(
                    locals_scope=locals_scope, source_ref=source_ref
                ),
                class_decl_dict=ExpressionTempVariableRef(
                    variable=tmp_class_decl_dict, source_ref=source_ref
                ),
                class_variable=class_variable,
                source_ref=source_ref,
            ),
            source_ref=source_ref,
        ),
        StatementReturn(expression=class_variable_ref, source_ref=source_ref),
    )

    # TODO: Is this something similar to makeTryFinallyReleaseStatement
    body = makeStatementsSequenceFromStatement(
        statement=makeTryFinallyStatement(
            provider=class_dict_creation_function,
            tried=mergeStatementsWithNone(statements),
            final=StatementReleaseLocals(
                locals_scope=locals_scope, source_ref=source_ref
            ),
            source_ref=source_ref,
        )
    )

    # The class body is basically a function that implicitly, at the end
    # returns its locals and cannot have other return statements contained.
    class_dict_creation_function.setChildBody(body)

    # The class body is basically a function that implicitly, at the end
    # returns its created class and cannot have other return statements
    # contained.

    decorated_body = class_dict_creation_function

    for decorator in buildNodeTuple(
        provider, reversed(node.decorator_list), source_ref
    ):
        decorated_body = makeExpressionCall(
            called=decorator,
            args=makeExpressionMakeTuple(
                elements=(decorated_body,), source_ref=source_ref
            ),
            kw=None,
            source_ref=decorator.getSourceReference(),
        )

    statements = new_statements

    if has_bases:
        if type_variables:
            typevar_outline = ExpressionOutlineFunction(
                provider=provider, name="class_typevar", source_ref=source_ref
            )
            outline_statements = []
            for type_param, temp_type_var in type_variables:
                outline_statements.append(
                    StatementAssignmentVariableName(
                        provider=typevar_outline,
                        source=ExpressionTempVariableRef(
                            variable=temp_type_var,
                            source_ref=source_ref,
                        ),
                        variable_name=type_param.name,
                        source_ref=source_ref,
                    )
                )

            unwrapped_bases_value = _buildBasesTupleCreationNode(
                provider=typevar_outline, elements=node.bases, source_ref=source_ref
            )
            outline_statements.append(
                StatementReturn(unwrapped_bases_value, source_ref)
            )
            typevar_outline.setChildBody(
                makeStatementsSequenceFromStatements(outline_statements)
            )
            bases_value = typevar_outline
        else:
            bases_value = _buildBasesTupleCreationNode(
                provider=provider, elements=node.bases, source_ref=source_ref
            )

        if has_type_params:
            bases_value = makeBinaryOperationNode(
                operator="Add",
                left=bases_value,
                right=ExpressionTypeMakeGeneric(
                    type_params=ExpressionTempVariableRef(
                        variable=tmp_type_params,
                        source_ref=source_ref,
                    ),
                    source_ref=source_ref,
                ),
                source_ref=source_ref,
            )

        statements.append(
            makeStatementAssignmentVariable(
                variable=tmp_bases_orig if needs_orig_bases else tmp_bases,
                source=bases_value,
                source_ref=source_ref,
            )
        )

        if needs_orig_bases:
            bases_conversion = makeExpressionFunctionCall(
                function=makeExpressionFunctionCreation(
                    function_ref=ExpressionFunctionRef(
                        function_body=getClassBasesMroConversionHelper(),
                        source_ref=source_ref,
                    ),
                    defaults=(),
                    kw_defaults=None,
                    annotations=None,
                    source_ref=source_ref,
                ),
                values=(
                    ExpressionTempVariableRef(
                        variable=tmp_bases_orig, source_ref=source_ref
                    ),
                ),
                source_ref=source_ref,
            )

            statements.append(
                makeStatementAssignmentVariable(
                    variable=tmp_bases, source=bases_conversion, source_ref=source_ref
                )
            )

    # TODO: It's not really clear, since when those in the middle keywords are accepted
    # and not a SyntaxError, and if then we might have to raise it.
    keyword_keys = tuple(keyword.arg for keyword in node.keywords)
    keyword_values = tuple(keyword.value for keyword in node.keywords)

    if None in keyword_keys:
        assert python_version >= 0x350

        make_keywords_dict = buildDictionaryUnpacking(
            provider,
            keys=keyword_keys,
            values=keyword_values,
            source_ref=source_ref,
        )
    else:
        make_keywords_dict = makeDictCreationOrConstant2(
            keys=keyword_keys,
            values=buildNodeTuple(provider, keyword_values, source_ref),
            source_ref=source_ref,
        )

    statements.append(
        makeStatementAssignmentVariable(
            variable=tmp_class_decl_dict,
            source=make_keywords_dict,
            source_ref=source_ref,
        )
    )

    # Check if there are bases, and if there are, go with the type of the
    # first base class as a metaclass unless it was specified in the class
    # decl dict of course.
    if node.bases:
        unspecified_metaclass_expression = ExpressionBuiltinType1(
            value=makeExpressionIndexLookup(
                expression=ExpressionTempVariableRef(
                    variable=tmp_bases, source_ref=source_ref
                ),
                index_value=0,
                source_ref=source_ref,
            ),
            source_ref=source_ref,
        )

        # Might become empty behind our back during conversion, therefore make the
        # check at run time for 3.7 or higher.
        if needs_orig_bases:
            unspecified_metaclass_expression = ExpressionConditional(
                condition=ExpressionTempVariableRef(
                    variable=tmp_bases, source_ref=source_ref
                ),
                expression_yes=unspecified_metaclass_expression,
                expression_no=makeExpressionBuiltinTypeRef(
                    builtin_name="type", source_ref=source_ref
                ),
                source_ref=source_ref,
            )
    else:
        unspecified_metaclass_expression = makeExpressionBuiltinTypeRef(
            builtin_name="type", source_ref=source_ref
        )

    call_prepare = makeStatementAssignmentVariable(
        variable=tmp_prepared,
        source=makeExpressionCall(
            called=makeExpressionAttributeLookup(
                expression=ExpressionTempVariableRef(
                    variable=tmp_metaclass, source_ref=source_ref
                ),
                attribute_name="__prepare__",
                source_ref=source_ref,
            ),
            args=makeExpressionMakeTuple(
                elements=(
                    makeConstantRefNode(
                        constant=node.name, source_ref=source_ref, user_provided=True
                    ),
                    makeBasesRef(),
                ),
                source_ref=source_ref,
            ),
            kw=ExpressionTempVariableRef(
                variable=tmp_class_decl_dict, source_ref=source_ref
            ),
            source_ref=source_ref,
        ),
        source_ref=source_ref,
    )

    if python_version >= 0x364:
        call_prepare = makeStatementsSequenceFromStatements(
            call_prepare,
            makeStatementConditional(
                condition=ExpressionAttributeCheck(
                    expression=ExpressionTempVariableRef(
                        variable=tmp_prepared, source_ref=source_ref
                    ),
                    attribute_name="__getitem__",
                    source_ref=source_ref,
                ),
                yes_branch=None,
                no_branch=makeRaiseExceptionExpressionFromTemplate(
                    exception_type="TypeError",
                    template="%s.__prepare__() must return a mapping, not %s",
                    template_args=(
                        ExpressionBuiltinGetattr(
                            expression=ExpressionTempVariableRef(
                                variable=tmp_metaclass, source_ref=source_ref
                            ),
                            name=makeConstantRefNode(
                                constant="__name__", source_ref=source_ref
                            ),
                            default=makeConstantRefNode(
                                constant="<metaclass>", source_ref=source_ref
                            ),
                            source_ref=source_ref,
                        ),
                        makeExpressionAttributeLookup(
                            expression=ExpressionBuiltinType1(
                                value=ExpressionTempVariableRef(
                                    variable=tmp_prepared, source_ref=source_ref
                                ),
                                source_ref=source_ref,
                            ),
                            attribute_name="__name__",
                            source_ref=source_ref,
                        ),
                    ),
                    source_ref=source_ref,
                ).asStatement(),
                source_ref=source_ref,
            ),
        )

    if class_body_class is ExpressionClassDictBody:
        prepare_condition = makeConstantRefNode(constant=False, source_ref=source_ref)
    else:
        prepare_condition = ExpressionAttributeCheck(
            expression=ExpressionTempVariableRef(
                variable=tmp_metaclass, source_ref=source_ref
            ),
            attribute_name="__prepare__",
            source_ref=source_ref,
        )

    statements += (
        makeStatementAssignmentVariable(
            variable=tmp_metaclass,
            source=makeExpressionSelectMetaclass(
                metaclass=ExpressionConditional(
                    condition=ExpressionDictOperationIn(
                        key=makeConstantRefNode(
                            constant="metaclass",
                            source_ref=source_ref,
                            user_provided=True,
                        ),
                        dict_arg=ExpressionTempVariableRef(
                            variable=tmp_class_decl_dict, source_ref=source_ref
                        ),
                        source_ref=source_ref,
                    ),
                    expression_yes=ExpressionDictOperationGet2(
                        dict_arg=ExpressionTempVariableRef(
                            variable=tmp_class_decl_dict, source_ref=source_ref
                        ),
                        key=makeConstantRefNode(
                            constant="metaclass",
                            source_ref=source_ref,
                            user_provided=True,
                        ),
                        source_ref=source_ref,
                    ),
                    expression_no=unspecified_metaclass_expression,
                    source_ref=source_ref,
                ),
                bases=makeBasesRef(),
                source_ref=source_ref,
            ),
            source_ref=source_ref_orig,
        ),
        makeStatementConditional(
            condition=ExpressionDictOperationIn(
                key=makeConstantRefNode(
                    constant="metaclass", source_ref=source_ref, user_provided=True
                ),
                dict_arg=ExpressionTempVariableRef(
                    variable=tmp_class_decl_dict, source_ref=source_ref
                ),
                source_ref=source_ref,
            ),
            no_branch=None,
            yes_branch=StatementDictOperationRemove(
                dict_arg=ExpressionTempVariableRef(
                    variable=tmp_class_decl_dict, source_ref=source_ref
                ),
                key=makeConstantRefNode(
                    constant="metaclass", source_ref=source_ref, user_provided=True
                ),
                source_ref=source_ref,
            ),
            source_ref=source_ref,
        ),
        makeStatementConditional(
            condition=prepare_condition,
            yes_branch=call_prepare,
            no_branch=makeStatementAssignmentVariable(
                variable=tmp_prepared,
                source=makeConstantRefNode(
                    constant={}, source_ref=source_ref, user_provided=True
                ),
                source_ref=source_ref,
            ),
            source_ref=source_ref,
        ),
        StatementAssignmentVariableName(
            provider=provider,
            variable_name=mangleName(node.name, provider),
            source=decorated_body,
            source_ref=source_ref,
        ),
    )

    if python_version >= 0x300:
        class_dict_creation_function.qualname_setup = node.name, qualname_assign

    tmp_variables = [tmp_class_decl_dict, tmp_metaclass, tmp_prepared]
    if has_bases:
        tmp_variables.insert(0, tmp_bases)
        if needs_orig_bases:
            tmp_variables.insert(0, tmp_bases_orig)
    if has_type_params:
        tmp_variables.append(tmp_type_params)
    for _, type_temp_var in type_variables:
        tmp_variables.append(type_temp_var)

    type_variable_assignments = []
    for type_param, type_param_var in type_variables:
        type_variable_assignments.append(
            makeStatementAssignmentVariable(
                variable=type_param_var,
                source=buildNode(provider, type_param, source_ref),
                source_ref=source_ref,
            )
        )

    body = makeStatementsSequenceFromStatements(
        type_variable_assignments,
        makeTryFinallyReleaseStatement(
            provider=outline_body,
            tried=statements,
            variables=tmp_variables,
            source_ref=source_ref,
        ),
        makeStatementReturnConstant(constant=None, source_ref=source_ref),
    )
    outline_body.setChildBody(body)

    return StatementExpressionOnly(expression=outline_body, source_ref=source_ref)


# Note: This emulates "Python/bltinmodule.c/update_bases" function. We have it
# here, so we can hope to statically optimize it later on. spell-checker: ignore bltinmodule
@once_decorator
def getClassBasesMroConversionHelper():
    helper_name = "_mro_entries_conversion"

    result = makeInternalHelperFunctionBody(
        name=helper_name,
        parameters=ParameterSpec(
            ps_name=helper_name,
            ps_normal_args=("bases",),
            ps_pos_only_args=(),
            ps_list_star_arg=None,
            ps_dict_star_arg=None,
            ps_default_count=0,
            ps_kw_only_args=(),
        ),
        inline_const_args=False,  # TODO: Allow this.
    )

    temp_scope = None

    tmp_result_variable = result.allocateTempVariable(
        temp_scope, "list", temp_type="object"
    )
    tmp_iter_variable = result.allocateTempVariable(
        temp_scope, "iter", temp_type="object"
    )
    tmp_item_variable = result.allocateTempVariable(
        temp_scope, "base", temp_type="object"
    )

    args_variable = result.getVariableForAssignment(variable_name="bases")

    non_type_case = makeStatementConditional(
        condition=ExpressionAttributeCheck(
            expression=ExpressionTempVariableRef(
                variable=tmp_item_variable, source_ref=internal_source_ref
            ),
            attribute_name="__mro_entries__",
            source_ref=internal_source_ref,
        ),
        yes_branch=StatementExpressionOnly(
            expression=ExpressionListOperationExtend(
                list_arg=ExpressionTempVariableRef(
                    variable=tmp_result_variable, source_ref=internal_source_ref
                ),
                value=makeExpressionCall(
                    called=makeExpressionAttributeLookup(
                        expression=ExpressionTempVariableRef(
                            variable=tmp_item_variable, source_ref=internal_source_ref
                        ),
                        attribute_name="__mro_entries__",
                        source_ref=internal_source_ref,
                    ),
                    args=makeExpressionMakeTuple(
                        elements=(
                            ExpressionVariableRef(
                                variable=args_variable, source_ref=internal_source_ref
                            ),
                        ),
                        source_ref=internal_source_ref,
                    ),
                    kw=None,
                    source_ref=internal_source_ref,
                ),
                source_ref=internal_source_ref,
            ),
            source_ref=internal_source_ref,
        ),
        no_branch=StatementListOperationAppend(
            list_arg=ExpressionTempVariableRef(
                variable=tmp_result_variable, source_ref=internal_source_ref
            ),
            value=ExpressionTempVariableRef(
                variable=tmp_item_variable, source_ref=internal_source_ref
            ),
            source_ref=internal_source_ref,
        ),
        source_ref=internal_source_ref,
    )

    type_case = StatementListOperationAppend(
        list_arg=ExpressionTempVariableRef(
            variable=tmp_result_variable, source_ref=internal_source_ref
        ),
        value=ExpressionTempVariableRef(
            variable=tmp_item_variable, source_ref=internal_source_ref
        ),
        source_ref=internal_source_ref,
    )

    loop_body = makeStatementsSequenceFromStatements(
        makeTryExceptSingleHandlerNode(
            tried=makeStatementAssignmentVariable(
                variable=tmp_item_variable,
                source=ExpressionBuiltinNext1(
                    value=ExpressionTempVariableRef(
                        variable=tmp_iter_variable, source_ref=internal_source_ref
                    ),
                    source_ref=internal_source_ref,
                ),
                source_ref=internal_source_ref,
            ),
            exception_name="StopIteration",
            handler_body=StatementLoopBreak(source_ref=internal_source_ref),
            source_ref=internal_source_ref,
        ),
        makeStatementConditional(
            condition=ExpressionTypeCheck(
                cls=ExpressionTempVariableRef(
                    variable=tmp_item_variable, source_ref=internal_source_ref
                ),
                source_ref=internal_source_ref,
            ),
            yes_branch=type_case,
            no_branch=non_type_case,
            source_ref=internal_source_ref,
        ),
    )

    tried = makeStatementsSequenceFromStatements(
        makeStatementAssignmentVariable(
            variable=tmp_iter_variable,
            source=ExpressionBuiltinIter1(
                value=ExpressionVariableRef(
                    variable=args_variable, source_ref=internal_source_ref
                ),
                source_ref=internal_source_ref,
            ),
            source_ref=internal_source_ref,
        ),
        makeStatementAssignmentVariable(
            variable=tmp_result_variable,
            source=makeConstantRefNode(constant=[], source_ref=internal_source_ref),
            source_ref=internal_source_ref,
        ),
        StatementLoop(loop_body=loop_body, source_ref=internal_source_ref),
        StatementReturn(
            expression=ExpressionBuiltinTuple(
                value=ExpressionTempVariableRef(
                    variable=tmp_result_variable, source_ref=internal_source_ref
                ),
                source_ref=internal_source_ref,
            ),
            source_ref=internal_source_ref,
        ),
    )

    result.setChildBody(
        makeStatementsSequenceFromStatement(
            makeTryFinallyReleaseStatement(
                provider=result,
                tried=tried,
                variables=(
                    args_variable,
                    tmp_result_variable,
                    tmp_iter_variable,
                    tmp_item_variable,
                ),
                source_ref=internal_source_ref,
            )
        )
    )

    return result


def makeExpressionSelectMetaclass(metaclass, bases, source_ref):
    if isExperimental("select-metaclass-helper"):
        return makeExpressionFunctionCall(
            function=makeExpressionFunctionCreation(
                function_ref=ExpressionFunctionRef(
                    function_body=getClassSelectMetaClassHelper(),
                    source_ref=source_ref,
                ),
                defaults=(),
                kw_defaults=None,
                annotations=None,
                source_ref=source_ref,
            ),
            values=(metaclass, bases),
            source_ref=source_ref,
        )

    else:
        return ExpressionSelectMetaclass(
            metaclass=metaclass, bases=bases, source_ref=source_ref
        )


def _makeRaiseExceptionMetaclassConflict():
    return StatementRaiseException(
        exception_type=ExpressionBuiltinMakeException(
            exception_name="TypeError",
            args=(
                makeConstantRefNode(
                    constant="""\
metaclass conflict: the metaclass of a derived class must be a (non-strict) \
subclass of the metaclasses of all its bases""",
                    source_ref=internal_source_ref,
                ),
            ),
            for_raise=False,
            source_ref=internal_source_ref,
        ),
        exception_value=None,
        exception_trace=None,
        exception_cause=None,
        source_ref=internal_source_ref,
    )


# Note: This emulates selection of meta class based on base classes
@once_decorator
def getClassSelectMetaClassHelper():
    helper_name = "_select_metaclass"

    result = makeInternalHelperFunctionBody(
        name=helper_name,
        parameters=ParameterSpec(
            ps_name=helper_name,
            ps_normal_args=(
                "metaclass",
                "bases",
            ),
            ps_pos_only_args=(),
            ps_list_star_arg=None,
            ps_dict_star_arg=None,
            ps_default_count=0,
            ps_kw_only_args=(),
        ),
        inline_const_args=False,  # TODO: Allow this.
    )

    metaclass_variable = result.getVariableForAssignment(variable_name="metaclass")
    bases_variable = result.getVariableForAssignment(variable_name="bases")

    temp_scope = None

    tmp_winner_variable = result.allocateTempVariable(
        temp_scope, "winner", temp_type="object"
    )
    tmp_iter_variable = result.allocateTempVariable(
        temp_scope, "iter", temp_type="object"
    )
    tmp_item_variable = result.allocateTempVariable(
        temp_scope, "base", temp_type="object"
    )
    tmp_item_type_variable = result.allocateTempVariable(
        temp_scope, "base_type", temp_type="object"
    )

    # For non-types, the metaclass cannot be overruled by bases.
    non_type_case = StatementReturn(
        expression=ExpressionVariableRef(
            variable=metaclass_variable, source_ref=internal_source_ref
        ),
        source_ref=internal_source_ref,
    )

    type_loop_body = makeStatementsSequenceFromStatements(
        makeTryExceptSingleHandlerNode(
            tried=makeStatementAssignmentVariable(
                variable=tmp_item_variable,
                source=ExpressionBuiltinNext1(
                    value=ExpressionTempVariableRef(
                        variable=tmp_iter_variable, source_ref=internal_source_ref
                    ),
                    source_ref=internal_source_ref,
                ),
                source_ref=internal_source_ref,
            ),
            exception_name="StopIteration",
            handler_body=StatementLoopBreak(source_ref=internal_source_ref),
            source_ref=internal_source_ref,
        ),
        makeStatementAssignmentVariable(
            variable=tmp_item_type_variable,
            source=ExpressionBuiltinType1(
                value=ExpressionTempVariableRef(
                    variable=tmp_item_variable, source_ref=internal_source_ref
                ),
                source_ref=internal_source_ref,
            ),
            source_ref=internal_source_ref,
        ),
        makeStatementConditional(
            condition=ExpressionSubtypeCheck(
                left=ExpressionTempVariableRef(
                    variable=tmp_winner_variable, source_ref=internal_source_ref
                ),
                right=ExpressionTempVariableRef(
                    variable=tmp_item_type_variable, source_ref=internal_source_ref
                ),
                source_ref=internal_source_ref,
            ),
            yes_branch=None,  # Ignore if current winner is already a subtype.
            no_branch=makeStatementConditional(
                condition=ExpressionSubtypeCheck(
                    left=ExpressionTempVariableRef(
                        variable=tmp_item_type_variable, source_ref=internal_source_ref
                    ),
                    right=ExpressionTempVariableRef(
                        variable=tmp_winner_variable, source_ref=internal_source_ref
                    ),
                    source_ref=internal_source_ref,
                ),
                yes_branch=makeStatementAssignmentVariable(
                    variable=tmp_winner_variable,
                    source=ExpressionTempVariableRef(
                        variable=tmp_item_type_variable, source_ref=internal_source_ref
                    ),
                    source_ref=internal_source_ref,
                ),
                no_branch=_makeRaiseExceptionMetaclassConflict(),
                source_ref=internal_source_ref,
            ),
            source_ref=internal_source_ref,
        ),
    )

    type_case = makeStatementsSequenceFromStatements(
        makeStatementAssignmentVariable(
            variable=tmp_winner_variable,
            source=ExpressionVariableRef(
                variable=metaclass_variable, source_ref=internal_source_ref
            ),
            source_ref=internal_source_ref,
        ),
        makeStatementAssignmentVariable(
            variable=tmp_iter_variable,
            source=ExpressionBuiltinIter1(
                value=ExpressionVariableRef(
                    variable=bases_variable, source_ref=internal_source_ref
                ),
                source_ref=internal_source_ref,
            ),
            source_ref=internal_source_ref,
        ),
        StatementLoop(loop_body=type_loop_body, source_ref=internal_source_ref),
        StatementReturn(
            expression=ExpressionTempVariableRef(
                variable=tmp_winner_variable, source_ref=internal_source_ref
            ),
            source_ref=internal_source_ref,
        ),
    )

    tried = makeStatementConditional(
        condition=ExpressionTypeCheck(
            cls=ExpressionVariableRef(
                variable=metaclass_variable, source_ref=internal_source_ref
            ),
            source_ref=internal_source_ref,
        ),
        yes_branch=type_case,
        no_branch=non_type_case,
        source_ref=internal_source_ref,
    )

    result.setChildBody(
        makeStatementsSequenceFromStatement(
            makeTryFinallyReleaseStatement(
                provider=result,
                tried=tried,
                variables=(
                    tmp_winner_variable,
                    tmp_iter_variable,
                    tmp_item_variable,
                    tmp_item_type_variable,
                ),
                source_ref=internal_source_ref,
            )
        )
    )

    return result


#     Part of "Nuitka", an optimizing Python compiler that is compatible and
#     integrates with CPython, but also works on its own.
#
#     Licensed under the GNU Affero General Public License, Version 3 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#        https://www.gnu.org/licenses/agpl-3.0.txt
#
#     See also: "Nuitka Runtime Library Exception, Version 1.0" in file
#     "LICENSE-RUNTIME.txt" for additional permissions granted under Section 7.
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.
