# 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 the models for storing docstrings structured data.

from __future__ import annotations

from typing import TYPE_CHECKING

from griffe._internal.enumerations import DocstringSectionKind
from griffe._internal.expressions import ExprTuple

if TYPE_CHECKING:
    from collections.abc import Sequence
    from typing import Any, Literal

    from griffe._internal.expressions import Expr


# Elements -----------------------------------------------
class DocstringElement:
    """This base class represents annotated, nameless elements."""

    def __init__(self, *, description: str, annotation: str | Expr | None = None) -> None:
        """Initialize the element.

        Parameters:
            annotation: The element annotation, if any.
            description: The element description.
        """
        self.description: str = description
        """The element description."""
        self.annotation: str | Expr | None = annotation
        """The element annotation."""

    def as_dict(self, **kwargs: Any) -> dict[str, Any]:  # noqa: ARG002
        """Return this element's data as a dictionary.

        Parameters:
            **kwargs: Additional serialization options.

        Returns:
            A dictionary.
        """
        return {
            "annotation": self.annotation,
            "description": self.description,
        }


class DocstringNamedElement(DocstringElement):
    """This base class represents annotated, named elements."""

    def __init__(
        self,
        name: str,
        *,
        description: str,
        annotation: str | Expr | None = None,
        value: str | Expr | None = None,
    ) -> None:
        """Initialize the element.

        Parameters:
            name: The element name.
            description: The element description.
            annotation: The element annotation, if any.
            value: The element value, as a string.
        """
        super().__init__(description=description, annotation=annotation)
        self.name: str = name
        """The element name."""
        self.value: str | Expr | None = value
        """The element value, if any"""

    def as_dict(self, **kwargs: Any) -> dict[str, Any]:
        """Return this element's data as a dictionary.

        Parameters:
            **kwargs: Additional serialization options.

        Returns:
            A dictionary.
        """
        base = {"name": self.name, **super().as_dict(**kwargs)}
        if self.value is not None:
            base["value"] = self.value
        return base


class DocstringAdmonition(DocstringElement):
    """This class represents an admonition."""

    @property
    def kind(self) -> str | Expr | None:
        """The kind of this admonition."""
        return self.annotation

    @kind.setter
    def kind(self, value: str | Expr) -> None:
        self.annotation = value

    @property
    def contents(self) -> str:
        """The contents of this admonition."""
        return self.description

    @contents.setter
    def contents(self, value: str) -> None:
        self.description = value


class DocstringDeprecated(DocstringElement):
    """This class represents a documented deprecated item."""

    @property
    def version(self) -> str:
        """The version of this deprecation."""
        return self.annotation  # ty:ignore[invalid-return-type]

    @version.setter
    def version(self, value: str) -> None:
        self.annotation = value


class DocstringRaise(DocstringElement):
    """This class represents a documented raise value."""


class DocstringWarn(DocstringElement):
    """This class represents a documented warn value."""


class DocstringReturn(DocstringNamedElement):
    """This class represents a documented return value."""


class DocstringYield(DocstringNamedElement):
    """This class represents a documented yield value."""


class DocstringReceive(DocstringNamedElement):
    """This class represents a documented receive value."""


class DocstringParameter(DocstringNamedElement):
    """This class represent a documented function parameter."""

    @property
    def default(self) -> str | Expr | None:
        """The default value of this parameter."""
        return self.value

    @default.setter
    def default(self, value: str) -> None:
        self.value = value


class DocstringTypeParameter(DocstringNamedElement):
    """This class represent a documented type parameter."""

    @property
    def default(self) -> str | Expr | None:
        """The default value of this type parameter."""
        return self.value

    @default.setter
    def default(self, value: str) -> None:
        self.value = value

    @property
    def bound(self) -> str | Expr | None:
        """The bound of this type parameter."""
        if not isinstance(self.annotation, ExprTuple):
            return self.annotation
        return None

    @bound.setter
    def bound(self, bound: str | Expr | None) -> None:
        self.annotation = bound

    @property
    def constraints(self) -> tuple[str | Expr, ...] | None:
        """The constraints of this type parameter."""
        if isinstance(self.annotation, ExprTuple):
            return tuple(self.annotation.elements)
        return None

    @constraints.setter
    def constraints(self, constraints: Sequence[str | Expr] | None) -> None:
        if constraints is not None:
            self.annotation = ExprTuple(constraints)
        else:
            self.annotation = None


