#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
#   "python-docx>=1.1",
#   "markdown-it-py>=3.0",
# ]
# ///
"""
Apply Gemma Analytics branding to a document.

Usage:
  uv run apply_branding.py <input.md> [output.html]
  uv run apply_branding.py <input.md> --format docx [output.docx]
  uv run apply_branding.py <input.md> --dark-proposal [output.html]

Cover modes (mutually exclusive; the default is a white document cover):
  --plum-cover      Midnight Plum cover page. For actual slides, use the
                    `presentation` skill instead; this only recolours a cover.
  --dark-proposal   Dark proposal theme (#160329 page fill). HTML only.
  --one-pager       Compact header instead of a cover page. HTML only.

Dark proposal options:
  --language en|de        Cover and running-header labels. Default: en.
  --no-standard-sections  Skip the auto-appended Ways of Working and
                          About Gemma closing sections.

DOCX supports headings, lists, `**highlight**` spans and Markdown pipe tables.
It does not support the dark proposal theme, the one-pager layout, or inlined
SVGs; the HTML-only flags are rejected rather than silently ignored.

Based on:
  - Corporate Manual: https://drive.google.com/file/d/1_w5OzlKXGgbPBt3GopSbyqc5pzye0SBg/view?usp=drive_link
  - Google Docs Template: https://docs.google.com/document/d/1qEIZ721NTJiGtJFcpx6Ffa2fx7gZFUv8bsB-zelErgc/
"""

import argparse
import re
import sys
from datetime import datetime
from itertools import count
from pathlib import Path

SKILL_DIR = Path(__file__).parent.parent
FONTS_DIR = SKILL_DIR / "assets" / "fonts"

# Brand colours from corporate manual
COLORS = {
    "midnight_plum": "2F0559",
    "super_purple":  "883DD8",
    "lavender":      "A976E5",
    "fresh_teal":    "2D9B8B",
    "tingy_coral":   "ED5C58",
    "sunny_yellow":  "FFE000",
    "black":         "000000",
    "white":         "FFFFFF",
}

# Typography — font face names match the TTF files in assets/fonts/
# Headings (H1–H4) and Title: "Kanit Medium"
# Body / Subtitle:            "Kanit ExtraLight"
# Highlight (strong):         "Kanit" (Regular)
FONT_MEDIUM     = "Kanit Medium"
FONT_EXTRALIGHT = "Kanit ExtraLight"
FONT_REGULAR    = "Kanit"


# ─── DOCX ─────────────────────────────────────────────────────────────────────

def _cover_page(doc, title: str, subtitle: str, date: str, plum_cover: bool):
    from docx.shared import Pt, RGBColor
    from docx.enum.text import WD_ALIGN_PARAGRAPH
    from docx.oxml.ns import qn
    from docx.oxml import OxmlElement

    def _add_shading(para, fill_hex: str):
        pPr = para._p.get_or_add_pPr()
        shd = OxmlElement("w:shd")
        shd.set(qn("w:val"), "clear")
        shd.set(qn("w:color"), "auto")
        shd.set(qn("w:fill"), fill_hex)
        pPr.append(shd)

    if plum_cover:
        # Presentation mode: Midnight Plum background, white text
        title_color    = RGBColor(0xFF, 0xFF, 0xFF)
        subtitle_color = RGBColor(0xFF, 0xFF, 0xFF)
        meta_color     = RGBColor(0xFF, 0xFF, 0xFF)
        bg_fill        = COLORS["midnight_plum"]
    else:
        # Document mode: no background
        title_color    = RGBColor(0x88, 0x3D, 0xD8)  # Super Purple #883DD8
        subtitle_color = RGBColor(0x00, 0x00, 0x00)  # Black #000000
        meta_color     = RGBColor(0x00, 0x00, 0x00)  # Black #000000
        bg_fill        = None

    # Title: Kanit Medium, 28pt, uppercase, centred
    cover = doc.add_paragraph()
    cover.alignment = WD_ALIGN_PARAGRAPH.CENTER
    if bg_fill:
        _add_shading(cover, bg_fill)
    run = cover.add_run(title.upper())
    run.font.name = FONT_MEDIUM
    run.font.size = Pt(28)
    run.font.bold = False
    run.font.color.rgb = title_color

    doc.add_paragraph()

    # Subtitle: Kanit ExtraLight 10pt black (document) /
    # [IMPL] Kanit Medium 14pt white (plum cover, matches H3 size), centred
    sub = doc.add_paragraph()
    sub.alignment = WD_ALIGN_PARAGRAPH.CENTER
    if bg_fill:
        _add_shading(sub, bg_fill)
    r = sub.add_run(subtitle)
    if plum_cover:
        r.font.name = FONT_MEDIUM
        r.font.size = Pt(14)
    else:
        r.font.name = FONT_EXTRALIGHT
        r.font.size = Pt(10)
    r.font.bold = False
    r.font.color.rgb = subtitle_color

    # Meta line: Kanit ExtraLight, 8pt — same typeface as subtitle but smaller
    meta = doc.add_paragraph()
    if bg_fill:
        _add_shading(meta, bg_fill)
    r = meta.add_run(f"{date}  ·  Gemma Analytics GmbH  ·  gemmaanalytics.com")
    r.font.name = FONT_EXTRALIGHT
    r.font.size = Pt(8)
    r.font.bold = False
    r.font.color.rgb = meta_color

    doc.add_page_break()


