"""A web3.py Contract class for the {{contract_name}} contract."""

# contracts have PascalCase names
# pylint: disable=invalid-name

# contracts control how many attributes and arguments we have in generated code
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-arguments

# we don't need else statement if the other conditionals all have return,
# but it's easier to generate
# pylint: disable=no-else-return

# This file is bound to get very long depending on contract sizes.
# pylint: disable=too-many-lines

from __future__ import annotations
from typing import cast

from eth_typing import ChecksumAddress
from web3.types import ABI
from web3.contract.contract import Contract, ContractFunction, ContractFunctions
from web3.exceptions import FallbackNotFound

{# loop over all functions and create types for each #}
{% for function in functions %}
class {{contract_name}}{{function.capitalized_name}}ContractFunction(ContractFunction):
    """ContractFunction for the {{function.name}} method."""
    # super() call methods are generic, while our version adds values & types
    # pylint: disable=arguments-differ
    {# functions with multiple signatures have a more complicated logic #}
    {%- if function.input_names_and_types|length > 1 %}
    def __call__(self, {{function.required_input_names_and_types|join(', ')}}, {{function.optional_input_names_and_types|join(', ')}}) -> "{{contract_name}}{{function.capitalized_name}}ContractFunction":
        {# loop over the signatures for this function #}
        {% for signature_name in function.input_names %}
        {# determine what type of python condition this will be #}
        {% if loop.first %}
        {% set conditional_string = 'if' %}
        {% elif loop.last %}
        {% set conditional_string = 'else' %}
        {% else %}
        {% set conditional_string = 'elif' %}
        {% endif %}
        {# python "if" or "elif" condition #}
        {% if conditional_string != 'else' %}
        {# get the optional names that are in in this signature #}
        {%- set optional_names_in_this_signature = signature_name|select('in', function.optional_input_names)|list %}
        {# does this signature have no optional names? #}
        {% if optional_names_in_this_signature|length == 0 %}
        {# py: [if] all optional args are None, execute this signature #}
        {{conditional_string}} all([{{function.optional_input_names|join(' is None, ')}} is None]):
        {# does this signature have optional names? #}
        {% else %}
        {# get the optional names that are not in this signature #}
        {%- set optional_names_not_in_this_signature = function.optional_input_names|reject('in', signature_name)|list %}
        {# does this signature have all of the optional names? #}
        {% if  optional_names_not_in_this_signature|length == 0 %}
        {# py: [if] all optional args are not None, execute this signature #}
        {{conditional_string}} all([{{optional_names_in_this_signature|join(' is not None, ')}} is not None]):
        {# are there optional names not in this signature? #}
        {% else %}
        {# py: [if] all optional signature args are not None and others are None, execute this signature #}
        {{conditional_string}} (
            all([{{optional_names_in_this_signature|join(' is not None, ')}} is not None])
            and
            all([{{optional_names_not_in_this_signature|join(' is None, ')}} is None])
        ):
        {% endif %}
        {% endif %}
        {# if we are the "else" python condition, don't check any values in python #}
        {% else %}
        {{conditional_string}}:
        {% endif %}
            super().__call__({{signature_names|join(', ')}})
            return self
        {% endfor %}
    {# for functions with only one signature, just make the super call #}
    {% else %}
    def __call__(self, {{function.input_names_and_types[0]|join(', ')}}) -> "{{contract_name}}{{function.capitalized_name}}ContractFunction":
        super().__call__({{function.input_names[0]|join(', ')}})
        return self
    {% endif %}

    # TODO: add call def so we can get return types for the calls
    # def call()
{% endfor %}


class {{contract_name}}ContractFunctions(ContractFunctions):
    """ContractFunctions for the {{contract_name}} contract."""
{% for function in functions %}
    {{function.name}}: {{contract_name}}{{function.capitalized_name}}ContractFunction
{% endfor %}

{{contract_name | lower}}_abi: ABI = cast(ABI, {{abi}})

class {{contract_name}}Contract(Contract):
    """A web3.py Contract class for the {{contract_name}} contract."""

    abi: ABI = {{contract_name | lower}}_abi

    def __init__(self, address: ChecksumAddress | None = None) -> None:
        try:
            # Initialize parent Contract class
            super().__init__(address=address)

        except FallbackNotFound:
            print("Fallback function not found. Continuing...")

    # TODO: add events
    # events: ERC20ContractEvents

    functions: {{contract_name}}ContractFunctions
