# Copyright Visual Collaboration Technologies Inc. All Rights Reserved.
# See LICENSE for details.
"""Log level registry and string encoding.

Defines the canonical set of log levels (standard + custom) and provides
functions to convert between enum members and the lowercase string names
used in configuration (e.g., ``LogLevel.INFO`` <-> ``"info"``).

The string-to-level mapping is hardcoded rather than derived from Python's
``logging`` module. This gives us full control over the user-facing names
(e.g., ``"fatal"`` instead of ``"critical"``) and avoids platform-specific
aliases like ``WARN`` that vary across Python versions.

Historical note: this encoding was previously implemented using ``EnumCoder``
from the ``vcti-utils`` package, a generic enum serialization class. That
dependency pulled in the entire ``vcti-utils`` package (and its transitive
dependencies: numpy, psutil, pydantic, etc.) for what amounts to a simple
mapping. The inline implementation below is self-contained and keeps
``vcti-logging`` dependency-free from internal packages.
"""

import logging
from enum import IntEnum
from typing import Literal

from .custom_level import CustomLogLevel

# ════════════════════════════════════════════════════════════
# Step 5: Literal type for log level strings (for type annotations)
#
# When adding a new custom log level, add its lowercase name here.
# See custom_level.py for the full step-by-step guide.
#
# Note: This is defined before LogLevel because DEFAULT_LOG_LEVEL_STR
# (which uses LogLevelStr as its type annotation) must be available to
# other modules that import it alongside LogLevel.
# ════════════════════════════════════════════════════════════

LogLevelStr = Literal[
    "notset",
    "trace",
    "debug",
    "verbose",
    "info",
    "warning",
    "error",
    "fatal",
]


# ════════════════════════════════════════════════════════════
# Step 4: Log level enum (standard + custom)
#
# When adding a new custom log level, add the enum member here.
# See custom_level.py for the full step-by-step guide.
# ════════════════════════════════════════════════════════════


class LogLevel(IntEnum):
    """
    All log levels available in the VCTI logging system.

    Combines Python's standard levels with custom levels from CustomLogLevel.
    When adding a new custom level, add the enum member here with the same
    value as in CustomLogLevel.
    """

    NOTSET = logging.NOTSET  # 0
    TRACE = CustomLogLevel.TRACE  # 5
    DEBUG = logging.DEBUG  # 10
    VERBOSE = CustomLogLevel.VERBOSE  # 15
    INFO = logging.INFO  # 20
    WARNING = logging.WARNING  # 30
    ERROR = logging.ERROR  # 40
    FATAL = logging.FATAL  # 50


# ════════════════════════════════════════════════════════════
# String encoding/decoding
#
# By default, the user-facing string name is the lowercase version of the
# enum member name (e.g., LogLevel.INFO -> "info"). Use _NAME_OVERRIDES
# only when the user-facing name should differ from the enum name.
# ════════════════════════════════════════════════════════════

# Override only when the user-facing name differs from enum name.lower().
# Example: if the enum were CRITICAL but we wanted "fatal" as the string.
_NAME_OVERRIDES: dict[LogLevel, str] = {
    # No overrides needed currently — all names match their enum lowercase.
}

_level_to_name: dict[LogLevel, str] = {
    member: _NAME_OVERRIDES.get(member, member.name.lower()) for member in LogLevel
}
_name_to_level: dict[str, LogLevel] = {name: level for level, name in _level_to_name.items()}

DEFAULT_LOG_LEVEL_STR: LogLevelStr = "info"
LOG_LEVEL_VALUES = list(_name_to_level.keys())


def encode_log_level(level: LogLevel) -> str:
    """Convert a LogLevel enum member to its string representation.

    Args:
        level: The LogLevel enum member to encode.

    Returns:
        Lowercase string name (e.g., "info", "debug", "trace").
    """
    return _level_to_name[level]


def decode_log_level(name: str) -> LogLevel:
    """Convert a string name to a LogLevel enum member.

    The lookup is case-insensitive — the name is converted to lowercase
    before matching.

    Args:
        name: Level name (e.g., "info", "INFO", "Info").

    Returns:
        The corresponding LogLevel enum member.

    Raises:
        ValueError: If name doesn't match any log level.
    """
    try:
        return _name_to_level[name.lower()]
    except KeyError:
        raise ValueError(
            f"'{name}' is not a valid log level. Valid values: {tuple(_name_to_level.keys())}"
        )
