#     Copyright 2025, QutaYba, nasr2python@gmail.com find license text at end of file


""" Reformulation of dictionary creations.

Dictionary creations might be directly translated to constants, or they might
become nodes that build dictionaries.

For Python3.5, unpacking can happen while creating dictionaries, these are
being re-formulated to an internal function.

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

"""

from qutayba.nodes.AttributeNodes import makeExpressionAttributeLookup
from qutayba.nodes.BuiltinIteratorNodes import ExpressionBuiltinIter1
from qutayba.nodes.BuiltinNextNodes import ExpressionBuiltinNext1
from qutayba.nodes.ConstantRefNodes import makeConstantRefNode
from qutayba.nodes.ContainerMakingNodes import makeExpressionMakeTuple
from qutayba.nodes.DictionaryNodes import (
    StatementDictOperationUpdate,
    makeExpressionMakeDict,
    makeExpressionMakeDictOrConstant,
)
from qutayba.nodes.ExceptionNodes import (
    ExpressionBuiltinMakeException,
    StatementRaiseException,
)
from qutayba.nodes.FunctionNodes import (
    ExpressionFunctionRef,
    makeExpressionFunctionCall,
    makeExpressionFunctionCreation,
)
from qutayba.nodes.KeyValuePairNodes import (
    makeExpressionKeyValuePair,
    makeExpressionKeyValuePairConstantKey,
    makeExpressionPairs,
)
from qutayba.nodes.LoopNodes import StatementLoop, StatementLoopBreak
from qutayba.nodes.OperatorNodes import makeBinaryOperationNode
from qutayba.nodes.ReturnNodes import StatementReturn
from qutayba.nodes.TypeNodes import ExpressionBuiltinType1
from qutayba.nodes.VariableAssignNodes import makeStatementAssignmentVariable
from qutayba.nodes.VariableRefNodes import (
    ExpressionTempVariableRef,
    ExpressionVariableRef,
)
from qutayba.PythonVersions import python_version
from qutayba.specs.ParameterSpecs import ParameterSpec

from .InternalModule import (
    internal_source_ref,
    makeInternalHelperFunctionBody,
    once_decorator,
)
from .ReformulationTryExceptStatements import makeTryExceptSingleHandlerNode
from .ReformulationTryFinallyStatements import makeTryFinallyReleaseStatement
from .TreeHelpers import (
    buildNode,
    buildNodeTuple,
    makeStatementsSequenceFromStatement,
    makeStatementsSequenceFromStatements,
)


def buildDictionaryNode(provider, node, source_ref):
    if python_version >= 0x350:
        for key in node.keys:
            if key is None:
                return buildDictionaryUnpacking(
                    provider=provider,
                    keys=node.keys,
                    values=node.values,
                    source_ref=source_ref,
                )

    return makeExpressionMakeDictOrConstant(
        pairs=makeExpressionPairs(
            keys=buildNodeTuple(provider, node.keys, source_ref),
            values=buildNodeTuple(provider, node.values, source_ref),
        ),
        user_provided=True,
        source_ref=source_ref,
    )


@once_decorator
def _getDictUnpackingHelper():
    helper_name = "_unpack_dict"

    result = makeInternalHelperFunctionBody(
        name=helper_name,
        parameters=ParameterSpec(
            ps_name=helper_name,
            ps_normal_args=(),
            ps_list_star_arg="args",
            ps_dict_star_arg=None,
            ps_default_count=0,
            ps_kw_only_args=(),
            ps_pos_only_args=(),
        ),
    )

    temp_scope = None

    tmp_result_variable = result.allocateTempVariable(
        temp_scope, "dict", temp_type="object"
    )
    tmp_iter_variable = result.allocateTempVariable(
        temp_scope, "iter", temp_type="object"
    )
    tmp_item_variable = result.allocateTempVariable(
        temp_scope, "keys", temp_type="object"
    )

    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,
        ),
        makeTryExceptSingleHandlerNode(
            tried=StatementDictOperationUpdate(
                dict_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,
            ),
            exception_name="AttributeError",
            handler_body=StatementRaiseException(
                exception_type=ExpressionBuiltinMakeException(
                    exception_name="TypeError",
                    args=(
                        makeBinaryOperationNode(
                            operator="Mod",
                            left=makeConstantRefNode(
                                constant="""\
'%s' object is not a mapping""",
                                source_ref=internal_source_ref,
                                user_provided=True,
                            ),
                            right=makeExpressionMakeTuple(
                                elements=(
                                    makeExpressionAttributeLookup(
                                        expression=ExpressionBuiltinType1(
                                            value=ExpressionTempVariableRef(
                                                variable=tmp_item_variable,
                                                source_ref=internal_source_ref,
                                            ),
                                            source_ref=internal_source_ref,
                                        ),
                                        attribute_name="__name__",
                                        source_ref=internal_source_ref,
                                    ),
                                ),
                                source_ref=internal_source_ref,
                            ),
                            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,
            ),
            source_ref=internal_source_ref,
        ),
    )

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

    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=ExpressionTempVariableRef(
                variable=tmp_result_variable, source_ref=internal_source_ref
            ),
            source_ref=internal_source_ref,
        ),
    )

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

    return result


def buildDictionaryUnpackingArgs(provider, keys, values, source_ref):
    result = []

    for key, value in zip(keys, values):
        # TODO: We could be a lot cleverer about the dictionaries for non-starred
        # arguments, but lets get this to work first.
        if key is None:
            result.append(buildNode(provider, value, source_ref))
        elif type(key) is str:
            result.append(
                makeExpressionMakeDict(
                    pairs=(
                        makeExpressionKeyValuePairConstantKey(
                            key=key,
                            value=buildNode(provider, value, source_ref),
                        ),
                    ),
                    source_ref=source_ref,
                )
            )
        else:
            result.append(
                makeExpressionMakeDict(
                    pairs=(
                        makeExpressionKeyValuePair(
                            key=buildNode(provider, key, source_ref),
                            value=buildNode(provider, value, source_ref),
                        ),
                    ),
                    source_ref=source_ref,
                )
            )

    return tuple(result)


def buildDictionaryUnpacking(provider, keys, values, source_ref):
    helper_args = buildDictionaryUnpackingArgs(provider, keys, values, source_ref)

    result = makeExpressionFunctionCall(
        function=makeExpressionFunctionCreation(
            function_ref=ExpressionFunctionRef(
                function_body=_getDictUnpackingHelper(), source_ref=source_ref
            ),
            defaults=(),
            kw_defaults=None,
            annotations=None,
            source_ref=source_ref,
        ),
        values=(makeExpressionMakeTuple(helper_args, source_ref),),
        source_ref=source_ref,
    )

    result.setCompatibleSourceReference(helper_args[-1].getCompatibleSourceReference())

    return result



