import re
from argparse import _HelpAction, _SubParsersAction


class NavigationError(Exception):
    pass


def parser_navigate(parser_result, path, current_path=None):
    if isinstance(path, str):
        if path == "":
            return parser_result
        path = re.split(r"\s+", path)
    current_path = current_path or []
    if len(path) == 0:
        return parser_result
    if "children" not in parser_result:
        raise NavigationError(
            "Current parser have no children elements.  (path: {})".format(
                " ".join(current_path),
            ),
        )
    next_hop = path.pop(0)
    for child in parser_result["children"]:
        # identifer is only used for aliased subcommands
        identifier = child["identifier"] if "identifier" in child else child["name"]
        if identifier == next_hop:
            current_path.append(next_hop)
            return parser_navigate(child, path, current_path)
    raise NavigationError(
        f"Current parser have no children element with name: {next_hop}  (path: %s)"
        % " ".join(current_path),
    )


def _try_add_parser_attribute(data, parser, attribname):
    attribval = getattr(parser, attribname, None)
    if attribval is None:
        return
    if not isinstance(attribval, str):
        return
    if len(attribval) > 0:
        data[attribname] = attribval


def _format_usage_without_prefix(parser):
    """
    Use private argparse APIs to get the usage string without
    the 'usage: ' prefix.
    """
    fmt = parser._get_formatter()
    fmt.add_usage(
        parser.usage,
        parser._actions,
        parser._mutually_exclusive_groups,
        prefix="",
    )
    return fmt.format_help().strip()


def parse_parser(parser, data=None, **kwargs):
    if data is None:
        data = {
            "name": "",
            "usage": parser.format_usage().strip(),
            "bare_usage": _format_usage_without_prefix(parser),
            "prog": parser.prog,
        }
    _try_add_parser_attribute(data, parser, "description")
    _try_add_parser_attribute(data, parser, "epilog")
    for action in parser._get_positional_actions():
        if isinstance(action, _HelpAction):
            continue
        if isinstance(action, _SubParsersAction):
            helps = {}
            for item in action._choices_actions:
                helps[item.dest] = item.help

            # commands which share an existing parser are an alias,
            # don't duplicate docs
            subsection_alias = {}
            subsection_alias_names = set()
            for name, subaction in action._name_parser_map.items():
                if subaction not in subsection_alias:
                    subsection_alias[subaction] = []
                else:
                    subsection_alias[subaction].append(name)
                    subsection_alias_names.add(name)

            for name, subaction in action._name_parser_map.items():
                if name in subsection_alias_names:
                    continue
                subalias = subsection_alias[subaction]
                subaction.prog = f"{parser.prog} {name}"
                subdata = {
                    "name": name
                    if not subalias
                    else "{} ({})".format(name, ", ".join(subalias)),
                    "help": helps.get(name, ""),
                    "usage": subaction.format_usage().strip(),
                    "bare_usage": _format_usage_without_prefix(subaction),
                }
                if subalias:
                    subdata["identifier"] = name
                parse_parser(subaction, subdata, **kwargs)
                data.setdefault("children", []).append(subdata)
            continue
        if "args" not in data:
            data["args"] = []
        arg = {
            "name": action.dest,
            "help": action.help or "",
            "metavar": action.metavar,
        }
        if action.choices:
            arg["choices"] = action.choices
        data["args"].append(arg)
    show_defaults = ("skip_default_values" not in kwargs) or (
        kwargs["skip_default_values"] is False
    )
    for action in parser._get_optional_actions():
        if isinstance(action, _HelpAction):
            continue
        if "options" not in data:
            data["options"] = []
        option = {
            "name": action.option_strings,
            "default": action.default if show_defaults else "==SUPPRESS==",
            "help": action.help or "",
        }
        if action.choices:
            option["choices"] = action.choices
        if "==SUPPRESS==" not in option["help"]:
            data["options"].append(option)
    return data
