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


""" This tool is generating node variants from Jinja templates.

"""

import sys
import nuitka.Options

nuitka.Options.is_full_compat = False

# isort:start

import textwrap
from collections import namedtuple

import nuitka.code_generation.BinaryOperationHelperDefinitions
import nuitka.code_generation.CodeGeneration
import nuitka.code_generation.ComparisonCodes
import nuitka.code_generation.Namify
import nuitka.nodes.NetworkxNodes
import nuitka.nodes.PackageMetadataNodes
import nuitka.nodes.PackageResourceNodes
import nuitka.nodes.SideEffectNodes
import nuitka.nodes.TensorflowNodes
import nuitka.specs.BuiltinBytesOperationSpecs
import nuitka.specs.BuiltinDictOperationSpecs
import nuitka.specs.BuiltinListOperationSpecs
import nuitka.specs.BuiltinStrOperationSpecs
import nuitka.specs.BuiltinTypeOperationSpecs
import nuitka.specs.HardImportSpecs
import nuitka.tree.Building
from nuitka.containers.OrderedDicts import OrderedDict
from nuitka.containers.OrderedSets import OrderedSet
from nuitka.nodes.ImportNodes import hard_modules_non_stdlib
from nuitka.nodes.NodeMetaClasses import NodeCheckMetaClass
from nuitka.nodes.shapes.BuiltinTypeShapes import (
    tshape_bool,
    tshape_bytes,
    tshape_dict,
    tshape_int,
    tshape_list,
    tshape_none,
    tshape_str,
    tshape_tuple,
)
from nuitka.utils.Jinja2 import getTemplate

from .Common import (
    formatArgs,
    getMethodVariations,
    getSpecs,
    python2_dict_methods,
    python2_list_methods,
    python2_str_methods,
    python2_type_methods,
    python3_bytes_methods,
    python3_dict_methods,
    python3_list_methods,
    python3_str_methods,
    python3_type_methods,
    withFileOpenedAndAutoFormatted,
    writeLine,
)

# This defines which attribute nodes are to specialize and how
# to do that.
attribute_information = {}

# Which ones have operations implemented.
attribute_shape_operations = {}

# What result shape is known for the operation if used.
attribute_shape_operations_result_types = {}

# What mixing class should be used for the operation if used.
attribute_shape_operations_mixin_classes = {}

# Version specific tests for attributes.
attribute_shape_versions = {}

# Argument count specific operation nodes if used.
attribute_shape_variations = {}

# Arguments names differences in spec vs. node
attribute_shape_node_arg_mapping = {}

# Argument names of an operation.
attribute_shape_args = {}

# How to test for argument name presence
attribute_shape_arg_tests = {}

# Some methods are static, but we still do them
attribute_shape_static = {}

# Translations for node names.
node_factory_translations = {}


def _getMixinForShape(shape):
    # Return driven for better debugging experience, pylint: disable=too-many-return-statements

    if shape is tshape_str:
        return "nuitka.nodes.ExpressionShapeMixins.ExpressionStrShapeExactMixin"
    elif shape is tshape_list:
        return "nuitka.nodes.ExpressionShapeMixins.ExpressionListShapeExactMixin"
    elif shape is tshape_tuple:
        return "nuitka.nodes.ExpressionShapeMixins.ExpressionTupleShapeExactMixin"
    elif shape is tshape_int:
        return "nuitka.nodes.ExpressionShapeMixins.ExpressionIntShapeExactMixin"
    elif shape is tshape_bool:
        return "nuitka.nodes.ExpressionShapeMixins.ExpressionBoolShapeExactMixin"
    elif shape is tshape_none:
        return "nuitka.nodes.ExpressionShapeMixins.ExpressionNoneShapeExactMixin"
    elif shape is tshape_dict:
        return "nuitka.nodes.ExpressionShapeMixins.ExpressionDictShapeExactMixin"
    elif shape is tshape_bytes:
        return "nuitka.nodes.ExpressionShapeMixins.ExpressionBytesShapeExactMixin"
    else:
        assert False, shape


