# This module contains the logger used throughout Griffe.
# The logger is actually a wrapper around the standard Python logger.
# We wrap it so that it is easier for other downstream libraries to patch it.
# For example, mkdocstrings-python patches the logger to relocate it as a child
# of `mkdocs.plugins` so that it fits in the MkDocs logging configuration.
#
# We use a single, global logger because our public API is exposed in a single module, `griffe`.
# Extensions however should use their own logger, which is why we provide the `get_logger` function.

from __future__ import annotations

import logging
from contextlib import contextmanager
from typing import TYPE_CHECKING, Any, Callable, ClassVar

if TYPE_CHECKING:
    from collections.abc import Iterator


class Logger:
    _default_logger: Any = logging.getLogger
    _instances: ClassVar[dict[str, Logger]] = {}

    def __init__(self, name: str) -> None:
        # Default logger that can be patched by third-party.
        self._logger = self.__class__._default_logger(name)

    def __getattr__(self, name: str) -> Any:
        # Forward everything to the logger.
        return getattr(self._logger, name)

    @contextmanager
    def disable(self) -> Iterator[None]:
        """Temporarily disable logging."""
        old_level = self._logger.level
        self._logger.setLevel(100)
        try:
            yield
        finally:
            self._logger.setLevel(old_level)

    @classmethod
    def _get(cls, name: str = "griffe") -> Logger:
        if name not in cls._instances:
            cls._instances[name] = cls(name)
        return cls._instances[name]

    @classmethod
    def _patch_loggers(cls, get_logger_func: Callable) -> None:
        # Patch current instances.
        for name, instance in cls._instances.items():
            instance._logger = get_logger_func(name)

        # Future instances will be patched as well.
        cls._default_logger = get_logger_func


logger: Logger = Logger._get()
"""Our global logger, used throughout the library.

Griffe's output and error messages are logging messages.

Griffe provides the [`patch_loggers`][griffe.patch_loggers]
function so dependent libraries can patch Griffe loggers as they see fit.

For example, to fit in the MkDocs logging configuration
and prefix each log message with the module name:

```python
import logging
from griffe import patch_loggers


class LoggerAdapter(logging.LoggerAdapter):
    def __init__(self, prefix, logger):
        super().__init__(logger, {})
        self.prefix = prefix

    def process(self, msg, kwargs):
        return f"{self.prefix}: {msg}", kwargs


def get_logger(name):
    logger = logging.getLogger(f"mkdocs.plugins.{name}")
    return LoggerAdapter(name, logger)


patch_loggers(get_logger)
```
"""


def get_logger(name: str = "griffe") -> Logger:
    """Create and return a new logger instance.

    Parameters:
        name: The logger name.

    Returns:
        The logger.
    """
    return Logger._get(name)


def patch_loggers(get_logger_func: Callable[[str], Any]) -> None:
    """Patch Griffe logger and Griffe extensions' loggers.

    Parameters:
        get_logger_func: A function accepting a name as parameter and returning a logger.
    """
    Logger._patch_loggers(get_logger_func)