def _apply_heading_style(para, level: int):
    from docx.shared import Pt, RGBColor
    run = para.runs[0] if para.runs else para.add_run(para.text)
    # All headings: Kanit Medium, uppercase
    run.font.name = FONT_MEDIUM
    run.font.bold = False
    run.text = run.text.upper()
    colors = {
        1: RGBColor(0x88, 0x3D, 0xD8),  # #883dd8 Super Purple
        2: RGBColor(0x2F, 0x05, 0x59),  # #2f0559 Midnight Plum
        3: RGBColor(0x00, 0x00, 0x00),  # #000000 Black
        4: RGBColor(0x00, 0x00, 0x00),  # #000000 Black
    }
    sizes = {1: 18, 2: 16, 3: 14, 4: 12}
    run.font.color.rgb = colors.get(level, RGBColor(0x00, 0x00, 0x00))
    run.font.size = Pt(sizes.get(level, 12))


def _apply_body_style(para):
    from docx.shared import Pt, RGBColor
    from docx.enum.text import WD_ALIGN_PARAGRAPH
    para.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
    for run in para.runs:
        if run.bold:
            # Highlight: Kanit Regular, 12pt
            run.font.name = FONT_REGULAR
            run.bold = False
        else:
            # Body: Kanit ExtraLight, 12pt
            run.font.name = FONT_EXTRALIGHT
        run.font.size = Pt(12)
        run.font.color.rgb = RGBColor(0x00, 0x00, 0x00)


# ─── DOCX: inline Markdown and pipe tables ────────────────────────────────────
# python-docx does not parse Markdown. A single add_paragraph(line) call yields
# one run holding the raw text, so `**text**` reaches Word as literal asterisks
# and a pipe table lands as a paragraph of `| a | b |`. These helpers split
# emphasis into its own runs and build real Word tables instead.

_BOLD_RE      = re.compile(r"\*\*(.+?)\*\*")
_TABLE_SEP_RE = re.compile(r"^\|(?:\s*:?-+:?\s*\|)+$")


def _strip_emphasis(text: str) -> str:
    """Drop `**` markers — used for headings, which carry their own weight."""
    return _BOLD_RE.sub(r"\1", text)


def _add_markdown_runs(para, text: str):
    """Add `text` to `para`, giving each `**bold**` span its own bold run.

    The caller's style pass maps bold runs to Kanit Regular (the Highlight style)
    and the rest to Kanit ExtraLight.
    """
    pos = 0
    for m in _BOLD_RE.finditer(text):
        if m.start() > pos:
            para.add_run(text[pos:m.start()])
        para.add_run(m.group(1)).bold = True
        pos = m.end()
    if pos < len(text):
        para.add_run(text[pos:])
    if not para.runs:
        para.add_run("")