def processTypeShapeAttribute(
    shape_name, spec_module, python2_methods, python3_methods, staticmethod_names=()
):
    for method_name in python2_methods:
        attribute_information.setdefault(method_name, set()).add(shape_name)
        key = method_name, shape_name

        if method_name not in python3_methods:
            attribute_shape_versions[key] = "str is bytes"

        (
            present,
            arg_names,
            arg_tests,
            arg_name_mapping,
            arg_counts,
            result_shape,
        ) = getMethodVariations(
            spec_module=spec_module, shape_name=shape_name, method_name=method_name
        )

        attribute_shape_operations[key] = present
        attribute_shape_operations_result_types[key] = result_shape

        if result_shape is not None:
            attribute_shape_operations_mixin_classes[key] = [
                _getMixinForShape(result_shape)
            ]

        if present:
            attribute_shape_args[key] = tuple(arg_names)
            attribute_shape_arg_tests[key] = arg_tests
            attribute_shape_static[key] = method_name in staticmethod_names

            if len(arg_counts) > 1:
                attribute_shape_variations[key] = arg_counts

            attribute_shape_node_arg_mapping[key] = arg_name_mapping

    for method_name in python3_methods:
        attribute_information.setdefault(method_name, set()).add(shape_name)
        key = method_name, shape_name

        if method_name not in python2_methods:
            attribute_shape_versions[key] = "str is not bytes"

        (
            present,
            arg_names,
            arg_tests,
            arg_name_mapping,
            arg_counts,
            result_shape,
        ) = getMethodVariations(
            spec_module=spec_module, shape_name=shape_name, method_name=method_name
        )

        attribute_shape_operations[key] = present
        attribute_shape_operations_result_types[key] = result_shape

        if result_shape is not None:
            attribute_shape_operations_mixin_classes[key] = [
                _getMixinForShape(result_shape)
            ]

        if present:
            attribute_shape_args[key] = tuple(arg_names)
            attribute_shape_arg_tests[key] = arg_tests
            attribute_shape_static[key] = method_name in staticmethod_names

            if len(arg_counts) > 1:
                attribute_shape_variations[key] = arg_counts

            attribute_shape_node_arg_mapping[key] = arg_name_mapping


processTypeShapeAttribute(
    "tshape_dict",
    nuitka.specs.BuiltinDictOperationSpecs,
    python2_dict_methods,
    python3_dict_methods,
    ("fromkeys",),
)


processTypeShapeAttribute(
    "tshape_str",
    nuitka.specs.BuiltinStrOperationSpecs,
    python2_str_methods,
    python3_str_methods,
)

processTypeShapeAttribute(
    "tshape_bytes",
    nuitka.specs.BuiltinBytesOperationSpecs,
    (),
    python3_bytes_methods,
)

processTypeShapeAttribute(
    "tshape_list",
    nuitka.specs.BuiltinListOperationSpecs,
    python2_list_methods,
    python3_list_methods,
)

processTypeShapeAttribute(
    "tshape_type",
    nuitka.specs.BuiltinTypeOperationSpecs,
    python2_type_methods,
    python3_type_methods,
)


attribute_shape_empty = {}

attribute_shape_empty[
    "update", "tshape_dict"
] = """\
lambda source_ref: wrapExpressionWithNodeSideEffects(
    new_node=makeConstantRefNode(
        constant=None,
        source_ref=source_ref
    ),
    old_node=dict_arg
)
"""

attribute_shape_empty[
    "fromkeys", "tshape_dict"
] = """
lambda source_ref: makeRaiseExceptionReplacementExpression(
    expression=dict_arg,
    exception_type="TypeError",
    exception_value=getDictFromkeysNoArgErrorMessage(),
)
"""


