{# loop over all functions and create types for each #}
{%- for name, function_data in functions.items() -%}
{# check if the function is overloaded#}
{% if function_data.has_overloading %}
{# go through signatures, create a class per signature #}
{% for i in range(function_data.signature_datas|length) %}
class {{contract_name}}{{function_data.capitalized_name}}ContractFunction{{i}}(ContractFunction):
    """ContractFunction for the {{function_data.name}} method."""

    def __call__(self{% if function_data.signature_datas[i].input_names_and_types %}, {{function_data.signature_datas[i].input_names_and_types|join(', ')}}{% endif %}) -> {{contract_name}}{{function_data.capitalized_name}}ContractFunction: #type: ignore
        super().__call__({% if function_data.signature_datas[i].input_names | length > 1%}{{function_data.signature_datas[i].input_names | wrap_with_dataclass_to_tuple | join(', ')}}{% endif %}) #type: ignore
        return cast({{contract_name}}{{function_data.capitalized_name}}ContractFunction, self)

    {% set output_names = function_data.signature_datas[i].outputs %}
    {% set output_types = function_data.signature_datas[i].output_types %}
    {% set return_type = output_types[0] if output_types|length == 1 else "ReturnValues" if output_types|length > 1 else "None" %}

    {% if output_types|length > 1%}
    class ReturnValues(NamedTuple):
        """The return named tuple for {{function_data.capitalized_name}}."""
        {% for j in range(output_types | length) -%}
            {{output_names[j]}}: {{output_types[j]}}
        {% endfor %}
    {% endif %}

    def call(
        self,
        transaction: TxParams | None = None,
        block_identifier: BlockIdentifier = "latest",
        state_override: CallOverride | None = None,
        ccip_read_enabled: bool | None = None,
    ) -> {{return_type}}:
        """returns {{return_type}}."""
        # Define the expected return types from the smart contract call
        {% if output_types|length == 1 %}
        return_types = {{return_type}}
        {% elif output_types|length > 1%}
        return_types = [{{output_types|join(', ')}}]
        {% endif %}
        # Call the function
        raw_values = super().call(transaction, block_identifier, state_override, ccip_read_enabled)
        {% if output_types|length == 1 %}
        return cast({{output_types[0]}}, rename_returned_types(structs, return_types, raw_values))
        {% elif output_types|length > 1%}
        return self.{{return_type}}(*rename_returned_types(structs, return_types, raw_values))
        {% endif %}
{% endfor %}
class {{contract_name}}{{function_data.capitalized_name}}ContractFunction(ContractFunction):
    """ContractFunction for the {{function_data.name}} method."""
    # super() call methods are generic, while our version adds values & types
    # pylint: disable=arguments-differ# disable this warning when there is overloading
    # pylint: disable=function-redefined

{# go through signatures, create a class per signature #}
{% for i in range(function_data.signature_datas|length) %}
    @overload
    def __call__(self{% if function_data.signature_datas[i].input_names_and_types %}, {{function_data.signature_datas[i].input_names_and_types|join(', ')}}{% endif %}) -> {{contract_name}}{{function_data.capitalized_name}}ContractFunction{{i}}:  # type: ignore
        ...
{% endfor %}

    def __call__(self, *args) -> {{contract_name}}{{function_data.capitalized_name}}ContractFunction:  # type: ignore
        clone = super().__call__(*(dataclass_to_tuple(arg) for arg in args))
        self.kwargs = clone.kwargs
        self.args = clone.args
        return self  # type: ignore
{% else %} {# no overloading #}
class {{contract_name}}{{function_data.capitalized_name}}ContractFunction(ContractFunction):
    """ContractFunction for the {{function_data.name}} method."""

    {% set signature_data = function_data.signature_datas[0] %}
    {% set output_names = signature_data.outputs %}
    {% set output_types = signature_data.output_types %}
    {% set return_type = output_types[0] if output_types|length == 1 else "ReturnValues" if output_types|length > 1 else "None" %}
    {% if output_types|length > 1%}
    class ReturnValues(NamedTuple):
        """The return named tuple for {{function_data.capitalized_name}}."""
        {% for j in range(output_types | length) -%}
            {{output_names[j]}}: {{output_types[j]}}
        {% endfor %}
    {% endif %}

    def __call__(self{% if signature_data.input_names_and_types %}, {{signature_data.input_names_and_types|join(', ')}}{% endif %}) -> {{contract_name}}{{function_data.capitalized_name}}ContractFunction: #type: ignore
        clone = super().__call__({{signature_data.input_names | wrap_with_dataclass_to_tuple | join(', ')}})
        self.kwargs = clone.kwargs
        self.args = clone.args
        return self

    def call(
        self,
        transaction: TxParams | None = None,
        block_identifier: BlockIdentifier = "latest",
        state_override: CallOverride | None = None,
        ccip_read_enabled: bool | None = None,
    ) -> {{return_type}}:
        """returns {{return_type}}."""
        # Define the expected return types from the smart contract call
        {% if output_types|length == 1 %}
        return_types = {{return_type}}
        {% elif output_types|length > 1%}
        return_types = [{{output_types|join(', ')}}]
        {% endif %}
        # Call the function
        {% if output_types|length == 1 %}
        raw_values = super().call(transaction, block_identifier, state_override, ccip_read_enabled)
        return cast({{output_types[0]}}, rename_returned_types(structs, return_types, raw_values))
        {% elif output_types|length > 1%}
        raw_values = super().call(transaction, block_identifier, state_override, ccip_read_enabled)
        return self.{{return_type}}(*rename_returned_types(structs, return_types, raw_values))
        {% endif %}
{% endif %} {# if function_data.has_overloading #}

{% endfor %}

class {{contract_name}}ContractFunctions(ContractFunctions):
    """ContractFunctions for the {{contract_name}} contract."""
{% for function in functions.values() %}
    {{function.name}}: {{contract_name}}{{function.capitalized_name}}ContractFunction
{% endfor %}
    def __init__(
         self,
         abi: ABI,
         w3: "Web3",
         address: ChecksumAddress | None = None,
         decode_tuples: bool | None = False,
     ) -> None:
         super().__init__(abi, w3, address, decode_tuples)
         {% for function in functions.values() -%}
         self.{{function.name}} = {{contract_name}}{{function.capitalized_name}}ContractFunction.factory(
             "{{function.name}}",
             w3=w3,
             contract_abi=abi,
             address=address,
             decode_tuples=decode_tuples,
             function_identifier="{{function.name}}",
         )
         {% endfor %}
