"""``attrs`` class wrapper to create a docstring from the metadata."""
import textwrap
import warnings

import attr


def type_to_sphinx(typ, field_name):
    """Convert a type object to a string acceptable by Sphinx."""
    # TODO better implementation of type_to_sphinx
    if typ is None:
        field_type = "Any"
        # warnings.warn(f'Field "{field_name}" has no declared type.')
    elif getattr(typ, "__module__", None) == "typing":
        field_type = str(typ).replace("typing.", "")
        if field_type.startswith("Union["):
            field_type = field_type[6:-1]
    elif (
        hasattr(typ, "__name__")
        and getattr(typ, "__module__", "builtins") != "builtins"
    ):
        field_type = f"{typ.__module__}.{typ.__name__}"
    elif hasattr(typ, "__name__"):
        field_type = typ.__name__
    else:
        warnings.warn(f'Field "{field_name}" type could not be converted.')
        field_type = "Any"
    return field_type


def autodoc(attrs_class):
    """Decorate an :class:`attr.s` class to update its docstring with attributes.

    The docstring will include the :class:`attributes <attr.ib>`,
    and emits warnings when partially undocumented attributes are found.

    If the class contains any attributes,
    they are documented as *constructor parameters* in a *Parameters* section,
    added at the end of the class' docstring.
    In order to extract the most information per parameter description, this
    decorator assumes that every attribute

    * Typed (either via the ``type`` argument, or via `PEP 526`_ type-annotations), and
    * Contains a metadata dictionary with the keys

        help:
            The string that describes the attribute,
            and will go in the description of the corresponding parameter.

    Default values are, of course, optional, and if provided,
    the parameter's description will inform this.
    Private attributes will have their names rendered correctly (no leading underscore),
    and non-private attributes help-text will be complemented with a reminder that
    the parameter's value can be later accessed via an attribute with that name.

    .. warning::

        Currently, there is no support for indicating that arguments are keyword-only,
        hashable, or validated.
        These and other exceptional conditions should be informed in the docstring.

    .. _PEP 526: https://www.python.org/dev/peps/pep-0526/
    """
    # noqa: D202

    def fix_indent(docstring):
        lines = docstring.split("\n")

        if len(lines) > 1:
            for i in range(1, len(lines)):
                if lines[i]:
                    break
            parts = ["\n".join(lines[:i]), textwrap.dedent("\n".join(lines[i:]))]
            result = "\n".join(p for p in parts if p)
            return result
        else:
            return lines[0]

    def param_doc(field):
        field_fn = f"{attrs_class.__module__}.{attrs_class.__name__}.{field.name}"
        help_complement = "  "

        name = field.name[1:] if field.name[0] == "_" else field.name
        field_type = type_to_sphinx(field.type, field_fn)
        type_line = f":type {name}: {field_type}"

        if field.default is not attr.NOTHING:
            if isinstance(field.default, str):
                help_complement += f'(default: "{field.default}")'
            else:
                help_complement += f"(default: {field.default})"

        if "doc" in field.metadata:
            description = textwrap.indent(
                textwrap.fill(
                    text=(field.metadata["doc"] + " " + help_complement).strip(),
                    width=88,
                ),
                prefix=" " * 4,
            )
        else:
            description = help_complement

        descript_line = f":param {name}: {description}"

        return descript_line + "\n" + type_line

    if attr.fields(attrs_class):
        params_section = "\n\n".join(
            param_doc(field) for field in attr.fields(attrs_class)
        )
    else:
        params_section = ""

    if attrs_class.__doc__ and params_section:
        attrs_class.__doc__ = (
            f"{fix_indent(attrs_class.__doc__).rstrip()}\n\n{params_section}"
        )
    elif params_section:
        attrs_class.__doc__ = params_section

    return attrs_class