def emitGenerationWarning(emit, doc_string, template_name):
    generate_names = set()

    generate_names.update(attribute_information.keys())
    generate_names.update(
        attribute_name.replace("_", "") for attribute_name in attribute_information
    )

    generate_names.update(sum(attribute_shape_args.values(), ()))

    for spec_descriptions in getSpecVersions(nuitka.specs.HardImportSpecs):
        spec = spec_descriptions[0][2]
        generate_names.update(spec.getArgumentNames())

    ignores = textwrap.fill(
        " ".join(sorted(generate_names)),
        width=90,
        initial_indent="spell-checker: ignore ",
        subsequent_indent="spell-checker: ignore ",
        break_on_hyphens=False,
        break_long_words=False,
        expand_tabs=False,
        replace_whitespace=False,
    )

    emit(
        """
# We are not avoiding these in generated code at all
# pylint: disable=I0021,line-too-long,too-many-instance-attributes,too-many-lines
# pylint: disable=I0021,too-many-arguments,too-many-return-statements,too-many-statements
"""
    )

    emit(
        '''
"""%s

WARNING, this code is GENERATED. Modify the template %s instead!

%s
"""

'''
        % (doc_string, template_name, ignores)
    )


def formatCallArgs(operation_node_arg_mapping, args, starting=True):
    def mapName(arg):
        if not operation_node_arg_mapping:
            return arg
        else:
            return operation_node_arg_mapping.get(arg, arg)

    def mapValue(arg):
        if arg == "pairs":
            return "makeKeyValuePairExpressionsFromKwArgs(pairs)"
        else:
            return arg

    if args is None:
        result = ""
    else:
        result = ",".join("%s=%s" % (mapName(arg), mapValue(arg)) for arg in args)

    if not starting and result:
        result = "," + result

    # print("args", args, "->", result)

    return result


def _getPython3OperationName(attribute_name):
    # Some attributes lead to different operations for Python3.
    if attribute_name == "items":
        return "iteritems"
    elif attribute_name == "keys":
        return "iterkeys"
    elif attribute_name == "values":
        return "itervalues"
    else:
        return None


def makeAttributeNodes():
    filename_python = "nuitka/nodes/AttributeNodesGenerated.py"

    template = getTemplate(
        package_name=__package__,
        template_subdir="templates_python",
        template_name="AttributeNodeFixed.py.j2",
    )

    with withFileOpenedAndAutoFormatted(
        filename_python, ignore_errors=True
    ) as output_python:

        def emit(*args):
            writeLine(output_python, *args)

        emitGenerationWarning(emit, "Specialized attribute nodes", template.name)

        emit("from .AttributeLookupNodes import ExpressionAttributeLookupFixedBase")
        emit("from nuitka.specs.BuiltinParameterSpecs import extractBuiltinArgs")

        emit("from nuitka.nodes.ConstantRefNodes import makeConstantRefNode")
        emit(
            """\
from nuitka.nodes.NodeMakingHelpers import (
    wrapExpressionWithNodeSideEffects,
    makeRaiseExceptionReplacementExpression
)
            """
        )

        emit(
            "from nuitka.nodes.KeyValuePairNodes import makeKeyValuePairExpressionsFromKwArgs"
        )

        emit("from nuitka.nodes.AttributeNodes import makeExpressionAttributeLookup")

        emit("from nuitka.PythonVersions import getDictFromkeysNoArgErrorMessage")

        # TODO: Maybe generate its effect instead of using a base class.
        emit("from .NodeBases import SideEffectsFromChildrenMixin")

        emit("attribute_classes = {}")
        emit("attribute_typed_classes = set()")

        for attribute_name, shape_names in sorted(attribute_information.items()):
            code = template.render(
                attribute_name=attribute_name,
                python3_operation_name=_getPython3OperationName(attribute_name),
                shape_names=shape_names,
                attribute_shape_versions=attribute_shape_versions,
                attribute_shape_operations=attribute_shape_operations,
                attribute_shape_variations=attribute_shape_variations,
                attribute_shape_node_arg_mapping=attribute_shape_node_arg_mapping,
                attribute_shape_args=attribute_shape_args,
                attribute_shape_arg_tests=attribute_shape_arg_tests,
                attribute_shape_empty=attribute_shape_empty,
                attribute_shape_static=attribute_shape_static,
                formatArgs=formatArgs,
                formatCallArgs=formatCallArgs,
                translateNodeClassName=translateNodeClassName,
                reversed=reversed,
                str=str,
                name=template.name,
            )

            emit(code)


