import logging

import numpy as np
import pandas as pd

logger = logging.getLogger(__name__)


def _normalize_index(indexer, index, as_idx=True):
    # work with ints, convert at the end
    # if single str, get idx and put to list
    # TODO: can be an issue if any of the indices is named 'all'..
    if indexer is None:
        raise IndexError("None index.")
    if isinstance(indexer, str):
        indexer = range(len(index)) if indexer == "all" else [index.get_loc(indexer)]
    # if single integer, put to list
    if isinstance(indexer, (np.integer, int)):
        indexer = [indexer]
    # work with np array
    indexer = np.array(indexer)
    # if empty
    if len(indexer) == 0:
        raise IndexError(f"Empty index, `{indexer}`.")
    # if mask, get indices where True
    if isinstance(indexer[0], (bool, np.bool_)):
        indexer = np.where(indexer)[0]
    # if all False from previous boolean mask
    if len(indexer) == 0:
        raise IndexError(f"Empty index, `{indexer}`.")
    # if str, get indices where names match
    if isinstance(indexer[0], (str, np.str_)):
        _indexer = index.get_indexer(indexer)
        bad_idx = [idx for _idx, idx in zip(_indexer, indexer) if _idx == -1]
        if len(bad_idx) > 0:
            logger.warning(f"Invalid index, `{bad_idx}`, removing...")
        indexer = [_idx for _idx in _indexer if _idx != -1]
    # if all bad indices
    if len(indexer) == 0:
        raise IndexError(f"Empty index, `{indexer}`.")
    if isinstance(indexer[0], (int, np.integer)):
        if as_idx:
            return indexer
        return index[indexer]
    raise IndexError(f"Invalid index, `{indexer}`.")


# as implemented in https://github.com/scverse/anndata/blob/main/anndata/utils.py#L211
def _make_index_unique(index: pd.Index, join: str = "_"):
    """
    Makes the index unique by appending a number string to each duplicate index element:
    '1', '2', etc.

    If a tentative name created by the algorithm already exists in the index, it tries
    the next integer in the sequence.

    The first occurrence of a non-unique value is ignored.

    Parameters
    ----------
    join
         The connecting string between name and integer.

    Examples
    --------
    >>> from anndata import AnnData
    >>> adata = AnnData(np.ones((2, 3)), var=pd.DataFrame(index=["a", "a", "b"]))
    >>> adata.var_names
    Index(['a', 'a', 'b'], dtype='object')
    >>> adata.var_names_make_unique()
    >>> adata.var_names
    Index(['a', 'a_1', 'b'], dtype='object')
    """
    if index.is_unique:
        return index
    from collections import Counter

    values = index.values.copy()
    indices_dup = index.duplicated(keep="first")
    values_dup = values[indices_dup]
    values_set = set(values)
    counter = Counter()
    issue_interpretation_warning = False
    example_colliding_values = []
    for i, v in enumerate(values_dup):
        while True:
            counter[v] += 1
            tentative_new_name = v + join + str(counter[v])
            if tentative_new_name not in values_set:
                values_set.add(tentative_new_name)
                values_dup[i] = tentative_new_name
                break
            issue_interpretation_warning = True
            if len(example_colliding_values) < 5:
                example_colliding_values.append(tentative_new_name)

    if issue_interpretation_warning:
        logger.warning(
            f"Suffix used ({join}[0-9]+) to deduplicate index values may make index "
            + "values difficult to interpret. There values with a similar suffixes in "
            + "the index. Consider using a different delimiter by passing "
            + "`join={delimiter}`"
            + "Example key collisions generated by the _make_index_unique algorithm: "
            + str(example_colliding_values)
        )
    values[indices_dup] = values_dup
    index = pd.Index(values, name=index.name)
    return index
