import typing as t

import griffe


class SimplifiedDocstring(t.NamedTuple):
    description: str | None
    parameters: list[dict[str, str | None]]
    returns: dict[str, str | None]
    attributes: list[dict[str, str | None]]
    remainder: list[griffe.DocstringSection]


def simplify_docstring(
    doc: griffe.Docstring, parent: griffe.Module | griffe.Class | griffe.Function = None
) -> SimplifiedDocstring:
    def get_parameters_from_signature(parent: griffe.Class | griffe.Function):
        return [
            {
                "name": p.name,
                "annotation": p.annotation,
                "description": None,
                "value": p.default,
            }
            for p in parent.parameters
        ]

    def get_returns_from_signature(parent: griffe.Function):
        return {
            "name": "",
            "annotation": (
                parent.returns
                if isinstance(parent.returns, (str, type(None)))
                else "".join(
                    elem if isinstance(elem, str) else elem.canonical_path
                    for elem in parent.returns.iterate(flat=True)
                )
            ),
            "description": None,
        }

    def get_attributes_from_signature(parent: griffe.Module | griffe.Class):
        return [
            {
                "name": attr.name,
                "annotation": attr.annotation,
                "description": attr.docstring.parsed if attr.docstring else None,
                "value": attr.value,
            }
            for attr in parent.attributes.values()
            if (not attr.is_alias and not attr.is_private)
        ]

    description = None
    parameters = (
        get_parameters_from_signature(parent)
        if isinstance(parent, (griffe.Class, griffe.Function))
        else None
    )
    attributes = (
        get_attributes_from_signature(parent)
        if isinstance(parent, (griffe.Class, griffe.Module))
        else None
    )
    returns = (
        get_returns_from_signature(parent)
        if isinstance(parent, (griffe.Function))
        else None
    )
    remainder = []
    if not doc:
        return SimplifiedDocstring(
            description, parameters, returns, attributes, remainder
        )

    for i, sec in enumerate(doc.parsed):
        if sec.kind == "text" and i == 0:
            description = sec.value
            continue

        # Sort the parameters with the real signature
        if sec.kind == "parameters":
            map = {i.name: i for i in sec.value}
            params_list = []
            for param in parent.parameters:
                if param.name in map:
                    docstring = map[param.name]
                    try:
                        docstring.description = griffe.parse_google(
                            griffe.Docstring(docstring.description)
                        )
                    except AttributeError:
                        pass
                    params_list.append(docstring)
                else:
                    params_list.append(
                        {
                            "name": param.name,
                            "annotation": param.annotation,
                            "description": None,
                            "value": param.default,
                        }
                    )

            parameters = params_list
            continue

        if sec.kind == "returns":
            returns: griffe.DocstringReturn = sec.value[0]
            returns.annotation = (
                returns.annotation.canonical_path
                if isinstance(returns.annotation, griffe.Expr)
                else returns.annotation
            )
            continue

        if sec.kind == "attributes":
            map = {i.name: i for i in sec.value}
            attributes_list = []
            for attr in parent.attributes.values():
                # exclude aliased attributes
                if attr.is_alias:
                    continue

                if attr.name in map:
                    attr_in_docstring: dict = map[attr.name]
                    attr_item: dict = {
                        "name": attr_in_docstring.name,
                        "description": None,
                        "annotation": attr_in_docstring.annotation,
                        "value": attr.value,
                    }

                    try:
                        attr_item["description"] = griffe.parse_google(
                            griffe.Docstring(attr_in_docstring.description)
                        )
                    except AttributeError:
                        pass

                    attributes_list.append(attr_item)
                else:
                    attributes_list.append(
                        {
                            "name": attr.name,
                            "annotation": attr.annotation,
                            "description": (
                                attr.docstring.parsed if attr.docstring else None
                            ),
                            "value": attr.value,
                        }
                    )
            attributes = attributes_list
            continue

        remainder.append(sec)

    return SimplifiedDocstring(description, parameters, returns, attributes, remainder)