def makeBuiltinOperationNodes():
    filename_python = "nuitka/nodes/BuiltinOperationNodeBasesGenerated.py"

    template = getTemplate(
        package_name=__package__,
        template_subdir="templates_python",
        template_name="BuiltinOperationNodeBases.py.j2",
    )

    with withFileOpenedAndAutoFormatted(
        filename_python, ignore_errors=True
    ) as output_python:

        def emit(*args):
            writeLine(output_python, *args)

        emitGenerationWarning(emit, "Specialized attribute nodes", template.name)

        for attribute_name, shape_names in sorted(attribute_information.items()):
            attribute_name_class = attribute_name.replace("_", "").title()

            code = template.render(
                attribute_name=attribute_name,
                attribute_name_class=attribute_name_class,
                python3_operation_name=_getPython3OperationName(attribute_name),
                shape_names=shape_names,
                attribute_shape_versions=attribute_shape_versions,
                attribute_shape_operations=attribute_shape_operations,
                attribute_shape_variations=attribute_shape_variations,
                attribute_shape_node_arg_mapping=attribute_shape_node_arg_mapping,
                attribute_shape_args=attribute_shape_args,
                attribute_shape_arg_tests=attribute_shape_arg_tests,
                attribute_shape_empty=attribute_shape_empty,
                attribute_shape_static=attribute_shape_static,
                attribute_shape_operations_mixin_classes=attribute_shape_operations_mixin_classes,
                formatArgs=formatArgs,
                formatCallArgs=formatCallArgs,
                addChildrenMixin=addChildrenMixin,
                reversed=reversed,
                str=str,
                repr=repr,
                name=template.name,
            )

            emit(code)


def adaptModuleName(value):
    if value == "importlib_metadata":
        return "importlib_metadata_backport"

    if value == "importlib_resources":
        return "importlib_resources_backport"

    return value


def makeTitleCased(value):
    return "".join(s.title() for s in value.split("_")).replace(".", "")


def makeCodeCased(value):
    return value.replace(".", "_")


def getCallModuleName(module_name, function_name):
    # return driven, pylint: disable=too-many-return-statements

    if module_name in ("pkg_resources", "importlib.metadata", "importlib_metadata"):
        if function_name in ("resource_stream", "resource_string"):
            return "PackageResourceNodes"

        return "PackageMetadataNodes"
    if module_name in ("pkgutil", "importlib.resources", "importlib_resources"):
        return "PackageResourceNodes"
    if module_name in ("os", "sys", "os.path"):
        return "OsSysNodes"
    if module_name == "ctypes":
        return "CtypesNodes"
    if module_name == "builtins":
        if function_name == "open":
            return "BuiltinOpenNodes"

    if module_name == "tensorflow":
        return "TensorflowNodes"

    if module_name.startswith("networkx"):
        return "NetworkxNodes"

    assert False, (module_name, function_name)


def translateNodeClassName(node_class_name):
    return node_factory_translations.get(node_class_name, node_class_name)


def makeMixinName(
    is_expression,
    is_statement,
    named_children,
    named_children_types,
    named_children_checkers,
    auto_compute_handling,
    node_attributes,
):
    def _addType(name):
        if name in named_children_types:
            if (
                named_children_types[name] == "optional"
                and named_children_checkers.get(name) == "convertNoneConstantToNone"
            ):
                return ""

            return "_" + named_children_types[name]
        else:
            return ""

    def _addChecker(name):
        if name in named_children_checkers:
            if named_children_checkers[name] == "convertNoneConstantToNone":
                return "_auto_none"
            if named_children_checkers[name] == "convertEmptyStrConstantToNone":
                return "_auto_none_empty_str"
            if named_children_checkers[name] == "checkStatementsSequenceOrNone":
                return "_statements_or_none"
            if named_children_checkers[name] == "checkStatementsSequence":
                return "_statements"
            else:
                assert False, named_children_checkers[name]
        else:
            return ""

    mixin_name = "".join(
        makeTitleCased(named_child + _addType(named_child) + _addChecker(named_child))
        for named_child in named_children
    )

    mixin_name += (
        "_".join(sorted(auto_compute_handling))
        .title()
        .replace("_", "")
        .replace(":", "")
    )

    mixin_name += "_".join(sorted(node_attributes)).title().replace("_", "")

    if len(named_children) == 0:
        mixin_name = "NoChildHaving" + mixin_name + "Mixin"
    elif len(named_children) == 1:
        mixin_name = "ChildHaving" + mixin_name + "Mixin"
    else:
        mixin_name = "ChildrenHaving" + mixin_name + "Mixin"

    if is_statement:
        mixin_name = "Statement" + mixin_name
    elif is_expression:
        pass
    else:
        mixin_name = "Module" + mixin_name

    return mixin_name