def _split_table_row(line: str):
    return [c.strip() for c in line.strip().strip("|").split("|")]


def _add_table(doc, header, body):
    from docx.shared import Pt, RGBColor
    from docx.enum.text import WD_ALIGN_PARAGRAPH

    def fill_cell(cell, text: str, is_header: bool):
        para = cell.paragraphs[0]
        para.alignment = WD_ALIGN_PARAGRAPH.LEFT
        _add_markdown_runs(para, text)
        for run in para.runs:
            # Header row reads as Highlight; body cells as body text.
            run.font.name = FONT_REGULAR if (is_header or run.bold) else FONT_EXTRALIGHT
            run.bold = False
            run.font.size = Pt(10)
            run.font.color.rgb = RGBColor(0x00, 0x00, 0x00)

    table = doc.add_table(rows=1, cols=len(header))
    table.style = "Table Grid"
    for ci, text in enumerate(header):
        fill_cell(table.rows[0].cells[ci], text, is_header=True)
    for row in body:
        cells = table.add_row().cells
        for ci in range(len(header)):
            fill_cell(cells[ci], row[ci] if ci < len(row) else "", is_header=False)
    return table


def build_docx(md_text: str, output_path: Path, title: str, subtitle: str,
               date: str, plum_cover: bool):
    from docx import Document
    from docx.shared import Cm

    doc = Document()

    section = doc.sections[0]
    section.page_width  = Cm(21)
    section.page_height = Cm(29.7)
    section.left_margin   = Cm(2.5)
    section.right_margin  = Cm(2.5)
    section.top_margin    = Cm(2.0)
    section.bottom_margin = Cm(2.0)

    _cover_page(doc, title, subtitle, date, plum_cover)

    lines = md_text.splitlines()
    i = 0
    while i < len(lines):
        line = lines[i].rstrip()

        # Pipe table: a header row followed by a `|---|---|` separator row.
        if (line.startswith("|") and i + 1 < len(lines)
                and _TABLE_SEP_RE.match(lines[i + 1].strip())):
            header = _split_table_row(line)
            i += 2
            body = []
            while i < len(lines) and lines[i].strip().startswith("|"):
                body.append(_split_table_row(lines[i]))
                i += 1
            _add_table(doc, header, body)
            continue

        if line.startswith("#### "):
            p = doc.add_heading(_strip_emphasis(line[5:]), level=4)
            _apply_heading_style(p, 4)
        elif line.startswith("### "):
            p = doc.add_heading(_strip_emphasis(line[4:]), level=3)
            _apply_heading_style(p, 3)
        elif line.startswith("## "):
            p = doc.add_heading(_strip_emphasis(line[3:]), level=2)
            _apply_heading_style(p, 2)
        elif line.startswith("# "):
            p = doc.add_heading(_strip_emphasis(line[2:]), level=1)
            _apply_heading_style(p, 1)
        elif line.startswith("- ") or line.startswith("* "):
            p = doc.add_paragraph(style="List Bullet")
            _add_markdown_runs(p, line[2:])
            _apply_body_style(p)
        elif re.match(r"^\d+\. ", line):
            p = doc.add_paragraph(style="List Number")
            _add_markdown_runs(p, re.sub(r"^\d+\. ", "", line))
            _apply_body_style(p)
        elif line == "":
            doc.add_paragraph()
        else:
            p = doc.add_paragraph()
            _add_markdown_runs(p, line)
            _apply_body_style(p)
        i += 1

    doc.save(output_path)
    print(output_path)


# ─── HTML ─────────────────────────────────────────────────────────────────────

