# Copyright Visual Collaboration Technologies Inc. All Rights Reserved.
# See LICENSE for details.
"""
Custom log level definitions, logger class, and color preferences.

This module defines custom logging levels beyond Python's standard set,
a logger class with concrete methods for those levels, and color mappings
for console output.

════════════════════════════════════════════════════════════
HOW TO ADD A NEW CUSTOM LOG LEVEL
════════════════════════════════════════════════════════════

Follow these steps to add a new custom logging level:

Step 1: Add enum value to CustomLogLevel (this file)
────────────────────────────────────────────────────────
   class CustomLogLevel(IntEnum):
       TRACE = 5
       VERBOSE = 15
       DIAGNOSTIC = 25  # ← Add your new level here

   Choose a numeric value between standard levels:
   - Below DEBUG (10): Use 1-9 (e.g., TRACE=5)
   - Between DEBUG-INFO: Use 11-19 (e.g., VERBOSE=15)
   - Between INFO-WARNING: Use 21-29
   - Between WARNING-ERROR: Use 31-39
   - Between ERROR-CRITICAL: Use 41-49

Step 2: Add method to CustomLevelLogger (this file)
────────────────────────────────────────────────────────
   class CustomLevelLogger(logging.Logger):
       def diagnostic(self, msg: object, *args: Any, **kwargs: Any) -> None:
           if self.isEnabledFor(CustomLogLevel.DIAGNOSTIC):
               self._log(CustomLogLevel.DIAGNOSTIC, msg, args, stacklevel=2, **kwargs)

   Method name must be lowercase version of enum name (e.g., DIAGNOSTIC → diagnostic).

   Note: The level name is registered with Python's logging module automatically
   — the module-level loop ``for _level in CustomLogLevel`` calls
   ``logging.addLevelName()`` for every member.

Step 3: Add color mapping to LOG_LEVEL_COLOR_PREFERENCES (this file)
────────────────────────────────────────────────────────
   LOG_LEVEL_COLOR_PREFERENCES = {
       # Standard levels (already defined)
       "DEBUG": "white",
       "INFO": "green",
       "WARNING": "yellow",
       "ERROR": "red",
       "CRITICAL": "bold_red",

       # Custom levels (add your new level here)
       "TRACE": "gray",
       "VERBOSE": "cyan",
       "DIAGNOSTIC": "magenta",  # ← Add your new level here
   }

   Standard color names (work across most logging packages):
   - Basic: black, red, green, yellow, blue, magenta, cyan, white, gray
   - Bold variants: bold_red, bold_green, bold_cyan, etc.

   Note: If a color isn't supported by the current logging package,
   it will automatically fall back to a similar color via translation.

Step 4: Add enum member to LogLevel (in log_level.py)
────────────────────────────────────────────────────────
   class LogLevel(IntEnum):
       ...
       ERROR   = logging.ERROR
       FATAL   = logging.FATAL
       DIAGNOSTIC = CustomLogLevel.DIAGNOSTIC  # ← Add here, referencing CustomLogLevel

   The user-facing string name is derived automatically as the lowercase of
   the enum name (e.g., DIAGNOSTIC → "diagnostic"). If you need a different
   string name, add an entry to _NAME_OVERRIDES in log_level.py.

Step 5: Update LogLevelStr Literal (in log_level.py)
────────────────────────────────────────────────────────
   LogLevelStr = Literal[
       ...
       "fatal",
       "diagnostic",  # ← Add lowercase name here
   ]

That's it! The new level is now available throughout the logging system.

Usage example:
   from vcti.logging import logger
   logger.diagnostic("This is a diagnostic message")

════════════════════════════════════════════════════════════
Historical note — factory-based approach (removed)
════════════════════════════════════════════════════════════

The original implementation used a ``create_custom_level_logger(name, custom_levels)``
factory function. On every call, the factory:

1. Created a new subclass of ``CustomLevelLogger`` via a nested class definition.
2. Used ``setattr`` to dynamically attach log methods (``trace``, ``verbose``, etc.)
   to the new subclass based on the ``custom_levels`` IntEnum.
3. Instantiated the subclass directly (bypassing ``logging.getLogger``).
4. Returned the instance with a ``# type: ignore[return-value]`` to suppress
   the type mismatch between the dynamic subclass and ``CustomLevelLogger``.

This was replaced with the current approach for the following reasons:

- **Unnecessary dynamism**: The custom levels (TRACE, VERBOSE) are statically
  known and do not change at runtime. Generating methods dynamically for a fixed
  set of levels added complexity without benefit.
- **Non-standard logger creation**: Instantiating loggers directly
  (``_CustomLevelLoggerImpl(name)``) bypasses Python's ``logging.Manager``, which
  means the logger is not registered in the global logger hierarchy. This breaks
  ``logging.getLogger("vcti")`` for any code that expects to retrieve the same
  logger instance by name.
- **Type safety**: The factory returned a dynamically-created subclass that the
  type checker could not verify, requiring ``type: ignore`` suppression.
- **Class proliferation**: Each call created a new class object, which is
  wasteful when only a single logger class is needed.

The current implementation uses ``logging.setLoggerClass()`` — the standard
Python mechanism for custom logger classes — and defines concrete methods
directly on ``CustomLevelLogger``. This integrates properly with the logging
hierarchy, is fully type-safe, and requires no dynamic method generation.

Note: ``setLoggerClass`` is process-global — all subsequent ``logging.getLogger``
calls will produce ``CustomLevelLogger`` instances. This is intentional and
harmless: the extra methods (``trace``, ``verbose``) are no-ops on loggers
that don't have handlers configured at those levels.
"""