children_mixins = []

children_mixins_intentions = {}

children_mixing_setters_needed = {}


def addChildrenMixin(
    is_expression,
    is_statement,
    intended_for,
    named_children,
    named_children_types,
    named_children_checkers,
    auto_compute_handling=(),
    node_attributes=(),
):
    assert type(is_statement) is bool

    children_mixins.append(
        (
            is_expression,
            is_statement,
            named_children,
            named_children_types,
            named_children_checkers,
            auto_compute_handling,
            node_attributes,
        )
    )

    mixin_name = makeMixinName(
        is_expression,
        is_statement,
        named_children,
        named_children_types,
        named_children_checkers,
        auto_compute_handling,
        node_attributes,
    )

    if mixin_name not in children_mixins_intentions:
        children_mixins_intentions[mixin_name] = []
    if intended_for not in children_mixins_intentions[mixin_name]:
        children_mixins_intentions[mixin_name].append(intended_for)

    for named_child in named_children_types:
        assert named_child in named_children, named_child

    for named_child, named_child_checker in named_children_checkers.items():
        if named_child_checker == "convertNoneConstantToNone":
            assert named_children_types[named_child] == "optional"

    return mixin_name


def _parseNamedChildrenSpec(named_children):
    new_named_children = []

    setters_needed = set()
    named_children_types = {}
    named_children_checkers = {}

    for named_child_spec in named_children:
        if "|" in named_child_spec:
            named_child, named_child_properties = named_child_spec.split("|", 1)

            for named_child_property in named_child_properties.split("+"):
                if named_child_property == "setter":
                    setters_needed.add(named_child)
                elif named_child_property == "tuple":
                    named_children_types[named_child] = "tuple"
                elif named_child_property == "auto_none":
                    named_children_types[named_child] = "optional"
                    named_children_checkers[named_child] = "convertNoneConstantToNone"
                elif named_child_property == "auto_none_empty_str":
                    named_children_types[named_child] = "optional"
                    named_children_checkers[named_child] = (
                        "convertEmptyStrConstantToNone"
                    )
                elif named_child_property == "statements_or_none":
                    named_children_types[named_child] = "optional"
                    named_children_checkers[named_child] = (
                        "checkStatementsSequenceOrNone"
                    )
                elif named_child_property == "statements":
                    named_children_checkers[named_child] = "checkStatementsSequence"
                elif named_child_property == "optional":
                    named_children_types[named_child] = "optional"
                else:
                    assert False, named_child_property
        else:
            named_child = named_child_spec

        new_named_children.append(named_child)

    return (
        new_named_children,
        named_children_types,
        named_children_checkers,
        setters_needed,
    )


def _addFromNode(node_class):
    named_children = getattr(node_class, "named_children", ())
    # assert not hasattr(node_class, "named_child"), node_class

    if hasattr(node_class, "auto_compute_handling"):
        auto_compute_handling = frozenset(
            getattr(node_class, "auto_compute_handling").split(",")
        )
    else:
        auto_compute_handling = ()

    node_attributes = getattr(node_class, "node_attributes", ())

    if not named_children and not auto_compute_handling and not node_attributes:
        return

    (
        new_named_children,
        named_children_types,
        named_children_checkers,
        setters_needed,
    ) = _parseNamedChildrenSpec(named_children)

    mixin_name = makeMixinName(
        # TODO: Subject to dying, we now make this up on the fly.
        node_class.kind.startswith("EXPRESSION"),
        node_class.kind.startswith("STATEMENT"),
        tuple(new_named_children),
        named_children_types,
        named_children_checkers,
        auto_compute_handling,
        node_attributes,
    )

    if mixin_name not in children_mixing_setters_needed:
        children_mixing_setters_needed[mixin_name] = set()
    children_mixing_setters_needed[mixin_name].update(setters_needed)

    for base in node_class.__mro__:
        if base.__name__ == mixin_name:
            break
    else:
        # if named_children == ("operand",):
        print("Not done", node_class.__name__, named_children, mixin_name)

    addChildrenMixin(
        # TODO: Subject to dying, we now make this up on the fly.
        node_class.kind.startswith("EXPRESSION"),
        node_class.kind.startswith("STATEMENT"),
        node_class.__name__,
        tuple(new_named_children),
        named_children_types,
        named_children_checkers,
        auto_compute_handling,
        node_attributes,
    )


