import re
from types import FrameType

from .. import info_variables, token_utils, utils
from ..core import TracebackData
from ..ft_gettext import current_lang
from ..typing_info import CauseInfo

parser = utils.RuntimeMessageParser()
_ = current_lang.translate


@parser.add
def container_changed_size_during_iteration(
    message: str, frame: FrameType, tb_data: TracebackData
) -> CauseInfo:
    pattern = re.compile(r"(.*) changed size during iteration")
    match = re.search(pattern, message)
    if not match:
        return {}
    container_name = match.group(1).lower()
    if container_name.startswith("dict"):
        container_name = "dict"
        obj_type = dict
    elif container_name == "set":
        obj_type = set
    else:
        return {}
    container_type = info_variables.convert_type(container_name)

    objects = info_variables.get_all_objects(tb_data.bad_line, frame)
    names = []
    for name, obj in objects["name, obj"]:
        if isinstance(obj, obj_type):
            names.append(name)

    tokens = token_utils.tokenize(tb_data.bad_line)
    loop_keywords = []
    for tok in tokens:
        if tok.string in {"for", "while"}:
            loop_keywords.append(tok.string)

    loop_keywords = set(loop_keywords)
    if len(loop_keywords) == 1:
        for_while = "for" if "for" in loop_keywords else "while"
    else:
        for_while = "for/while"

    if len(names) == 1:
        cause = _(
            "While you were iterating over the items of `{name}` ({container_type})\n"
            "in a `{for_while}` loop, you either tried to add or remove items from it.\n"
            "Suggestion: start by making a copy of `{name}` and iterate over the items\n"
            "of that copy if you want to change `{name}` inside a loop.\n"
            "You might want to do this as follows:\n\n"
            "    my_{name} = {name}.copy()\n"
            "    for item in my_{name}:\n"
            "        # Change {name}\n"
        ).format(name=names[0], container_type=container_type, for_while=for_while)
    else:
        cause = _(
            "While you were iterating over the items of a container ({container_type})\n"
            "in a `{for_while}` loop, you either tried to add or remove items from it.\n"
            "Suggestion: start by making a copy of the container and iterate over the items\n"
            "of that copy if you want to change that container inside a loop.\n"
        ).format(container_type=container_type, for_while=for_while)

    return {"cause": cause}
