"""Build the reader's index that heads the rendered report.

The index is derived from the rendered document instead of being declared in
each task template. A template that gains a section gets an index entry with
it, and there is no second list to keep in step.

Anchors come from the id a section already carries or from its
``data-report-section`` slug — never from the heading text, which changes with
the report language and would move every link with it.
"""
from __future__ import annotations

import re

# Every section of the human view opens with its own h2. A block that does not
# is not a place the reader navigates to, so it stays out of the index.
_SECTION_HEAD_RE = re.compile(
    r'<section\b(?P<attrs>[^>]*)>(?P<heading_open>\s*<h2[^>]*>)(?P<title>.*?)</h2>',
    re.DOTALL,
)
_MAIN_OPEN_RE = re.compile(r"<main\b[^>]*>")
_ID_RE = re.compile(r'\bid="([^"]+)"')
_SLUG_RE = re.compile(r'\bdata-report-section="([^"]+)"')
_TAG_RE = re.compile(r"<[^>]+>")

INDEX_TITLE_ID = "report-index-title"
# 맨 위로 버튼 패널에 같은 목차 항목을 채우는 자리. 본문 목차와 한 함수에서 만든다.
_INDEX_ITEMS_SLOT = "<!--report-index-items-->"


def _heading_text(title_markup: str) -> str:
    """The heading's words without the markup a link cannot carry."""
    return " ".join(_TAG_RE.sub("", title_markup).split())


def inject_report_index(document: str, *, label: str) -> str:
    """Return ``document`` with a section index at the top of ``<main>``.

    The same list fills the back-to-top hover panel, so the two cannot drift.
    Sections that lack both an id and a slug are skipped rather than given a
    generated anchor: a link whose target moves between renders is worse than
    an entry the reader never had.
    """
    opening = _MAIN_OPEN_RE.search(document)
    if opening is None:
        return document
    head, body = document[: opening.end()], document[opening.end() :]
    entries: list[tuple[str, str]] = []

    def _anchor_section(match: re.Match[str]) -> str:
        attrs = match.group("attrs")
        existing = _ID_RE.search(attrs)
        if existing:
            anchor = existing.group(1)
        else:
            slug = _SLUG_RE.search(attrs)
            if slug is None:
                return match.group(0)
            anchor = f"section-{slug.group(1)}"
            # Last, not first: templates lead a section with the attribute the
            # tests and the validator select it by.
            attrs = f'{attrs} id="{anchor}"'
        entries.append((anchor, _heading_text(match.group("title"))))
        return f'<section{attrs}>{match.group("heading_open")}{match.group("title")}</h2>'

    body = _SECTION_HEAD_RE.sub(_anchor_section, body)
    if not entries:
        return document
    items = "".join(
        f'<li><a href="#{anchor}">{text}</a></li>' for anchor, text in entries
    )
    index = (
        f'<nav class="report-index" aria-labelledby="{INDEX_TITLE_ID}">'
        f'<h2 id="{INDEX_TITLE_ID}">{label}</h2>'
        f"<ol>{items}</ol>"
        "</nav>"
    )
    filled = f"{head}\n{index}{body}"
    return filled.replace(_INDEX_ITEMS_SLOT, f"<ol>{items}</ol>", 1)