class DocstringAttribute(DocstringNamedElement):
    """This class represents a documented module/class attribute."""


class DocstringFunction(DocstringNamedElement):
    """This class represents a documented function."""

    @property
    def signature(self) -> str | Expr | None:
        """The function signature."""
        return self.annotation


class DocstringClass(DocstringNamedElement):
    """This class represents a documented class."""

    @property
    def signature(self) -> str | Expr | None:
        """The class signature."""
        return self.annotation


class DocstringTypeAlias(DocstringNamedElement):
    """This class represents a documented type alias."""


class DocstringModule(DocstringNamedElement):
    """This class represents a documented module."""


# Sections -----------------------------------------------
class DocstringSection:
    """This class represents a docstring section."""

    kind: DocstringSectionKind
    """The section kind."""

    def __init__(self, title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            title: An optional title.
        """
        self.title: str | None = title
        """The section title."""
        self.value: Any = None
        """The section value."""

    def __bool__(self) -> bool:
        """Whether this section has a true-ish value."""
        return bool(self.value)

    def as_dict(self, **kwargs: Any) -> dict[str, Any]:
        """Return this section's data as a dictionary.

        Parameters:
            **kwargs: Additional serialization options.

        Returns:
            A dictionary.
        """
        if hasattr(self.value, "as_dict"):  # noqa: SIM108
            serialized_value = self.value.as_dict(**kwargs)
        else:
            serialized_value = self.value
        base = {"kind": self.kind.value, "value": serialized_value}
        if self.title:
            base["title"] = self.title
        return base


class DocstringSectionText(DocstringSection):
    """This class represents a text section."""

    kind: DocstringSectionKind = DocstringSectionKind.text

    def __init__(self, value: str, title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            value: The section text.
            title: An optional title.
        """
        super().__init__(title)
        self.value: str = value


class DocstringSectionParameters(DocstringSection):
    """This class represents a parameters section."""

    kind: DocstringSectionKind = DocstringSectionKind.parameters

    def __init__(self, value: list[DocstringParameter], title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            value: The section parameters.
            title: An optional title.
        """
        super().__init__(title)
        self.value: list[DocstringParameter] = value


class DocstringSectionOtherParameters(DocstringSectionParameters):
    """This class represents an other parameters section."""

    kind: DocstringSectionKind = DocstringSectionKind.other_parameters


class DocstringSectionTypeParameters(DocstringSection):
    """This class represents a type parameters section."""

    kind: DocstringSectionKind = DocstringSectionKind.type_parameters

    def __init__(self, value: list[DocstringTypeParameter], title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            value: The section type parameters.
            title: An optional title.
        """
        super().__init__(title)
        self.value: list[DocstringTypeParameter] = value


class DocstringSectionRaises(DocstringSection):
    """This class represents a raises section."""

    kind: DocstringSectionKind = DocstringSectionKind.raises

    def __init__(self, value: list[DocstringRaise], title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            value: The section exceptions.
            title: An optional title.
        """
        super().__init__(title)
        self.value: list[DocstringRaise] = value


class DocstringSectionWarns(DocstringSection):
    """This class represents a warns section."""

    kind: DocstringSectionKind = DocstringSectionKind.warns

    def __init__(self, value: list[DocstringWarn], title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            value: The section warnings.
            title: An optional title.
        """
        super().__init__(title)
        self.value: list[DocstringWarn] = value


class DocstringSectionReturns(DocstringSection):
    """This class represents a returns section."""

    kind: DocstringSectionKind = DocstringSectionKind.returns

    def __init__(self, value: list[DocstringReturn], title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            value: The section returned items.
            title: An optional title.
        """
        super().__init__(title)
        self.value: list[DocstringReturn] = value


class DocstringSectionYields(DocstringSection):
    """This class represents a yields section."""

    kind: DocstringSectionKind = DocstringSectionKind.yields

    def __init__(self, value: list[DocstringYield], title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            value: The section yielded items.
            title: An optional title.
        """
        super().__init__(title)
        self.value: list[DocstringYield] = value


class DocstringSectionReceives(DocstringSection):
    """This class represents a receives section."""

    kind: DocstringSectionKind = DocstringSectionKind.receives

    def __init__(self, value: list[DocstringReceive], title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            value: The section received items.
            title: An optional title.
        """
        super().__init__(title)
        self.value: list[DocstringReceive] = value


class DocstringSectionExamples(DocstringSection):
    """This class represents an examples section."""

    kind: DocstringSectionKind = DocstringSectionKind.examples

    def __init__(
        self,
        value: list[tuple[Literal[DocstringSectionKind.text, DocstringSectionKind.examples], str]],
        title: str | None = None,
    ) -> None:
        """Initialize the section.

        Parameters:
            value: The section examples.
            title: An optional title.
        """
        super().__init__(title)
        self.value: list[tuple[Literal[DocstringSectionKind.text, DocstringSectionKind.examples], str]] = value


class DocstringSectionAttributes(DocstringSection):
    """This class represents an attributes section."""

    kind: DocstringSectionKind = DocstringSectionKind.attributes

    def __init__(self, value: list[DocstringAttribute], title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            value: The section attributes.
            title: An optional title.
        """
        super().__init__(title)
        self.value: list[DocstringAttribute] = value


class DocstringSectionFunctions(DocstringSection):
    """This class represents a functions/methods section."""

    kind: DocstringSectionKind = DocstringSectionKind.functions

    def __init__(self, value: list[DocstringFunction], title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            value: The section functions.
            title: An optional title.
        """
        super().__init__(title)
        self.value: list[DocstringFunction] = value


class DocstringSectionClasses(DocstringSection):
    """This class represents a classes section."""

    kind: DocstringSectionKind = DocstringSectionKind.classes

    def __init__(self, value: list[DocstringClass], title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            value: The section classes.
            title: An optional title.
        """
        super().__init__(title)
        self.value: list[DocstringClass] = value


class DocstringSectionTypeAliases(DocstringSection):
    """This class represents a type aliases section."""

    kind: DocstringSectionKind = DocstringSectionKind.type_aliases

    def __init__(self, value: list[DocstringTypeAlias], title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            value: The section classes.
            title: An optional title.
        """
        super().__init__(title)
        self.value: list[DocstringTypeAlias] = value


class DocstringSectionModules(DocstringSection):
    """This class represents a modules section."""

    kind: DocstringSectionKind = DocstringSectionKind.modules

    def __init__(self, value: list[DocstringModule], title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            value: The section modules.
            title: An optional title.
        """
        super().__init__(title)
        self.value: list[DocstringModule] = value


class DocstringSectionDeprecated(DocstringSection):
    """This class represents a deprecated section."""

    kind: DocstringSectionKind = DocstringSectionKind.deprecated

    def __init__(self, version: str, text: str, title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            version: The deprecation version.
            text: The deprecation text.
            title: An optional title.
        """
        super().__init__(title)
        self.value: DocstringDeprecated = DocstringDeprecated(annotation=version, description=text)


class DocstringSectionAdmonition(DocstringSection):
    """This class represents an admonition section."""

    kind: DocstringSectionKind = DocstringSectionKind.admonition

    def __init__(self, kind: str, text: str, title: str | None = None) -> None:
        """Initialize the section.

        Parameters:
            kind: The admonition kind.
            text: The admonition text.
            title: An optional title.
        """
        super().__init__(title)
        self.value: DocstringAdmonition = DocstringAdmonition(annotation=kind, description=text)
