"""Normalization rules for financial text values.

Converts literal financial text into normalized numeric values:
- "$94.9 billion" → 94_900_000_000.0
- "25 basis points" → 0.25 (percentage points)
- "$1.52 per share" → 1.52
- "4%" → 4.0
- "$123.45" → 123.45
- "€99" → 99.0
"""

from __future__ import annotations

import re

# Multiplier suffixes for money normalization
_MULTIPLIERS: dict[str, float] = {
    "trillion": 1_000_000_000_000.0,
    "billion": 1_000_000_000.0,
    "million": 1_000_000.0,
    "thousand": 1_000.0,
    "k": 1_000.0,
    "m": 1_000_000.0,
    "b": 1_000_000_000.0,
    "t": 1_000_000_000_000.0,
    "bn": 1_000_000_000.0,
    "mn": 1_000_000.0,
    "tn": 1_000_000_000_000.0,
}

_NUMBER_RE = re.compile(r"[+-]?\d[\d,]*\.?\d*")


def _extract_number(text: str) -> float | None:
    """Extract the first numeric value from text, stripping commas."""
    match = _NUMBER_RE.search(text)
    if not match:
        return None
    num_str = match.group().replace(",", "")
    try:
        return float(num_str)
    except ValueError:
        return None


def _find_multiplier(text: str) -> float:
    """Find a magnitude multiplier in text (billion, million, etc.)."""
    lower = text.lower()
    for suffix, mult in _MULTIPLIERS.items():
        # Match word boundaries for short suffixes to avoid false positives
        if len(suffix) <= 2:
            if re.search(rf"\b{suffix}\b", lower):
                return mult
        else:
            if suffix in lower:
                return mult
    return 1.0


def normalize_money(text: str) -> float | None:
    """Normalize a money expression to its numeric value.

    Examples:
        "$94.9 billion" → 94_900_000_000.0
        "$1.52 per share" → 1.52
        "€123.45" → 123.45
        "$1,234" → 1234.0
    """
    number = _extract_number(text)
    if number is None:
        return None

    # Check for "per share" — don't apply multiplier
    lower = text.lower()
    if "per share" in lower:
        return number

    multiplier = _find_multiplier(text)
    return number * multiplier


def normalize_percentage(text: str) -> float | None:
    """Normalize a percentage to its numeric value.

    Examples:
        "4%" → 4.0
        "-2.5%" → -2.5
        "+1.2 percent" → 1.2
    """
    return _extract_number(text)


def normalize_basis_points(text: str) -> float | None:
    """Normalize basis points to percentage points.

    Examples:
        "25 basis points" → 0.25
        "50bps" → 0.50
        "100 bps" → 1.0
    """
    number = _extract_number(text)
    if number is None:
        return None
    return number / 100.0


def normalize_range(text: str) -> tuple[float | None, float | None]:
    """Normalize a range expression to (low, high).

    Examples:
        "$10-$12" → (10.0, 12.0)
        "$1.50 to $2.00" → (1.50, 2.00)
    """
    numbers = _NUMBER_RE.findall(text)
    if len(numbers) < 2:
        return (None, None)
    try:
        low = float(numbers[0].replace(",", ""))
        high = float(numbers[1].replace(",", ""))
        return (low, high)
    except ValueError:
        return (None, None)


def normalize_value(candidate_type: str, text: str) -> float | None:
    """Normalize a candidate value based on its type.

    Returns the normalized numeric value, or None if not applicable.
    For ranges, returns the midpoint.
    """
    if candidate_type == "money" or candidate_type == "currency":
        return normalize_money(text)
    elif candidate_type == "percentage":
        return normalize_percentage(text)
    elif candidate_type == "basis_points":
        return normalize_basis_points(text)
    elif candidate_type == "eps":
        return normalize_money(text)
    elif candidate_type == "revenue":
        return normalize_money(text)
    elif candidate_type == "range":
        low, high = normalize_range(text)
        if low is not None and high is not None:
            return (low + high) / 2.0
        return low
    else:
        return None
