# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt

"""Arguments provider class used to expose options."""

from __future__ import annotations

import argparse
import optparse  # pylint: disable=deprecated-module
import warnings
from collections.abc import Iterator
from typing import Any

from pylint.config.arguments_manager import _ArgumentsManager
from pylint.typing import OptionDict, Options


class UnsupportedAction(Exception):
    """Raised by set_option when it doesn't know what to do for an action."""

    def __init__(self, *args: object) -> None:
        # TODO: 3.0: Remove deprecated exception
        warnings.warn(
            "UnsupportedAction has been deprecated and will be removed in pylint 3.0",
            DeprecationWarning,
            stacklevel=2,
        )
        super().__init__(*args)


class _ArgumentsProvider:
    """Base class for classes that provide arguments."""

    name: str
    """Name of the provider."""

    options: Options = ()
    """Options provided by this provider."""

    option_groups_descs: dict[str, str] = {}
    """Option groups of this provider and their descriptions."""

    def __init__(self, arguments_manager: _ArgumentsManager) -> None:
        self._arguments_manager = arguments_manager
        """The manager that will parse and register any options provided."""

        self._arguments_manager._register_options_provider(self)

        self._level = 0

    @property
    def level(self) -> int:
        # TODO: 3.0: Remove deprecated attribute
        warnings.warn(
            "The level attribute has been deprecated. It was used to display the checker in the help or not,"
            " and everything is displayed in the help now. It will be removed in pylint 3.0.",
            DeprecationWarning,
            stacklevel=2,
        )
        return self._level

    @level.setter
    def level(self, value: int) -> None:
        # TODO: 3.0: Remove deprecated attribute
        warnings.warn(
            "Setting the level attribute has been deprecated. It was used to display the checker "
            "in the help or not, and everything is displayed in the help now. It will be removed "
            "in pylint 3.0.",
            DeprecationWarning,
            stacklevel=2,
        )
        self._level = value

    @property
    def config(self) -> argparse.Namespace:
        # TODO: 3.0: Remove deprecated attribute
        warnings.warn(
            "The checker-specific config attribute has been deprecated. Please use "
            "'linter.config' to access the global configuration object.",
            DeprecationWarning,
            stacklevel=2,
        )
        return self._arguments_manager.config

    def load_defaults(self) -> None:  # pragma: no cover
        """DEPRECATED: Initialize the provider using default values."""
        warnings.warn(
            "load_defaults has been deprecated. Option groups should be "
            "registered by initializing an ArgumentsProvider. "
            "This automatically registers the group on the ArgumentsManager.",
            DeprecationWarning,
            stacklevel=2,
        )
        for opt, optdict in self.options:
            action = optdict.get("action")
            if action != "callback":
                # callback action have no default
                if optdict is None:
                    with warnings.catch_warnings():
                        warnings.filterwarnings("ignore", category=DeprecationWarning)
                        optdict = self.get_option_def(opt)
                default = optdict.get("default")
                self.set_option(opt, default, action, optdict)

    def option_attrname(
        self, opt: str, optdict: OptionDict | None = None
    ) -> str:  # pragma: no cover
        """DEPRECATED: Get the config attribute corresponding to opt."""
        warnings.warn(
            "option_attrname has been deprecated. It will be removed "
            "in a future release.",
            DeprecationWarning,
            stacklevel=2,
        )
        if optdict is None:
            with warnings.catch_warnings():
                warnings.filterwarnings("ignore", category=DeprecationWarning)
                optdict = self.get_option_def(opt)
        return optdict.get("dest", opt.replace("-", "_"))  # type: ignore[return-value]

    def option_value(self, opt: str) -> Any:  # pragma: no cover
        """DEPRECATED: Get the current value for the given option."""
        warnings.warn(
            "option_value has been deprecated. It will be removed "
            "in a future release.",
            DeprecationWarning,
            stacklevel=2,
        )
        return getattr(self._arguments_manager.config, opt.replace("-", "_"), None)

    def set_option(  # pragma: no cover
        self,
        optname: Any,
        value: Any,
        action: Any = None,  # pylint: disable=unused-argument
        optdict: Any = None,  # pylint: disable=unused-argument
    ) -> None:
        """DEPRECATED: Method called to set an option (registered in the options
        list).
        """
        # TODO: 3.0: Remove deprecated method.
        warnings.warn(
            "set_option has been deprecated. You can use _arguments_manager.set_option "
            "or linter.set_option to set options on the global configuration object.",
            DeprecationWarning,
            stacklevel=2,
        )
        self._arguments_manager.set_option(optname, value)

    def get_option_def(self, opt: str) -> OptionDict:  # pragma: no cover
        """DEPRECATED: Return the dictionary defining an option given its name.

        :raises OptionError: If the option isn't found.
        """
        warnings.warn(
            "get_option_def has been deprecated. It will be removed "
            "in a future release.",
            DeprecationWarning,
            stacklevel=2,
        )
        assert self.options
        for option in self.options:
            if option[0] == opt:
                return option[1]
        raise optparse.OptionError(
            f"no such option {opt} in section {self.name!r}", opt  # type: ignore[arg-type]
        )

    def options_by_section(
        self,
    ) -> Iterator[
        tuple[str, list[tuple[str, OptionDict, Any]]]
        | tuple[None, dict[str, list[tuple[str, OptionDict, Any]]]]
    ]:  # pragma: no cover
        """DEPRECATED: Return an iterator on options grouped by section.

        (section, [list of (optname, optdict, optvalue)])
        """
        # TODO 3.0: Make this function private see
        # https://github.com/pylint-dev/pylint/pull/6665#discussion_r880143229
        # It's only used in '_get_global_options_documentation'
        warnings.warn(
            "options_by_section has been deprecated. It will be removed "
            "in a future release.",
            DeprecationWarning,
            stacklevel=2,
        )
        sections: dict[str, list[tuple[str, OptionDict, Any]]] = {}
        for optname, optdict in self.options:
            with warnings.catch_warnings():
                warnings.filterwarnings("ignore", category=DeprecationWarning)
                sections.setdefault(optdict.get("group"), []).append(  # type: ignore[arg-type]
                    (optname, optdict, self.option_value(optname))
                )
        if None in sections:
            yield None, sections.pop(None)  # type: ignore[call-overload]
        for section, options in sorted(sections.items()):
            yield section.upper(), options

    def options_and_values(
        self, options: Options | None = None
    ) -> Iterator[tuple[str, OptionDict, Any]]:  # pragma: no cover
        """DEPRECATED."""
        warnings.warn(
            "options_and_values has been deprecated. It will be removed "
            "in a future release.",
            DeprecationWarning,
            stacklevel=2,
        )
        if options is None:
            options = self.options
        for optname, optdict in options:
            with warnings.catch_warnings():
                warnings.filterwarnings("ignore", category=DeprecationWarning)
                yield optname, optdict, self.option_value(optname)
