from __future__ import annotations

from typing import TYPE_CHECKING

from sphinx.ext.autodoc._property_types import (
    _AssignStatementProperties,
    _ClassDefProperties,
    _FunctionDefProperties,
    _TypeStatementProperties,
)
from sphinx.ext.autodoc._sentinels import SUPPRESS
from sphinx.locale import _

if TYPE_CHECKING:
    from collections.abc import Iterator
    from typing import Literal

    from docutils.statemachine import StringList

    from sphinx.ext.autodoc._directive_options import _AutoDocumenterOptions
    from sphinx.ext.autodoc._property_types import _ItemProperties


def _directive_header_lines(
    *,
    autodoc_typehints: Literal['signature', 'description', 'none', 'both'],
    directive_name: str,
    is_final: bool,
    options: _AutoDocumenterOptions,
    props: _ItemProperties,
) -> Iterator[str]:
    """Emit the directive header and option lines."""
    # normally the name doesn't contain the module
    # (except for module directives of course)
    name = props.dotted_parts or props.module_name

    # emit one signature per line
    # the first line contains the directive prefix
    sig_line, *sig_lines = props.signatures or ('',)
    prefix = f'.. {directive_name}:: '
    yield f'{prefix}{name}{sig_line}'
    # emit remaining lines, indented to the same column
    prefix = ' ' * len(prefix)
    for sig_line in sig_lines:
        yield f'{prefix}{name}{sig_line}'

    if options.no_index or options.noindex:
        yield '   :no-index:'
    if options.no_index_entry:
        yield '   :no-index-entry:'
    if props.parts:
        # Be explicit about the module, this is necessary since .. class::
        # etc. don't support a prepended module name
        yield f'   :module: {props.module_name}'

    if props.obj_type == 'module':
        # add some module-specific options
        if options.synopsis:
            yield f'   :synopsis: {options.synopsis}'
        if options.platform:
            yield f'   :platform: {options.platform}'
        if options.deprecated:
            yield '   :deprecated:'
        if options.no_index_entry:
            yield '   :no-index-entry:'

    if props.obj_type in {'class', 'exception'}:
        assert isinstance(props, _ClassDefProperties)

        if props._obj_is_new_type or props._obj_is_typevar:
            return

        if is_final:
            yield '   :final:'

        canonical_fullname = props.canonical_full_name
        if (
            not props.doc_as_attr
            and not props._obj_is_new_type
            and canonical_fullname
            and props.full_name != canonical_fullname
        ):
            yield f'   :canonical: {canonical_fullname}'

        # add inheritance info, if wanted
        if not props.doc_as_attr and options.show_inheritance:
            yield ''
            yield '   ' + _('Bases: %s') % ', '.join(props._obj_bases)

    if props.obj_type in {'function', 'decorator'}:
        assert isinstance(props, _FunctionDefProperties)

        if props.is_async:
            yield '   :async:'

    if props.obj_type == 'method':
        assert isinstance(props, _FunctionDefProperties)

        if props.is_abstractmethod:
            yield '   :abstractmethod:'
        if props.is_async:
            yield '   :async:'
        if props.is_classmethod:
            yield '   :classmethod:'
        if props.is_staticmethod:
            yield '   :staticmethod:'
        if props.is_final or is_final:
            yield '   :final:'

    if props.obj_type == 'property':
        assert isinstance(props, _FunctionDefProperties)

        if props.is_abstractmethod:
            yield '   :abstractmethod:'
        if props.is_classmethod:
            yield '   :classmethod:'

        objrepr = props._obj_property_type_annotation
        if autodoc_typehints != 'none' and objrepr is not None:
            yield f'   :type: {objrepr}'

    if props.obj_type == 'data':
        assert isinstance(props, _AssignStatementProperties)

        if options.annotation is SUPPRESS or props._obj_is_generic_alias:
            pass
        elif options.annotation:
            yield f'   :annotation: {options.annotation}'
        else:
            type_annotation = props._obj_type_annotation
            if autodoc_typehints != 'none' and type_annotation is not None:
                yield f'   :type: {type_annotation}'

            if (
                not options.no_value
                and props._obj_is_sentinel is None  # not any sentinel
                and not props._docstrings_has_hide_value
                and not props._obj_is_mock
            ):
                yield f'   :value: {props._obj_repr_rst}'

    if props.obj_type == 'attribute':
        assert isinstance(props, _AssignStatementProperties)

        if (
            options.annotation
            and options.annotation is not SUPPRESS
            and not props._obj_is_generic_alias
        ):
            yield f'   :annotation: {options.annotation}'
        else:
            type_annotation = props._obj_type_annotation
            if autodoc_typehints != 'none' and type_annotation is not None:
                yield f'   :type: {type_annotation}'

            if (
                not options.no_value
                and props._obj_is_sentinel is None  # not any sentinel
                and not props._obj_is_attribute_descriptor
                and not props._obj_is_generic_alias
                and not props._docstrings_has_hide_value
                and not props._obj_is_mock
            ):
                yield f'   :value: {props._obj_repr_rst}'

    if props.obj_type == 'type':
        assert isinstance(props, _TypeStatementProperties)

        if not options.no_value and not props._docstrings_has_hide_value:
            yield f'   :canonical: {props._obj___value__}'


def _add_content(content: StringList, *, result: StringList, indent: str) -> None:
    for line, src in zip(content.data, content.items, strict=True):
        if line.strip():  # not a blank line
            result.append(indent + line, src[0], src[1])
        else:
            result.append('', src[0], src[1])