def _inline_svgs(html: str, asset_base: Path) -> str:
    """Replace <img src="*.svg"> with inlined <svg> elements.

    - CSS class-based fills are flattened to direct fill= attributes.
    - The class attribute from <img> is transferred to <svg> so CSS sizing applies.
    - No inline style is added — the CSS class is the sole source of sizing/layout.
    - Every `id` is namespaced per SVG, because the brand assets are Illustrator
      exports that all ship `id="Ebene_1"` and generic gradient ids.
    """
    counter = count(1)

    def flatten_svg(svg_text: str, prefix: str) -> str:
        # Capture literal hex fills and gradient/pattern references alike.
        # Illustrator writes a gradient fill as `fill: url(#Unbenannter_Verlauf_27)`
        # in the class rule; dropping it leaves the shape with no fill attribute,
        # which SVG renders as solid black.
        fill_map = {}
        for m in re.finditer(
            r'\.([\w-]+)\s*\{[^}]*fill:\s*(url\(#[^)]+\)|#[0-9a-fA-F]{3,6})[^}]*\}',
            svg_text
        ):
            fill_map[m.group(1)] = m.group(2)
        svg_text = re.sub(r'<style>.*?</style>', '', svg_text, flags=re.DOTALL)
        def replace_class(m):
            for cls in m.group(1).split():
                if cls in fill_map:
                    return f'fill="{fill_map[cls]}"'
            return ''
        svg_text = re.sub(r'class="([^"]+)"', replace_class, svg_text)
        # Namespace every id and every internal reference to it. Without this a
        # page holding two inlined SVGs has duplicate ids, and `url(#…)` resolves
        # to whichever SVG was inlined first.
        svg_text = re.sub(r'(\sid=)"([^"]+)"',
                          lambda m: f'{m.group(1)}"{prefix}{m.group(2)}"', svg_text)
        svg_text = re.sub(r'url\(#([^)]+)\)',
                          lambda m: f'url(#{prefix}{m.group(1)})', svg_text)
        svg_text = re.sub(r'(href=)"#([^"]+)"',
                          lambda m: f'{m.group(1)}"#{prefix}{m.group(2)}"', svg_text)
        # The XML declaration is only valid at the top of a standalone document;
        # inside HTML it renders as a bogus tag.
        svg_text = re.sub(r'<\?xml[^>]*\?>', '', svg_text)
        return svg_text.strip()

    def inline_one(m):
        img_tag = m.group(0)
        src = m.group(1)
        svg_file = Path(src) if Path(src).is_absolute() else asset_base / src
        if not svg_file.exists() or svg_file.suffix != '.svg':
            return img_tag
        svg_text = flatten_svg(svg_file.read_text(encoding="utf-8"),
                               f"svg{next(counter)}-")
        cls_match = re.search(r'class="([^"]+)"', img_tag)
        if cls_match:
            svg_text = re.sub(
                r'<svg\b',
                f'<svg class="{cls_match.group(1)}"',
                svg_text, count=1
            )
        return svg_text

    return re.sub(r'<img\s[^>]*src="([^"]+\.svg)"[^>]*/?>', inline_one, html)


def _inline_fonts(html: str, asset_base: Path) -> str:
    """Replace font file URLs with base64 data URIs for self-contained output."""
    import base64

    def encode_font(m):
        url = m.group(1)
        font_file = Path(url) if Path(url).is_absolute() else asset_base / url
        if not font_file.exists():
            return m.group(0)
        data = base64.b64encode(font_file.read_bytes()).decode()
        return f"url('data:font/truetype;base64,{data}')"

    return re.sub(r"url\('([^']+\.ttf)'\)", encode_font, html)


