"""Utilities for currency normalization within pricing and financial workflows."""

from __future__ import annotations

import re
from dataclasses import dataclass

VALID_CURRENCY_PATTERN = re.compile(r"^[A-Z]{3}$")


@dataclass(frozen=True)
class CurrencyValidationError(ValueError):
    """Raised when a currency code fails validation."""

    code: str

    def __str__(self) -> str:  # pragma: no cover - dataclass repr not required in tests
        return f"Invalid currency code: {self.code!r}"


def normalise_currency(code: str | None) -> str | None:
    """Normalise currency codes to uppercase ISO-4217 values."""

    if code is None:
        return None
    candidate = code.strip().upper()
    if not VALID_CURRENCY_PATTERN.match(candidate):
        raise CurrencyValidationError(candidate)
    return candidate


def require_currency(code: str | None, default: str | None = None) -> str:
    """Return normalised currency code, falling back to default when missing."""

    normalised = normalise_currency(code)
    if normalised is not None:
        return normalised
    if default is None:
        raise CurrencyValidationError("<missing currency>")
    fallback = normalise_currency(default)
    if fallback is None:
        raise CurrencyValidationError("<invalid default currency>")
    return fallback
