from __future__ import annotations

# 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:
# --------------
# function parseargtest(varargin)
#
# #define the acceptable named arguments and assign default values
# Args=struct('Holdaxis',0, ...
#        'SpacingVertical',0.05,'SpacingHorizontal',0.05, ...
#        'PaddingLeft',0,'PaddingRight',0,'PaddingTop',0,'PaddingBottom',0, ...
#        'MarginLeft',.1,'MarginRight',.1,'MarginTop',.1,'MarginBottom',.1, ...
#        'rows',[],'cols',[]);
#
# #The capital letters define abrreviations.
# #  Eg. parseargtest('spacingvertical',0) is equivalent to  parseargtest('sv',0)
#
# Args=parseArgs(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'}});
#
# disp(Args)
#
#
#
#
# # Aslak Grinsted 2003

from typing import Any, Iterable


def _abbreviation(name: str) -> str:
    return "".join(character.lower() for character in name if character.isupper())


def parseArgs(
    args: Iterable[Any],
    ArgStruct: dict[str, Any],
    FlagTypeParams: Iterable[str] | None = None,
    Aliases: dict[str, Iterable[str]] | None = None,
) -> dict[str, Any]:
    """Python equivalent of the original case-insensitive MATLAB argument parser."""
    args = list(args)
    result = dict(ArgStruct)
    flag_names = {name.lower() for name in (FlagTypeParams or [])}
    aliases = {name.lower(): list(fields) for name, fields in (Aliases or {}).items()}

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

    # --------------Make an accepted fieldname matrix (case insensitive)
    field_names = [name for name in result if name != "NumericArguments"]
    lower_to_field = {name.lower(): name for name in field_names}
    abbreviation_to_field = {
        _abbreviation(name): name for name in field_names if _abbreviation(name)
    }

    # --------------get parameters--------------------
    l = NumArgCount
    while l < len(args):
        a = args[l]
        if not isinstance(a, str):
            raise ValueError(f"Expected a named parameter: {a}")

        key = a.lower()
        matched_fields: list[str] = []
        if key in abbreviation_to_field:
            matched_fields = [abbreviation_to_field[key]]
        else:
            prefix_matches = [field for lower, field in lower_to_field.items() if lower.startswith(key)]
            if prefix_matches:
                matched_fields = [min(prefix_matches, key=len)]
            elif key in aliases:
                matched_fields = aliases[key]

        if not matched_fields:
            raise ValueError(f"Unknown named parameter: {a}")

        is_flag = all(field.lower() in flag_names for field in matched_fields)
        if is_flag:
            value = 1
            l += 1
        else:
            if l + 1 >= len(args):
                raise ValueError(f"Missing value for named parameter: {a}")
            value = args[l + 1]
            l += 2

        for field in matched_fields:
            result[field] = value

    return result
