import re

from .. import config, h
from ..messages import *


def addAttributeInfoSpans(doc):
    # <dt><dfn attribute> automatically gets a <span attribute-info> appended to it
    # (and same for <dfn dict-member> and <span dict-member-info>).
    for dt in h.findAll("dt", doc):
        dfn = h.find("dfn", dt)
        if dfn is None:
            continue
        if h.find("span[data-attribute-info], span[data-dict-member-info]", dt) is not None:
            # Already has a span, nothing to add
            continue
        dfnType = dfn.get("data-dfn-type")
        if dfnType == "attribute":
            attrName = "data-attribute-info"
        elif dfnType == "dict-member":
            attrName = "data-dict-member-info"
        else:
            continue
        spanFor = config.firstLinkTextFromElement(dfn)
        if spanFor is None:
            continue
        # Internal slots (denoted by [[foo]] naming scheme) don't have attribute info
        if spanFor.startswith("[["):
            continue
        if dfn.get("data-dfn-for"):
            spanFor = dfn.get("data-dfn-for") + "/" + spanFor
        # If there's whitespace after the dfn, clear it out
        if h.emptyText(dfn.tail, wsAllowed=True):
            dfn.tail = None
        h.insertAfter(dfn, ", ", h.E.span({attrName: "", "for": spanFor}))


def fillAttributeInfoSpans(doc):
    for el in h.findAll("span[data-attribute-info], span[data-dict-member-info]", doc):
        if not h.isEmpty(el):
            # Manually filled, bail
            return

        info = getTargetInfo(doc, el)
        if info is None:
            continue

        h.appendChild(el, htmlFromInfo(info))


def getTargetInfo(doc, el):
    if el.get("data-attribute-info") is not None:
        refType = "attribute"
    else:
        refType = "dict-member"

    referencedAttribute = el.get("for")
    if not referencedAttribute:
        die("Missing for='' reference in attribute info span.", el=el)
        return
    if "/" in referencedAttribute:
        interface, referencedAttribute = referencedAttribute.split("/")
        targets = h.findAll(
            'a[data-link-type={2}][data-lt="{0}"][data-link-for="{1}"]'.format(referencedAttribute, interface, refType),
            doc,
        )
    else:
        targets = h.findAll(
            f'a[data-link-type={refType}][data-lt="{referencedAttribute}"]',
            doc,
        )

    if len(targets) == 0:
        die(f"Couldn't find target {referencedAttribute} '{refType}':\n{h.outerHTML(el)}", el=el)
        return
    elif len(targets) > 1:
        die(f"Multiple potential target {referencedAttribute}s '{refType}':\n{h.outerHTML(el)}", el=el)
        return

    target = targets[0]

    info = {}
    info["type"] = target.get("data-type").strip()
    if info["type"].endswith("?"):
        info["nullable"] = True
        info["type"] = info["type"][:-1]
    else:
        info["nullable"] = False
    info["default"] = target.get("data-default")
    info["readonly"] = target.get("data-readonly") is not None
    return info


def htmlFromInfo(info):
    deco = []
    if re.match(r"\w+<\w+(\s*,\s*\w+)*>", info["type"]):
        # Simple higher-kinded types
        match = re.match(r"(\w+)<(\w+(?:\s*,\s*\w+)*)>", info["type"])
        types = [h.E.a({"data-link-type": "idl-name"}, x.strip()) for x in match.group(2).split(",")]
        deco.extend([" of type ", match.group(1), "<", config.intersperse(types, ", "), ">"])
    elif "<" in info["type"] or "(" in info["type"]:
        # Unions or more-complex higher-kinded types
        # Currently just bail, but I need to address this at some point.
        deco.extend(
            [
                " of type ",
                h.E.code({"class": "idl-code"}, info["type"]),
            ]
        )
    else:
        # Everything else
        deco.extend(
            [
                " of type ",
                h.E.a({"data-link-type": "idl-name"}, info["type"]),
            ]
        )

    if info["readonly"]:
        deco.append(", readonly")
    if info["nullable"]:
        deco.append(", nullable")
    if info["default"] is not None:
        deco.append(", defaulting to ")
        deco.append(h.E.code(info["default"]))

    return deco
