"""
Project Euler Problem 092: https://projecteuler.net/problem=92
Square digit chains
A number chain is created by continuously adding the square of the digits in
a number to form a new number until it has been seen before.
For example,
44 → 32 → 13 → 10 → 1 → 1
85 → 89 → 145 → 42 → 20 → 4 → 16 → 37 → 58 → 89
Therefore any chain that arrives at 1 or 89 will become stuck in an endless loop.
What is most amazing is that EVERY starting number will eventually arrive at 1 or 89.
How many starting numbers below ten million will arrive at 89?
"""


DIGITS_SQUARED = [digit**2 for digit in range(10)]


def next_number(number: int) -> int:
    """
    Returns the next number of the chain by adding the square of each digit
    to form a new number.
    For example, if number = 12, next_number() will return 1^2 + 2^2 = 5.
    Therefore, 5 is the next number of the chain.
    >>> next_number(44)
    32
    >>> next_number(10)
    1
    >>> next_number(32)
    13
    """
    sum_of_digits_squared = 0
    while number:
        sum_of_digits_squared += DIGITS_SQUARED[number % 10]
        number //= 10

    return sum_of_digits_squared


CHAINS = {1: True, 58: False}


def chain(number: int) -> bool:
    """
    The function generates the chain of numbers until the next number is 1 or 89.
    For example, if starting number is 44, then the function generates the
    following chain of numbers:
    44 → 32 → 13 → 10 → 1 → 1.
    Once the next number generated is 1 or 89, the function returns whether
    or not the next number generated by next_number() is 1.
    >>> chain(10)
    True
    >>> chain(58)
    False
    >>> chain(1)
    True
    """
    if number in CHAINS:
        return CHAINS[number]

    number_chain = chain(next_number(number))
    CHAINS[number] = number_chain

    return number_chain


def solution(number: int = 10000000) -> int:
    """
    The function returns the number of integers that end up being 89 in each chain.
    The function accepts a range number and the function checks all the values
    under value number.

    >>> solution(100)
    80
    >>> solution(10000000)
    8581146
    """
    return sum(1 for i in range(1, number) if not chain(i))


if __name__ == "__main__":
    import doctest

    doctest.testmod()

    print(f"{solution() = }")
