from typing import Dict, Optional, Union, List, Hashable, Any


def getsert(
    data: Dict[Hashable, Any], key: Hashable, default: Any = None
) -> Any:
    """Implements defaultdict feature."""

    if isinstance(data, Dict):

        if key not in data:

            # Do not insert default value into data if it's None.
            if default is None:
                return default
            dict.__setitem__(data, key, default)

        return dict.__getitem__(data, key)

    else:

        return default


def get(
    data: Union[Dict[Hashable, Any], List[Any]],
    address: Hashable,
    default: Any = None,
    sep: str = ".",
) -> Any:

    # If key is a string, then try to split it if it contains the separator
    head: Hashable = address
    tail: Optional[Hashable] = None

    if isinstance(address, str) and sep and sep in address:
        # "my.0.key"
        head, tail = address.split(sep, maxsplit=1)

    if isinstance(data, Dict):
        if address in data:
            return dict.__getitem__(data, address)

        v = getsert(data, head, default=default)

        if tail and isinstance(v, (Dict, List)):
            return get(v, tail, sep=sep, default=default)

        # Return whatever got generated by "getsert" otherwise
        return v

    if isinstance(data, List):
        idx: Optional[int] = None

        # If key is string
        if isinstance(head, str):
            try:
                idx = int(head)
            except ValueError:
                return None
        # isinstance check satisfies mypy and type check makes sure we don't accidentally
        # cast hashable boolean as an int.
        elif isinstance(head, int) and type(head) == int:
            idx = head
        else:
            return None

        if idx >= len(data) or idx < 0 and abs(idx) > len(data):
            return None

        if tail and isinstance(data[idx], (Dict, List)):
            return get(data[idx], tail, sep=sep, default=default)

        return data[idx]

    return default
