from typing import Optional
from typing import List
from typing import Tuple


def is_valid_atom_index(index: int,
                        coordinates: Optional[dict] = None,
                        existing_indices: Optional[List[int]] = None,
                        ) -> Tuple[bool, str]:
    """
    Check whether an atom index is valid:
    1. it is not 0
    2. it is not in the existing indices list (if ``existing_indices`` is not ``None``)
    2. it is not higher than the total number of atoms

    Args:
        index (int): The atom index to be checked.
        existing_indices (list, optional): Entries are pre-checked atom indices.
        coordinates (dict, optional): The 3d coordinates from which the total number of atoms is deduced.

    Returns:
        Tuple[bool, str]:
            - Whether the atom index is valid.
            - A reason for invalidating the argument.
    """
    if index == 0:
        return False, 'A 1-indexed atom index cannot be zero.'
    if coordinates is not None and index > len(coordinates['symbols']):
        return False, f'An atom index {index} cannot be greater than the number of atoms {len(coordinates["symbols"])}.'
    if existing_indices is not None and index in existing_indices:
        return False, f'Atom index {index} appears more than once in this argument.'
    return True, ''