import logging
from enum import IntEnum
from types import MappingProxyType
from typing import Any

# ════════════════════════════════════════════════════════════
# Step 1: Define custom log levels
# ════════════════════════════════════════════════════════════


class CustomLogLevel(IntEnum):
    """
    Custom logging levels beyond Python's standard levels.

    Standard Python levels:
    - NOTSET   = 0
    - DEBUG    = 10
    - INFO     = 20
    - WARNING  = 30
    - ERROR    = 40
    - CRITICAL = 50

    Custom levels (add more here):
    - TRACE (5): Most detailed, low-level application flow
    - VERBOSE (15): More detailed than INFO but less granular than DEBUG
    """

    TRACE = 5
    VERBOSE = 15


# ════════════════════════════════════════════════════════════
# Step 2: Define logger class with concrete custom level methods
# ════════════════════════════════════════════════════════════


class CustomLevelLogger(logging.Logger):
    """
    Logger subclass with concrete methods for custom log levels.

    Each method corresponds to a member of :class:`CustomLogLevel`. Methods
    follow the same signature and semantics as :meth:`logging.Logger.debug`.
    """

    def trace(self, msg: object, *args: Any, **kwargs: Any) -> None:
        """Log a message with severity 'TRACE' (level 5)."""
        if self.isEnabledFor(CustomLogLevel.TRACE):
            self._log(CustomLogLevel.TRACE, msg, args, stacklevel=2, **kwargs)

    def verbose(self, msg: object, *args: Any, **kwargs: Any) -> None:
        """Log a message with severity 'VERBOSE' (level 15)."""
        if self.isEnabledFor(CustomLogLevel.VERBOSE):
            self._log(CustomLogLevel.VERBOSE, msg, args, stacklevel=2, **kwargs)


# ════════════════════════════════════════════════════════════
# Register custom level names with the logging module (automatic)
#
# This loop registers every CustomLogLevel member with Python's logging module.
# No manual step is needed — adding a member to CustomLogLevel is sufficient.
# ════════════════════════════════════════════════════════════

for _level in CustomLogLevel:
    logging.addLevelName(_level.value, _level.name)


# ════════════════════════════════════════════════════════════
# Step 3: Define color preferences for standard as well as custom log levels
# ════════════════════════════════════════════════════════════

LOG_LEVEL_COLOR_PREFERENCES: MappingProxyType[str, str] = MappingProxyType(
    {
        # Standard levels (explicitly defined for consistency across logging packages)
        "DEBUG": "white",
        "INFO": "green",
        "WARNING": "yellow",
        "ERROR": "red",
        "CRITICAL": "bold_red",
        # Custom levels
        "TRACE": "gray",  # Subdued color for lowest priority trace messages
        "VERBOSE": "cyan",  # Between DEBUG and INFO
    }
)


# ════════════════════════════════════════════════════════════
# Register CustomLevelLogger as the default logger class
# ════════════════════════════════════════════════════════════

logging.setLoggerClass(CustomLevelLogger)