def addFromNodes():
    for node_class in NodeCheckMetaClass.kinds.values():
        # Find nodes with a make variant.
        if hasattr(sys.modules[node_class.__module__], "make" + node_class.__name__):
            node_factory_translations[node_class.__name__] = (
                "make" + node_class.__name__
            )

        _addFromNode(node_class)

    # Fake factories:
    node_factory_translations["ExpressionImportlibMetadataMetadataCall"] = (
        "makeExpressionImportlibMetadataMetadataCall"
    )
    node_factory_translations["ExpressionImportlibMetadataBackportMetadataCall"] = (
        "makeExpressionImportlibMetadataBackportMetadataCall"
    )
    node_factory_translations["ExpressionBuiltinsOpenCall"] = (
        "makeExpressionBuiltinsOpenCall"
    )
    node_factory_translations["ExpressionSysExitCall"] = "makeExpressionSysExitCall"


addFromNodes()


def makeChildrenHavingMixinNodes():
    # Complex stuff with many details due to 2 files and modes,
    # pylint: disable=too-many-locals,too-many-statements

    filename_python = "nuitka/nodes/ChildrenHavingMixins.py"
    filename_python2 = "nuitka/nodes/ExpressionBasesGenerated.py"
    filename_python3 = "nuitka/nodes/StatementBasesGenerated.py"

    template = getTemplate(
        package_name=__package__,
        template_subdir="templates_python",
        template_name="ChildrenHavingMixin.py.j2",
    )

    mixins_done = set()

    with withFileOpenedAndAutoFormatted(
        filename_python, ignore_errors=True
    ) as output_python, withFileOpenedAndAutoFormatted(
        filename_python2, ignore_errors=True
    ) as output_python2, withFileOpenedAndAutoFormatted(
        filename_python3, ignore_errors=True
    ) as output_python3:

        def emit1(*args):
            writeLine(output_python, *args)

        def emit2(*args):
            writeLine(output_python2, *args)

        def emit3(*args):
            writeLine(output_python3, *args)

        def emit(*args):
            emit1(*args)
            emit2(*args)
            emit3(*args)

        emitGenerationWarning(emit1, "Children having mixins", template.name)
        emitGenerationWarning(emit2, "Children having expression bases", template.name)
        emitGenerationWarning(emit3, "Children having statement bases", template.name)

        emit("# Loop unrolling over child names, pylint: disable=too-many-branches")

        emit1(
            """
from nuitka.nodes.Checkers import (
    checkStatementsSequenceOrNone,
    convertNoneConstantToNone,
    convertEmptyStrConstantToNone
)
"""
        )

        emit3(
            """
from nuitka.nodes.Checkers import (
    checkStatementsSequenceOrNone, \
    checkStatementsSequence,
    convertNoneConstantToNone
)
"""
        )

        for (
            is_expression,
            is_statement,
            named_children,
            named_children_types,
            named_children_checkers,
            auto_compute_handling,
            node_attributes,
        ) in sorted(
            children_mixins,
            key=lambda x: (x[0], x[1], x[2], x[3].items(), x[4].items()),
        ):
            mixin_name = makeMixinName(
                is_expression,
                is_statement,
                named_children,
                named_children_types,
                named_children_checkers,
                auto_compute_handling,
                node_attributes,
            )

            if mixin_name in mixins_done:
                continue

            intended_for = [
                value
                for value in children_mixins_intentions[mixin_name]
                if (
                    not value.endswith("Base")
                    or value.rstrip("Base")
                    not in children_mixins_intentions[mixin_name]
                )
            ]
            intended_for.sort()

            auto_compute_handling_set = set(auto_compute_handling)

            def pop(name):
                # only used inside of the loop, pylint: disable=cell-var-from-loop
                result = name in auto_compute_handling_set
                auto_compute_handling_set.discard(name)

                return result

            is_compute_final = pop("final")

            is_compute_final_children = pop("final_children")

            is_compute_no_raise = pop("no_raise")
            is_compute_raise = pop("raise")
            is_compute_raise_operation = pop("raise_operation")
            assert (
                is_compute_no_raise + is_compute_raise + is_compute_raise_operation < 2
            )

            if is_compute_raise:
                raise_mode = "raise"
            elif is_compute_no_raise:
                raise_mode = "no_raise"
            elif is_compute_raise_operation:
                raise_mode = "raise_operation"
            else:
                raise_mode = None

            is_compute_statement = pop("operation")
            has_post_node_init = pop("post_init")

            awaited_constant_attributes = OrderedSet(
                value.split(":", 1)[1]
                for value in auto_compute_handling_set
                if value.startswith("wait_constant:")
            )

            auto_compute_handling_set -= {
                "wait_constant:%s" % value for value in awaited_constant_attributes
            }

            assert not auto_compute_handling_set, auto_compute_handling_set

            code = template.render(
                name=template.name,
                is_expression=is_expression,
                is_statement=is_statement,
                mixin_name=mixin_name,
                named_children=named_children,
                named_children_types=named_children_types,
                named_children_checkers=named_children_checkers,
                children_mixing_setters_needed=sorted(
                    tuple(children_mixing_setters_needed.get(mixin_name, ()))
                ),
                intended_for=intended_for,
                is_compute_final=is_compute_final,
                is_compute_final_children=is_compute_final_children,
                raise_mode=raise_mode,
                is_compute_statement=is_compute_statement,
                awaited_constant_attributes=awaited_constant_attributes,
                has_post_node_init=has_post_node_init,
                node_attributes=node_attributes,
                len=len,
            )

            if is_statement:
                emit3(code)
            elif auto_compute_handling or node_attributes:
                emit2(code)
            else:
                emit1(code)

            mixins_done.add(mixin_name)


