#!/usr/bin/env python3
"""Migrate inline editorial headers in apps/rep templates to use
``components/rep/editorial_header.html``.

Two pattern variants are recognised:

A) Bare header — simple <header> with eyebrow + title (+ optional italic + description).
   Replaces the entire <header>...</header> block with a clean include.

B) Header-with-actions — flex outer header that contains both the prose column
   AND a right-side action group (CTA buttons, streak chips). The script only
   replaces the prose <div class="max-w-2xl"> child with the include — the
   wrapping <header> and the right-side actions stay untouched.

Run: python scripts/migrate_editorial_headers_rep.py [--dry-run] [--limit N]

The transform is conservative: any <header> not matching one of these clean
shapes is skipped and reported in --dry-run output for manual follow-up.
"""
from __future__ import annotations

import argparse
import re
import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent

# Match a prose div whose class starts with `max-w-` containing the
# canonical editorial header pattern.
PROSE_RE = re.compile(
    r'<div class="(?P<width>max-w-[^"]+)">\s*'
    r'<p class="ed-eyebrow">(?P<eyebrow>[^<]+)</p>\s*'
    r'<h1 class="[^"]*ed-title (?P<title_size>[^"]+)">(?P<title>[^<]+)</h1>\s*'
    r'(?:<p class="[^"]*ed-italic[^"]*">(?P<italic>[^<]+)</p>\s*)?'
    r'(?:<p class="[^"]*text-ruforum-text-muted[^"]*">\s*(?P<description>[^<]+?)\s*</p>\s*)?'
    r'</div>',
    re.DOTALL,
)

# Match a bare prose <div> (no max-w-) — common in flex-with-actions headers.
# Restricted to ed-title to avoid changing font-family on `font-display`-only h1s.
BARE_DIV_RE = re.compile(
    r'<div>\s*'
    r'<p class="ed-eyebrow">(?P<eyebrow>[^<]+)</p>\s*'
    r'<h1 class="[^"]*ed-title (?P<title_size>[^"]+)">(?P<title>[^<]+)</h1>\s*'
    r'(?:<p class="[^"]*ed-italic[^"]*">(?P<italic>[^<]+)</p>\s*)?'
    r'(?:<p class="[^"]*text-ruforum-text-muted[^"]*">\s*(?P<description>[^<]+?)\s*</p>\s*)?'
    r'</div>',
    re.DOTALL,
)

# Match a bare <header> whose class is just max-w-2xl (no flex layout).
BARE_HEADER_RE = re.compile(
    r'<header class="(?:mb-\d+ )?max-w-2xl">\s*'
    r'<p class="ed-eyebrow">(?P<eyebrow>[^<]+)</p>\s*'
    r'<h1 class="[^"]*ed-title (?P<title_size>[^"]+)">(?P<title>[^<]+)</h1>\s*'
    r'(?:<p class="[^"]*ed-italic[^"]*">(?P<italic>[^<]+)</p>\s*)?'
    r'(?:<p class="[^"]*text-ruforum-text-muted[^"]*">\s*(?P<description>[^<]+?)\s*</p>\s*)?'
    r'</header>',
    re.DOTALL,
)


def _build_include(width: str, title_size: str, eyebrow: str, title: str, italic: str | None, description: str | None) -> str:
    args = [
        f'eyebrow="{eyebrow.strip()}"',
        f'title="{title.strip()}"',
    ]
    if italic:
        args.append(f'italic_lede="{italic.strip()}"')
    if description:
        args.append(f'description="{description.strip()}"')
    # Width: empty means "use partial default of max-w-2xl"; pass through other widths.
    if width and width != "max-w-2xl":
        args.append(f'max_width="{width}"')
    if width == "":
        # Bare <div> — let the partial fall back to max-w-2xl (the default).
        # If the surrounding header already constrains width, this is fine.
        pass
    if title_size and title_size != "text-[clamp(2rem,3vw,2.875rem)]":
        args.append(f'title_size="{title_size}"')
    return '{% include "components/rep/editorial_header.html" with ' + " ".join(args) + " %}"


def transform(text: str) -> tuple[str, int]:
    count = 0

    def prose_repl(match):
        nonlocal count
        # Avoid stomping on prose blocks that contain Django logic (would
        # need manual touch-up).
        body = match.group(0)
        if "{%" in body or "{{" in body:
            return body
        count += 1
        return _build_include(
            width=match.group("width"),
            title_size=match.group("title_size"),
            eyebrow=match.group("eyebrow"),
            title=match.group("title"),
            italic=match.group("italic"),
            description=match.group("description"),
        )

    def bare_repl(match):
        nonlocal count
        body = match.group(0)
        if "{%" in body or "{{" in body:
            return body
        count += 1
        include = _build_include(
            width="max-w-2xl",
            title_size=match.group("title_size"),
            eyebrow=match.group("eyebrow"),
            title=match.group("title"),
            italic=match.group("italic"),
            description=match.group("description"),
        )
        return f'<header class="mb-6">\n  {include}\n</header>'

    def bare_div_repl(match):
        nonlocal count
        body = match.group(0)
        if "{%" in body or "{{" in body:
            return body
        count += 1
        # Bare <div> with no width class — preserve the surrounding <header>'s
        # flex layout by emitting a thin wrapper without max-w-2xl forcing.
        return _build_include(
            width="",
            title_size=match.group("title_size"),
            eyebrow=match.group("eyebrow"),
            title=match.group("title"),
            italic=match.group("italic"),
            description=match.group("description"),
        )

    text = BARE_HEADER_RE.sub(bare_repl, text)
    text = PROSE_RE.sub(prose_repl, text)
    text = BARE_DIV_RE.sub(bare_div_repl, text)
    return text, count


def main(argv=None):
    parser = argparse.ArgumentParser()
    parser.add_argument("--dry-run", action="store_true")
    parser.add_argument("--limit", type=int, default=None)
    args = parser.parse_args(argv)

    files = sorted((REPO_ROOT / "apps" / "rep").rglob("*.html"))
    total = 0
    files_touched = 0
    for path in files:
        text = path.read_text(encoding="utf-8")
        new_text, n = transform(text)
        if n == 0:
            continue
        files_touched += 1
        total += n
        if args.dry_run:
            print(f"  would migrate {n}: {path.relative_to(REPO_ROOT)}")
        else:
            path.write_text(new_text, encoding="utf-8")
            print(f"  migrated {n}: {path.relative_to(REPO_ROOT)}")
        if args.limit and files_touched >= args.limit:
            break
    print(f"\n{total} editorial header{'' if total == 1 else 's'} migrated across {files_touched} files.")
    return 0


if __name__ == "__main__":
    sys.exit(main())