def _standard_proposal_sections(language: str, white_logo: Path) -> str:
    """Ways of Working + About Gemma closing — always appended to dark proposals,
    mirroring proposal-en.typ. Language-aware (en/de)."""
    if language == "de":
        wow = (
            "<h1>Arbeitsweise</h1>\n"
            "<table><thead><tr><th>Bereich</th><th>Ansatz</th></tr></thead><tbody>"
            "<tr><td><strong>Kommunikation</strong></td><td>Gemeinsamer Slack-Channel für den täglichen Austausch</td></tr>"
            "<tr><td><strong>Sync-Rhythmus</strong></td><td>Wöchentlicher Sync-Call zur Fortschrittskontrolle</td></tr>"
            "<tr><td><strong>Code Review</strong></td><td>Alle Arbeit über Pull Requests mit Review vor dem Merge</td></tr>"
            "<tr><td><strong>Dokumentation</strong></td><td>Alle Arbeit auf Handoff-Niveau dokumentiert</td></tr>"
            "</tbody></table>\n"
        )
        about = (
            "<h1>Über Gemma Analytics</h1>\n"
            "<p>Gemma Analytics ist eine Data-Consultancy aus Berlin, spezialisiert auf den Aufbau moderner Datenplattformen. Wir arbeiten mit wachstumsstarken Unternehmen und bauen produktionsreife Dateninfrastruktur – vom Data-Warehouse-Setup über Pipeline-Engineering bis zu KPI-Dashboards und Team-Enablement.</p>\n"
            "<p>Unser Team bringt umfassende Erfahrung im Modern Data Stack aus über 70 abgeschlossenen Datenplattform-Projekten mit. Wir arbeiten Code-first und nutzen AI-gestützte Tools, um schnell zu liefern – ohne Kompromisse bei Qualität und Wartbarkeit.</p>\n"
            "<p>Aus diesen Projekten sind umfangreiche interne Dokumentation, wiederverwendbare Patterns und Engineering Best Practices entstanden, die in jedes neue Projekt einfließen und die Entwicklung vom ersten Tag an deutlich beschleunigen.</p>\n"
        )
    else:
        wow = (
            "<h1>Ways of Working</h1>\n"
            "<table><thead><tr><th>Area</th><th>Approach</th></tr></thead><tbody>"
            "<tr><td><strong>Communication</strong></td><td>Shared Slack channel for day-to-day</td></tr>"
            "<tr><td><strong>Sync Cadence</strong></td><td>Weekly sync call to review progress</td></tr>"
            "<tr><td><strong>Code Review</strong></td><td>All work via PRs with review before merge</td></tr>"
            "<tr><td><strong>Documentation</strong></td><td>All work documented to handoff-ready standard</td></tr>"
            "</tbody></table>\n"
        )
        about = (
            "<h1>About Gemma Analytics</h1>\n"
            "<p>Gemma Analytics is a Berlin-based data consultancy specializing in modern data platform implementation. We work with growth-stage companies to build production-grade data infrastructure – from warehouse setup and pipeline engineering through to KPI dashboards and team enablement.</p>\n"
            "<p>Our team brings deep experience across the modern data stack, with 70+ completed data platform projects. We operate code-first and leverage AI-assisted tooling to deliver at speed without compromising on quality or maintainability.</p>\n"
            "<p>Over these projects we have built extensive internal documentation, reusable patterns, and engineering best practices that carry over to every new engagement – accelerating development significantly from day one.</p>\n"
        )
    closing = (
        '<div class="about-gemma-closing">'
        f'<img class="about-gemma-closing__logo" src="{white_logo}" alt="Gemma Analytics" />'
        '<p class="about-gemma-closing__address">Gemma Analytics GmbH – Chausseestraße 17, 10115 Berlin<br />gemmaanalytics.com</p>'
        '</div>'
    )
    return wow + about + closing


