from __future__ import annotations

from typing import Any, Iterable, Mapping, MutableMapping, Sequence


def parse_args(
    args: Sequence[Any],
    ArgStruct: MutableMapping[str, Any],
    FlagTypeParams: Iterable[str] | None = None,
    Aliases: Mapping[str, Sequence[str]] | None = None,
) -> MutableMapping[str, Any]:
    # Helper function for parsing varargin.
    #
    #
    # ArgStruct=parseArgs(varargin,ArgStruct[,FlagtypeParams[,Aliases]])
    #
    # * ArgStruct is the structure full of named arguments with default values.
    # * Flagtype params is params that don't require a value. (the value will be set to 1 if it is present)
    # * Aliases can be used to map one argument-name to several argstruct fields
    #
    #
    # example usage:
    # --------------
    # def parseargtest(*varargin):
    #
    #     # define the acceptable named arguments and assign default values
    #     Args = {
    #         'Holdaxis': 0,
    #         'SpacingVertical': 0.05, 'SpacingHorizontal': 0.05,
    #         'PaddingLeft': 0, 'PaddingRight': 0,
    #         'PaddingTop': 0, 'PaddingBottom': 0,
    #         'MarginLeft': 0.1, 'MarginRight': 0.1,
    #         'MarginTop': 0.1, 'MarginBottom': 0.1,
    #         'rows': [], 'cols': [],
    #     }
    #
    #     # The capital letters define abbreviations.
    #     # E.g. parseargtest('spacingvertical',0) is equivalent to parseargtest('sv',0)
    #
    #     Args = parse_args(
    #         varargin, Args,                         # fill the arg-struct with values entered by the user
    #         {'Holdaxis'},                          # this argument has no value (flag-type)
    #         {'Spacing': ('sh','sv'),
    #          'Padding': ('pl','pr','pt','pb'),
    #          'Margin': ('ml','mr','mt','mb')},
    #     )
    #
    #     print(Args)
    #
    #
    #
    #
    # Aslak Grinsted 2003

    aliases = dict(Aliases or {})
    flag_type_params = {str(name).lower() for name in (FlagTypeParams or [])}

    # ---------------Get "numeric" arguments
    NumArgCount = 0
    while NumArgCount < len(args) and not isinstance(args[NumArgCount], str):
        NumArgCount += 1
    ArgStruct["NumericArguments"] = list(args[:NumArgCount])

    # --------------Make an accepted fieldname matrix (case insensitive)
    Fnames = [name for name in ArgStruct.keys() if name != "NumericArguments"]
    full_names = {name.lower(): name for name in Fnames}
    abbreviations: dict[str, str] = {}
    for field_name in Fnames:
        abbreviation = "".join(ch.lower() for ch in field_name if ch.isupper())
        if abbreviation:
            abbreviations[abbreviation] = field_name

    alias_targets: dict[str, list[str]] = {}
    alias_abbreviations: dict[str, list[str]] = {}
    for alias_name, target_abbreviations in aliases.items():
        targets: list[str] = []
        for abbreviation in target_abbreviations:
            key = abbreviation.lower()
            if key in abbreviations:
                targets.append(abbreviations[key])
        alias_targets[alias_name.lower()] = targets
        alias_abbreviation = "".join(ch.lower() for ch in alias_name if ch.isupper())
        if alias_abbreviation:
            alias_abbreviations[alias_abbreviation] = targets

    def resolve_fields(name: str) -> list[str]:
        lowered = name.lower()
        if lowered in abbreviations:
            return [abbreviations[lowered]]
        if lowered in alias_abbreviations:
            return alias_abbreviations[lowered]
        if lowered in alias_targets:
            return alias_targets[lowered]

        matches = [original for full, original in full_names.items() if full.startswith(lowered)]
        alias_matches = [targets for alias, targets in alias_targets.items() if alias.startswith(lowered)]
        for targets in alias_matches:
            matches.extend(targets)
        if not matches:
            raise ValueError(f"Unknown named parameter: {name}")
        # Shortest fieldname should win.
        shortest = min(len(field) for field in matches)
        return [field for field in matches if len(field) == shortest]

    # --------------get parameters--------------------
    l = NumArgCount
    while l < len(args):
        a = args[l]
        if not isinstance(a, str):
            raise ValueError(f"Expected a named parameter: {a}")
        fields = resolve_fields(a)
        first_field = fields[0]
        parameter_has_value = first_field.lower() not in flag_type_params
        if parameter_has_value:
            if l + 1 >= len(args):
                raise ValueError(f"Missing value for named parameter: {a}")
            val = args[l + 1]
        else:
            val = 1
        for cur_field in fields:
            ArgStruct[cur_field] = val
        l += 2 if parameter_has_value else 1

    return ArgStruct


# MATLAB-compatible name.
parseArgs = parse_args
