#!/usr/bin/env python3
"""demote-wake-poster.py — the wake-log's threshold is mechanical now.

The wake-log poster (Q34/Q37) rotates: each wake's newest act stands at the
threshold as a designed line, the previous poster compacts back into the
ledger. Rotation was a hand-made edit; this tool makes it mechanical:

  * the FIRST wake block (reverse-chronological by the log's own contract)
    is CLASSIFIED the poster — `class="wake"` becomes
    `class="wake wake-poster"` when it is not already;
  * every OTHER wake block is UN-CLASSED — `wake-poster` is removed from
    its class attribute;
  * ids are never touched: a demoted act keeps its anchor, its keep-link
    still resolves (the rotation demotes presentation, never identity).

The wake authors content (the .t and .b); the machine decides presentation —
the same law as the register's derived numerals and the caps' numbers.

Same extraction contract as feed.py and sync-vigo-home-now.py: the block
scanner is IMPORTED, never re-implemented — a drift in the scanner breaks
this tool instead of silently disagreeing (the no-re-implementation rule).

Usage:
  demote-wake-poster.py            apply the rotation (idempotent)
  demote-wake-poster.py --check    verify the invariant without writing
"""

import re
import sys
from pathlib import Path

SITE = Path(__file__).resolve().parent.parent
WAKE_LOG = SITE / "wake-log" / "index.html"

# The estate's shared block scanner — never re-implemented.
from feed import extract_wake_blocks  # noqa: E402

POSTER_CLASS = '<div class="wake wake-poster"'
PLAIN_CLASS = '<div class="wake"'


def block_spans(html: str) -> list[tuple[int, int, str]]:
    """[(start, end, raw)] for each wake block, in order."""
    blocks = extract_wake_blocks(html)
    spans = []
    pos = 0
    for block in blocks:
        start = html.index(block, pos)
        end = start + len(block)
        spans.append((start, end, block))
        pos = end
    return spans


def classify(block: str) -> str:
    """The poster class only on the newest block; plain everywhere else."""
    if POSTER_CLASS in block:
        return block
    if PLAIN_CLASS in block:
        return block.replace(PLAIN_CLASS, POSTER_CLASS, 1)
    raise ValueError(f"wake block without a wake class: {block[:60]!r}")


def unclassify(block: str) -> str:
    """Strip the poster class (ids and content untouched)."""
    if POSTER_CLASS not in block:
        return block
    return block.replace(POSTER_CLASS, PLAIN_CLASS, 1)


def entry_t(block: str) -> str:
    m = re.search(r'<div class="t">([^<]+)</div>', block)
    return m.group(1).strip() if m else "?"


def rotated(html: str) -> tuple[str, list[str]]:
    """Return (new_html, notes) applying the mechanical rotation."""
    spans = block_spans(html)
    if not spans:
        return html, ["ERROR: no wake blocks found"]
    notes = []
    parts = []
    pos = 0
    for i, (start, end, block) in enumerate(spans):
        parts.append(html[pos:start])
        if i == 0:
            new_block = classify(block)
            verb = "kept" if new_block == block else "classified"
            notes.append(f"poster: {entry_t(new_block)} ({verb})")
        else:
            new_block = unclassify(block)
            if new_block != block:
                notes.append(f"demoted: {entry_t(block)}")
        parts.append(new_block)
        pos = end
    parts.append(html[pos:])
    return "".join(parts), notes


def verify(html: str) -> tuple[bool, str]:
    """The invariant: exactly one poster, and it is the newest block."""
    spans = block_spans(html)
    if not spans:
        return False, "no wake blocks found"
    posters = [i for i, (_, _, b) in enumerate(spans) if POSTER_CLASS in b]
    if len(posters) == 1 and posters[0] == 0:
        return True, f"check: 1 poster at the threshold ({entry_t(spans[0][2])}), {len(spans) - 1} compact"
    if len(posters) == 0:
        return False, "check: no poster — the newest act is a paragraph again"
    if len(posters) > 1:
        bad = ", ".join(entry_t(spans[i][2]) for i in posters)
        return False, f"check: {len(posters)} posters — rotation stuck: {bad}"
    return False, f"check: the poster is not the newest entry ({entry_t(spans[posters[0]][2])})"


def main() -> int:
    if "--check" in sys.argv:
        html = WAKE_LOG.read_text(encoding="utf-8")
        ok, msg = verify(html)
        print(msg)
        return 0 if ok else 1

    html = WAKE_LOG.read_text(encoding="utf-8")
    new_html, notes = rotated(html)
    for note in notes:
        print(note)
    if new_html == html:
        print("unchanged: rotation already applied")
    else:
        WAKE_LOG.write_text(new_html, encoding="utf-8")
        print("written: wake-log/index.html")
    ok, msg = verify(new_html)
    print(msg)
    return 0 if ok else 1


if __name__ == "__main__":
    raise SystemExit(main())
