"""Utility helpers for config parsing — type coercion and dict merging."""
from __future__ import annotations

from dataclasses import MISSING as dataclass_MISSING
from typing import Any


def _coerce_env_value(raw: str, f: "dataclass_field") -> Any:  # noqa: F821
    """Coerce a string environment variable value to the type of a dataclass field.

    Supports: str, int, float, bool, Optional variants, and List[str] (comma-separated).
    When the default is None (Optional fields), inspects the type annotation to
    determine the correct target type.
    """
    import typing

    # Determine the target type from the field's default value
    default = f.default if f.default is not dataclass_MISSING else (
        f.default_factory() if f.default_factory is not dataclass_MISSING else None
    )

    if isinstance(default, bool):
        return raw.lower() in ("true", "1", "yes", "on")
    elif isinstance(default, int):
        return int(raw)
    elif isinstance(default, float):
        return float(raw)
    elif isinstance(default, list):
        # Comma-separated list
        return [item.strip() for item in raw.split(",") if item.strip()]
    elif default is None:
        # Optional[T] field — inspect the type annotation to find T
        hint = f.type if isinstance(f.type, type) else None
        if hint is None and isinstance(f.type, str):
            # String annotations — try to resolve common Optional patterns
            type_str = f.type
            # Handle "Optional[float]", "Optional[float]", etc.
            for candidate_type, coercer in [
                ("float", float), ("int", int), ("bool", lambda v: v.lower() in ("true", "1", "yes", "on")),
            ]:
                if candidate_type in type_str.lower():
                    return coercer(raw)
            # Fall through to string
            return raw
        # Try typing.get_args for runtime-available type hints
        type_hint = f.type
        args = typing.get_args(type_hint)
        # For Optional[X], args is (X, NoneType)
        inner_types = [a for a in args if a is not type(None)]
        if inner_types:
            inner = inner_types[0]
            if inner is float:
                return float(raw)
            elif inner is int:
                return int(raw)
            elif inner is bool:
                return raw.lower() in ("true", "1", "yes", "on")
        # Default: string (for Optional[str] or unresolvable)
        return raw
    else:
        # Default: string
        return raw


def _deep_merge(base: dict, override: dict) -> None:
    """Merge *override* into *base* in-place (nested dicts merged recursively)."""
    for k, v in override.items():
        if k in base and isinstance(base[k], dict) and isinstance(v, dict):
            _deep_merge(base[k], v)
        else:
            base[k] = v
