"""Core option type definitions and utilities.

Provides base Typer option definitions, normalization functions,
and validation helpers used across all CLI options.
"""

from __future__ import annotations

import logging
from pathlib import Path
from typing import Annotated, Final

import typer

LOG_FORMAT_CHOICES: Final[set[str]] = {"text", "json"}
LOG_LEVEL_CHOICES: Final[list[str]] = sorted(
    name
    for name, value in logging.getLevelNamesMapping().items()
    if isinstance(name, str) and not name.isdigit()
)
LOG_LEVEL_SET: Final[set[str]] = {choice.upper() for choice in LOG_LEVEL_CHOICES}
LOG_LEVEL_MAP: Final[dict[str, int]] = {
    name.upper(): value
    for name, value in logging.getLevelNamesMapping().items()
    if isinstance(name, str) and not name.isdigit()
}

ConfigPathOption = Annotated[
    Path,
    typer.Option(
        "--config",
        help="Path to a BiRRe configuration TOML file to load",
        envvar="BIRRE_CONFIG",
        show_envvar=True,
        rich_help_panel="Configuration",
    ),
]


def clean_string(value: str | None) -> str | None:
    """Normalize optional string input."""

    if value is None:
        return None
    candidate = value.strip()
    return candidate or None


def validate_positive(name: str, value: int | None) -> int | None:
    """Validate that a numeric option is positive when provided."""

    if value is None:
        return None
    if value <= 0:
        raise typer.BadParameter(
            f"{name} must be a positive integer",
            param_hint=f"--{name.replace('_', '-')}",
        )
    return value


def normalize_context(value: str | None, *, choices: set[str] | frozenset[str]) -> str | None:
    """Normalize the context option, ensuring it is one of the allowed choices."""

    if value is None:
        return None
    candidate = value.strip().lower().replace("-", "_")
    if not candidate:
        return None
    if candidate not in choices:
        raise typer.BadParameter(
            f"Context must be one of: {', '.join(sorted(choices))}",
            param_hint="--context",
        )
    return candidate


def normalize_log_format(value: str | None) -> str | None:
    """Normalize the log format option."""

    if value is None:
        return None
    candidate = value.strip().lower()
    if not candidate:
        return None
    if candidate not in LOG_FORMAT_CHOICES:
        raise typer.BadParameter(
            "Log format must be either 'text' or 'json'",
            param_hint="--log-format",
        )
    return candidate


def normalize_log_level(value: str | None) -> str | None:
    """Normalize the log level option."""

    if value is None:
        return None
    candidate = value.strip().upper()
    if not candidate:
        return None
    if candidate not in LOG_LEVEL_SET:
        raise typer.BadParameter(
            f"Log level must be one of: {', '.join(LOG_LEVEL_CHOICES)}",
            param_hint="--log-level",
        )
    return candidate


__all__ = [
    "ConfigPathOption",
    "LOG_FORMAT_CHOICES",
    "LOG_LEVEL_CHOICES",
    "LOG_LEVEL_MAP",
    "clean_string",
    "normalize_context",
    "normalize_log_format",
    "normalize_log_level",
    "validate_positive",
]
