from pathlib import Path

from docutils.nodes import Text, label, reference, section
from docutils.parsers.rst import roles
from sphinx.roles import XRefRole


class autonumber(label):
    pass


class autonumber_ref(reference):
    pass


def autonumber_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
    return ([autonumber(text=text)], [])


def warn(*args, **kwargs):
    """Issue a warning/notification to the user. Same signature as `print`"""
    print("\nAutonumber: ", *args, **kwargs)


def chapter_numbers_by_section(env):
    """Collect a mapping from a section's reference key to its position in the TOC

    The reference key is of the form `f'{path-to-doc}:{section-id}'`.
    The position in the TOC is useful eg. to label sections with numbers.
    It is returned as a tuple of ints"""
    # Record the number of each chapter
    section_numbers = {}
    for doc in env.toc_secnumbers:
        sections = env.toc_secnumbers[doc]
        for section_id in sections:
            # Include the src to disambiguate duplicates
            src = env.srcdir + "/" + doc
            key = f"{src}:{section_id[1:]}"
            if key in section_numbers:
                warn(f"{section_id} is duplicated in {src}")
            section_numbers[key] = sections[section_id]
    return section_numbers


def get_chapter(node, depth, section_numbers):
    """Get the numerical position of the chapter in which node resides

    args:
        node:
            A docutils node whose chapter we want the number of
        depth:
            How many levels deep into the toctree is a "chapter"
        section_numbers:
            The output of chapter_numbers_by_section(env)
    """
    parent = node.parent
    chapter = None
    while chapter is None:
        if isinstance(parent, section):
            chapter = parent
        parent = parent.parent
    src = str(Path(chapter.source).with_suffix(""))
    chapter_id = chapter.attributes["ids"][0]
    key = src + ":" + chapter_id
    try:
        chapter = section_numbers[key][:depth]
    except KeyError:
        # The above will fail if the section is at the top of a file;
        # There doesn't seem to be a way to get the top section label
        # in chapter_numbers_by_section, so we'll just assume that if
        # the above fails, we're looking for a section with no label:
        key = src + ":"
        warn(f"Assuming {repr(chapter_id)} is a top level section")
        chapter = section_numbers[key][:depth]
    return ".".join(str(i) for i in chapter)


def proc_autonumber(app, doctree, docname):
    index = {}
    ref_table = {}
    autonum_depth = app.config.autonumber_by_depth
    if autonum_depth:
        section_numbers = chapter_numbers_by_section(app.builder.env)
        last_chapter = None

    # Assign numbers to all the autonumbered objects.

    for node in doctree.traverse(autonumber):
        category = node.astext().split(",")[0]
        if category in index:
            next_number = index[category] + 1
        else:
            next_number = 1
        if autonum_depth:
            chapter = get_chapter(node, autonum_depth, section_numbers)
            if chapter != last_chapter:
                index = {}
                next_number = 1
            new_node = Text(f"{category} {chapter}-{next_number}")
            last_chapter = chapter
        else:
            new_node = Text(f"{category} {next_number}")
        index[category] = next_number
        ref_table[node.astext()] = new_node
        node.parent.replace(node, new_node)

    # Replace references with the name of the referenced object

    for ref_info in doctree.traverse(autonumber_ref):
        target = ref_info["reftarget"]
        if target not in ref_table:
            raise ValueError(f"Unknown target for autonumber reference: {target}")
        ref_info.replace_self(Text(ref_table[target].astext()))


def setup(app):
    app.add_config_value("autonumber_by_depth", 1, "env")
    roles.register_local_role("autonumber", autonumber_role)
    app.add_node(autonumber)
    app.add_node(autonumber_ref)
    app.add_role("autonumref", XRefRole(nodeclass=autonumber_ref))
    app.connect("doctree-resolved", proc_autonumber)
