# SPDX-License-Identifier: ISC
#
# Copyright (c) 2021, Timothée Mazzucotelli and contributors
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

# This module contains helpers for testing docstring parsing.

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Protocol

from griffe import (
    Attribute,
    Class,
    Docstring,
    DocstringSection,
    Function,
    LogLevel,
    Module,
)

if TYPE_CHECKING:
    from collections.abc import Iterator
    from types import ModuleType


ParentType = Module | Class | Function | Attribute | None
ParseResultType = tuple[list[DocstringSection], list[str]]


class ParserType(Protocol):  # noqa: D101
    def __call__(  # noqa: D102
        self,
        docstring: str,
        parent: ParentType | None = None,
        **parser_opts: Any,
    ) -> ParseResultType: ...


def parser(parser_module: ModuleType) -> Iterator[ParserType]:
    """Wrap a parser to help testing.

    Parameters:
        parser_module: The parser module containing a `parse` function.

    Yields:
        The wrapped function.
    """
    original_warn = parser_module.docstring_warning

    def parse(docstring: str, parent: ParentType | None = None, **parser_opts: Any) -> ParseResultType:
        """Parse a docstring.

        Parameters:
            docstring: The docstring to parse.
            parent: The docstring's parent object.
            **parser_opts: Additional options accepted by the parser.

        Returns:
            The parsed sections, and warnings.
        """
        docstring_object = Docstring(docstring, lineno=1, endlineno=None)
        docstring_object.endlineno = len(docstring_object.lines) + 1
        if parent is not None:
            docstring_object.parent = parent
            parent.docstring = docstring_object
        warnings = []
        parser_module.docstring_warning = (  # ty:ignore[unresolved-attribute]
            lambda _docstring, _offset, message, log_level=LogLevel.warning: warnings.append(message)
        )
        func_name = f"parse_{parser_module.__name__.split('.')[-1]}"
        func = getattr(parser_module, func_name)
        sections = func(docstring_object, **parser_opts)
        return sections, warnings

    yield parse

    parser_module.docstring_warning = original_warn  # ty:ignore[unresolved-attribute]