def build_html(md_text: str, output_path: Path, title: str, subtitle: str,
               date: str, plum_cover: bool, dark_proposal: bool = False,
               one_pager: bool = False, language: str = "en",
               no_standard_sections: bool = False):
    from markdown_it import MarkdownIt
    # Enable GFM pipe tables (the commonmark preset renders them as raw
    # "| a | b |" text) and raw-HTML passthrough so proposals can embed the
    # component blocks (module-card, info-cards, fixed-price-box, timeline, …).
    body_html = MarkdownIt("commonmark", {"html": True}).enable("table").render(md_text)

    template_path = SKILL_DIR / "assets" / "templates" / "base.html"
    template = template_path.read_text(encoding="utf-8")

    asset_base = (SKILL_DIR / "assets").resolve()

    logos      = asset_base / "logos" / "primary_logo"
    logo_marks = asset_base / "logos" / "logo_mark"

    # Running header (dark proposal only) — left text + right white wordmark.
    header_text = ""
    header_logo = ""
    # Cover meta: non-dark modes append the company to the date line; the dark
    # proposal cover keeps date + a separate validity line instead (per the
    # contracts proposal-title-page).
    cover_meta_suffix = " · Gemma Analytics GmbH"
    cover_validity    = ""

    if plum_cover:
        cover_class           = "cover--plum"
        cover_logo            = str(logos / "gemma_primary_logo_white.svg")
        cover_bars            = ""   # no bars on plum cover — hidden via CSS
        footer_logo           = str(logos / "gemma_primary_logo_white.svg")
        cover_prepared_label  = ""
        cover_client          = ""
    elif dark_proposal:
        # Dark proposal cover mirrors contracts/lib/styling.typ proposal-title-page:
        # white primary logo (centred, 50% width), no bars, no cover decoration,
        # Lavender rule, "Prepared for" block, muted date.
        cover_class           = ""
        cover_logo            = str(logos / "gemma_primary_logo_white.svg")
        cover_bars            = ""   # hidden via CSS
        footer_logo           = str(logos / "gemma_primary_logo_white.svg")
        # Extract client name from date string ("July 2026 · Prepared for Acme")
        # or leave blank — the template shows/hides via CSS
        # Language-dependent labels (mirror contracts lib/styling.typ).
        prepared_label = "Erstellt für" if language == "de" else "Prepared for"
        connector      = " für " if language == "de" else " for "
        validity_text  = "Gültig für 30 Tage" if language == "de" else "Valid for 30 days"
        # "· Prepared for" / "· Erstellt für" both accepted in the --date string.
        marker = "· Erstellt für" if language == "de" else "· Prepared for"
        if marker not in date and "· Prepared for" in date:
            marker = "· Prepared for"   # tolerate EN marker even in DE runs
        if marker in date:
            parts = date.split(marker, 1)
            cover_prepared_label = prepared_label
            cover_client         = parts[1].strip()
            date                 = parts[0].strip()
        else:
            cover_prepared_label = ""
            cover_client         = ""
        # Running header: white horizontal wordmark + "<title> <for> <client> – <subtitle>".
        header_logo = str(asset_base / "logos" / "secondary_logo" / "gemma_secondary_logo_white.svg")
        if cover_client and subtitle:
            header_text = f"{title}{connector}{cover_client} – {subtitle}"
        elif cover_client:
            header_text = f"{title}{connector}{cover_client}"
        elif subtitle:
            header_text = f"{title} – {subtitle}"
        else:
            header_text = title
        # Dark proposal cover: no company suffix on the date; add validity line.
        cover_meta_suffix = ""
        cover_validity    = validity_text
    else:
        cover_class           = ""
        cover_logo            = ""   # hidden via CSS in document mode
        # One-pager: compact header on the same page, no separate cover → no bars.
        cover_bars            = "" if one_pager else str(asset_base / "elements" / "bars" / "gemma_bars.svg")
        footer_logo           = str(logos / "gemma_primary_logo_black.svg")
        cover_prepared_label  = ""
        cover_client          = ""

    # Dark proposals always close with Ways of Working + About Gemma
    # (mirrors proposal-en.typ); suppress with --no-standard-sections.
    if dark_proposal and not no_standard_sections:
        body_html += "\n" + _standard_proposal_sections(
            language, logos / "gemma_primary_logo_white.svg"
        )

    html = (
        template
        .replace("{{DOCUMENT_TITLE}}",       title)
        .replace("{{DOCUMENT_SUBTITLE}}",    subtitle)
        .replace("{{DOCUMENT_DATE}}",        date)
        .replace("{{DOCUMENT_BODY}}",        body_html)
        .replace("{{ASSET_BASE}}",           str(asset_base))
        .replace("{{COVER_CLASS}}",          cover_class)
        .replace("{{COVER_LOGO}}",           cover_logo)
        .replace("{{COVER_BARS}}",           cover_bars)
        .replace("{{FOOTER_LOGO}}",          footer_logo)
        .replace("{{COVER_PREPARED_LABEL}}", cover_prepared_label)
        .replace("{{COVER_CLIENT}}",         cover_client)
        .replace("{{HEADER_TEXT}}",          header_text)
        .replace("{{HEADER_LOGO}}",          header_logo)
        .replace("{{COVER_META_SUFFIX}}",    cover_meta_suffix)
        .replace("{{COVER_VALIDITY}}",       cover_validity)
    )

    if language != "en":
        html = html.replace('<html lang="en">', f'<html lang="{language}">', 1)

    # Dark proposal theme: apply CSS class to body
    if dark_proposal:
        html = html.replace('<body>', '<body class="theme--dark-proposal">')
    # One-pager: collapse the cover into a compact header (no separate page)
    elif one_pager:
        html = html.replace('<body>', '<body class="theme--one-pager">')

    # Each cover mode leaves some asset slots unused (no bars on a dark cover, no
    # running header outside dark proposals). Drop those elements instead of
    # shipping <img src="">, which browsers resolve against the document URL and
    # re-request the page for. CSS already hides them; this removes the request.
    html = re.sub(r'<img(?:\s[^>]*)?\ssrc=""[^>]*/?>', '', html)

    html = _inline_svgs(html, asset_base)
    html = _inline_fonts(html, asset_base)
    # UTF-8 explicitly: the template and the brand copy carry middle dots, en
    # dashes and umlauts, and a bare container's C locale would otherwise make
    # this raise UnicodeEncodeError. Matches base.html's <meta charset="UTF-8">.
    output_path.write_text(html, encoding="utf-8")
    print(output_path)


