"""Parse mode-parameter strings into structured dicts.

A mode-params string encodes voting formula parameters in a compact format.
Example: ``"D30M50B.1G.2T.1"``

Supported codes:
    D = duration_days (int)
    M = majority_pct (int, 0-100)
    B = base_exponent (float)
    G = gradient_exponent (float)
    C = constant_base (float)
    S = smith_exponent (float)
    T = techcomm_exponent (float)
    N = ratio_multiplier (float)
    R = ratio_mode (bool, 0 or 1)

Values may start with a dot for decimals < 1, e.g. ``B.1`` means base_exponent=0.1.
"""

from __future__ import annotations

import re


# Ordered list of recognised codes and their target keys + types
_CODES: dict[str, tuple[str, type]] = {
    "D": ("duration_days", int),
    "M": ("majority_pct", int),
    "B": ("base_exponent", float),
    "G": ("gradient_exponent", float),
    "C": ("constant_base", float),
    "S": ("smith_exponent", float),
    "T": ("techcomm_exponent", float),
    "N": ("ratio_multiplier", float),
    "R": ("is_ratio_mode", bool),
}

# Regex: a single uppercase letter followed by a numeric value (int or float,
# possibly starting with '.' for values like .1 meaning 0.1)
_PARAM_RE = re.compile(r"([A-Z])(\d*\.?\d+)")


def parse_mode_params(params_str: str) -> dict:
    """Parse a mode-params string into a parameter dict.

    Parameters
    ----------
    params_str:
        Compact parameter string, e.g. ``"D30M50B.1G.2T.1"``.

    Returns
    -------
    dict
        Keys present depend on codes found in the string.  Defaults are
        applied for any code not present::

            {
                "duration_days": 30,
                "majority_pct": 50,
                "base_exponent": 0.1,
                "gradient_exponent": 0.2,
                "constant_base": 0.0,
                "smith_exponent": None,
                "techcomm_exponent": None,
                "ratio_multiplier": None,
                "is_ratio_mode": False,
            }

    Raises
    ------
    ValueError
        If an unrecognised code letter is found.
    """
    defaults: dict = {
        "duration_days": 30,
        "majority_pct": 50,
        "base_exponent": 0.1,
        "gradient_exponent": 0.2,
        "constant_base": 0.0,
        "smith_exponent": None,
        "techcomm_exponent": None,
        "ratio_multiplier": None,
        "is_ratio_mode": False,
    }

    if not params_str or not params_str.strip():
        return dict(defaults)

    result = dict(defaults)

    for match in _PARAM_RE.finditer(params_str):
        code = match.group(1)
        raw_value = match.group(2)

        if code not in _CODES:
            raise ValueError(f"Code de parametre inconnu : '{code}'")

        key, target_type = _CODES[code]

        if target_type is int:
            result[key] = int(float(raw_value))
        elif target_type is float:
            result[key] = float(raw_value)
        elif target_type is bool:
            result[key] = float(raw_value) != 0.0

    return result