SpecVersion = namedtuple(
    "SpecVersion", ("spec_name", "python_criterion", "spec", "suffix")
)


def getSpecVersions(spec_module):
    result = {}

    for spec_name, spec in getSpecs(spec_module):
        for version, str_version in (
            (0x370, "37"),
            (0x380, "38"),
            (0x390, "39"),
            (0x3A0, "310"),
            (0x3B0, "311"),
            (0x3C0, "312"),
            (0x3D0, "313"),
        ):
            if "since_%s" % str_version in spec_name:
                python_criterion = ">= 0x%x" % version
                suffix = "Since%s" % str_version
                break

            if "before_%s" % str_version in spec_name:
                python_criterion = "< 0x%x" % version
                suffix = "Before%s" % str_version
                break
        else:
            python_criterion = None
            suffix = ""

        assert ".entry_points" not in spec_name or python_criterion is not None

        if spec.name not in result:
            result[spec.name] = []

        result[spec.name].append(SpecVersion(spec_name, python_criterion, spec, suffix))
        result[spec.name].sort(
            key=lambda spec_version: spec_version.python_criterion or "", reverse=True
        )

    return tuple(sorted(result.values()))


def makeHardImportNodes():
    # Too many details, pylint: disable=too-many-locals

    filename_python = "nuitka/nodes/HardImportNodesGenerated.py"

    template_ref_node = getTemplate(
        package_name=__package__,
        template_subdir="templates_python",
        template_name="HardImportReferenceNode.py.j2",
    )

    template_call_node = getTemplate(
        package_name=__package__,
        template_subdir="templates_python",
        template_name="HardImportCallNode.py.j2",
    )

    with withFileOpenedAndAutoFormatted(
        filename_python, ignore_errors=True
    ) as output_python:

        def emit(*args):
            writeLine(output_python, *args)

        emitGenerationWarning(emit, "Hard import nodes", template_ref_node.name)

        emit(
            """
hard_import_node_classes = {}

"""
        )

        for spec_descriptions in getSpecVersions(nuitka.specs.HardImportSpecs):
            spec = spec_descriptions[0][2]

            named_children_checkers = OrderedDict()

            module_name, function_name = spec.name.rsplit(".", 1)
            module_name_title = makeTitleCased(adaptModuleName(module_name))
            function_name_title = makeTitleCased(function_name)

            node_class_name = "Expression%s%s" % (
                module_name_title,
                function_name_title,
            )

            code = template_ref_node.render(
                name=template_ref_node.name,
                parameter_names_count=len(spec.getParameterNames()),
                function_name=function_name,
                function_name_title=function_name_title,
                function_name_code=makeCodeCased(function_name),
                module_name=module_name,
                module_name_code=makeCodeCased(adaptModuleName(module_name)),
                module_name_title=module_name_title,
                call_node_module_name=getCallModuleName(module_name, function_name),
                translateNodeClassName=translateNodeClassName,
                is_stdlib=module_name not in hard_modules_non_stdlib,
                specs=spec_descriptions,
            )

            emit(code)

            for spec_desc in spec_descriptions:
                spec = spec_desc.spec
                parameter_names = spec.getParameterNames2()

                named_children_types = {}
                if spec.name == "pkg_resources.require":
                    named_children_types["requirements"] = "tuple"

                if spec.getDefaultCount():
                    for optional_name in spec.getArgumentNames()[
                        -spec.getDefaultCount() :
                    ]:
                        assert optional_name not in named_children_types
                        named_children_types[optional_name] = "optional"

                if spec.getStarListArgumentName():
                    named_children_types[spec.getStarListArgumentName()] = "tuple"

                if spec.getStarDictArgumentName():
                    named_children_types[spec.getStarDictArgumentName()] = "tuple"

                for kw_only_name in spec.getKwOnlyParameterNames():
                    assert kw_only_name not in named_children_types
                    named_children_types[kw_only_name] = "optional"

                if parameter_names:
                    mixin_name = addChildrenMixin(
                        True,
                        False,
                        node_class_name,
                        parameter_names,
                        named_children_types,
                        named_children_checkers,
                    )
                else:
                    mixin_name = None

                extra_mixins = []

                result_shape = spec.getTypeShape()
                if result_shape is not None:
                    extra_mixins.append(_getMixinForShape(result_shape))

                code = template_call_node.render(
                    name=template_call_node.name,
                    mixin_name=mixin_name,
                    suffix=spec_desc.suffix,
                    python_criterion=spec_desc.python_criterion,
                    extra_mixins=extra_mixins,
                    parameter_names_count=len(spec.getParameterNames()),
                    named_children=parameter_names,
                    named_children_types=named_children_types,
                    argument_names=spec.getArgumentNames(),
                    star_list_argument_name=spec.getStarListArgumentName(),
                    star_dict_argument_name=spec.getStarDictArgumentName(),
                    function_name=function_name,
                    function_name_title=function_name_title,
                    function_name_code=makeCodeCased(function_name),
                    module_name=module_name,
                    is_stdlib_module=module_name
                    in (
                        "builtins",
                        "os",
                        "os.path",
                        "pkgutil",
                        "ctypes",
                        "importlib.metadata",
                        "importlib.resources",
                    ),
                    module_name_code=makeCodeCased(adaptModuleName(module_name)),
                    module_name_title=module_name_title,
                    call_node_module_name=getCallModuleName(module_name, function_name),
                    spec_name=spec_desc.spec_name,
                )

                emit(code)


def main():
    makeHardImportNodes()
    makeAttributeNodes()
    makeBuiltinOperationNodes()
    makeChildrenHavingMixinNodes()


#     Part of "Nuitka", an optimizing Python compiler that is compatible and
#     integrates with CPython, but also works on its own.
#
#     Licensed under the Apache License, Version 2.0 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#     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.