# ─── CLI ──────────────────────────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(description="Apply Gemma brand to a Markdown document")
    parser.add_argument("input",  help="Input Markdown file")
    parser.add_argument("output", nargs="?", help="Output file (optional)")
    parser.add_argument("--format", choices=["docx", "html"], default="html")
    parser.add_argument("--title",    default="Gemma Analytics")
    parser.add_argument("--subtitle", default="")
    parser.add_argument("--date",     default=datetime.now().strftime("%B %Y"))
    # The three cover modes are alternatives, not layers: build_html picks one
    # branch, so accepting two would silently drop whichever loses.
    cover_mode = parser.add_mutually_exclusive_group()
    cover_mode.add_argument(
        "--plum-cover", action="store_true",
        help="Midnight Plum cover page. Default is a white cover (reports, "
             "contracts, DOCX, PDF). This is a cover colour, not a slide deck: "
             "for slides use the presentation skill."
    )
    cover_mode.add_argument(
        "--dark-proposal", action="store_true",
        help="Apply dark proposal theme (#160329 page fill, white headings, lavender accents). "
             "HTML only."
    )
    cover_mode.add_argument(
        "--one-pager", action="store_true",
        help="Render title/subtitle as a compact header on the same page as the body — "
             "no separate cover page, no page break, no bars. HTML only."
    )
    parser.add_argument(
        "--language", choices=["en", "de"], default="en",
        help="Language for dark-proposal labels (header connective, 'Prepared for' / "
             "'Erstellt für', validity line). Default: en."
    )
    parser.add_argument(
        "--no-standard-sections", action="store_true",
        help="Dark proposals only: skip the auto-appended Ways of Working and "
             "About Gemma closing sections."
    )
    args = parser.parse_args()

    # HTML-only flags reach build_docx nowhere, so say so instead of dropping
    # the theme without a word.
    if args.format == "docx":
        html_only = [name for name, on in (
            ("--dark-proposal",        args.dark_proposal),
            ("--one-pager",            args.one_pager),
            ("--no-standard-sections", args.no_standard_sections),
        ) if on]
        if html_only:
            parser.error(
                f"{', '.join(html_only)} applies to HTML output only — "
                "drop the flag or use --format html"
            )

    input_path = Path(args.input)
    if not input_path.exists():
        sys.exit(f"Input file not found: {input_path}")

    md_text = input_path.read_text(encoding="utf-8")
    ext = ".html" if args.format == "html" else ".docx"
    output_path = Path(args.output) if args.output else input_path.with_suffix(ext)

    if args.format == "html":
        build_html(md_text, output_path, args.title, args.subtitle, args.date,
                   args.plum_cover, args.dark_proposal, args.one_pager,
                   args.language, args.no_standard_sections)
    else:
        build_docx(md_text, output_path, args.title, args.subtitle, args.date,
                   args.plum_cover)


if __name__ == "__main__":
    main()
