#!/usr/bin/env python3
"""
Numbering & List Showcase — generates numbering.docx exercising every supported
`num` / `abstractNum` feature:
  • abstractNum top-level props: name, styleLink, numStyleLink, multiLevelType
  • Per-level dotted props on all levels: format, text, start, indent, hanging,
    justification, suff, font, size, color, bold, italic
  • num mode A (auto-create matching abstractNum from format/text/indent)
  • num mode B (reuse existing abstractNum via abstractNumId)
  • num mode C (startOverride — restart numbering for one instance)
  • Two num instances sharing one abstractNum → independent counters
  • continue=true opt-in to Word-style continuation
  • style-borne numPr (paragraph inherits numbering via pStyle)
  • Set on /numbering/abstractNum[@id=N]/level[L] after creation

SDK twin of numbering.sh (officecli CLI). Both produce an equivalent
numbering.docx. This one drives the **officecli Python SDK**
(`pip install officecli-sdk`): one resident is started, the `add num` calls go
out via `doc.send(...)` so we can read back each freshly-minted numId, and the
paragraphs that reference those ids ship in `doc.batch(...)` round-trips. Each
item is the same `{"command","parent","type","props"}` dict you'd put in an
`officecli batch` list.

Usage:
  pip install officecli-sdk          # plus the `officecli` binary on PATH
  python3 numbering.py
"""

import os
import re
import sys

# --- locate the SDK: prefer an installed `officecli-sdk`, else the in-repo copy
try:
    import officecli  # pip install officecli-sdk
except ImportError:
    sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                    "..", "..", "sdk", "python"))
    import officecli

FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "numbering.docx")


def para(text, **props):
    """One `add paragraph` item in batch-shape."""
    return {"command": "add", "parent": "/body", "type": "paragraph",
            "props": {"text": text, **props}}


def _num_id(resp):
    """Pull the numId out of an `add num` envelope ("Added num at
    /numbering/num[@id=N]") — the SDK twin of the .sh `sed @id=N` capture."""
    text = resp.get("data") or resp.get("message") or "" if isinstance(resp, dict) else str(resp)
    m = re.search(r"@id=(\d+)\]", text)
    if not m:
        raise RuntimeError(f"could not parse numId from add response: {resp!r}")
    return m.group(1)


print(f"Building {FILE} ...")

with officecli.create(FILE, "--force") as doc:
    def add_num(**props):
        """`add /numbering --type num` via send(), returning the new numId."""
        return _num_id(doc.send({"command": "add", "parent": "/numbering",
                                 "type": "num", "props": props}))

    # ============================================================
    # Title
    # ============================================================
    doc.batch([
        para("Numbering & List Showcase",
             align="center", bold="true", size="20"),
        para("Generated by officecli — covers abstractNum, num, and style-borne numPr",
             align="center", italic="true", color="666666"),
        para(""),

        # ===== Section 1 heading + abstractNum #100 (custom 3-level template) =====
        para("1. Three-level numbered list (custom marker styling)",
             bold="true", size="14", spaceBefore="240", spaceAfter="120"),
    ])

    # abstractNum #100 — fully customized 3-level numbered template
    doc.send({"command": "add", "parent": "/numbering", "type": "abstractNum", "props": {
        "id": "100",
        "name": "ShowcaseMultilevel",
        "type": "hybridMultilevel",
        "level0.format": "decimal",
        "level0.text": "%1.",
        "level0.indent": "720",
        "level0.hanging": "360",
        "level0.justification": "left",
        "level0.suff": "tab",
        "level0.color": "C00000",
        "level0.bold": "true",
        "level0.size": "14",
        "level1.format": "lowerLetter",
        "level1.text": "%2)",
        "level1.indent": "1440",
        "level1.hanging": "360",
        "level1.color": "2E74B5",
        "level1.italic": "true",
        "level2.format": "lowerRoman",
        "level2.text": "%3.",
        "level2.indent": "2160",
        "level2.hanging": "360",
        "level2.color": "666666",
    }})

    # A num instance pointing at #100
    num_a = add_num(abstractNumId="100")
    print(f"  Created num #{num_a} → abstractNum #100")

    doc.batch([
        para("Project Phoenix kickoff agenda", numId=num_a, ilvl="0"),
        para("Stakeholder alignment", numId=num_a, ilvl="1"),
        para("identify decision makers", numId=num_a, ilvl="2"),
        para("schedule discovery interviews", numId=num_a, ilvl="2"),
        para("Architecture review", numId=num_a, ilvl="1"),
        para("Sprint planning", numId=num_a, ilvl="0"),
        para("Resource allocation", numId=num_a, ilvl="0"),

        # ===== Section 2 heading =====
        para(""),
        para("2. Independent counters (default) and continue=true opt-in",
             bold="true", size="14", spaceBefore="240", spaceAfter="120"),
    ])

    # Two new nums on the same abstractNum: by default each gets its own
    # auto-injected startOverride.0 → independent counters. The third opts into
    # Word's literal continuation via continue=true.
    num_b = add_num(abstractNumId="100")
    print(f"  Created num #{num_b} → independent counter (auto-injected startOverride.0=1)")
    num_cont = add_num(abstractNumId="100", **{"continue": "true"})
    print(f"  Created num #{num_cont} → Word-style continuation (continue=true)")

    doc.batch([
        para("List B starts fresh at 1 (default behavior)", numId=num_b, ilvl="0"),
        para("List B item two (counts 2)", numId=num_b, ilvl="0"),
        para("List C continues from List A's count (continue=true)", numId=num_cont, ilvl="0"),
        para("List C item two", numId=num_cont, ilvl="0"),

        # ===== Section 3 heading =====
        para(""),
        para("3. Restart numbering with startOverride",
             bold="true", size="14", spaceBefore="240", spaceAfter="120"),
    ])

    # Mode C — num with startOverride (restart at 100)
    num_c = add_num(abstractNumId="100", start="100")
    print(f"  Created num #{num_c} → abstractNum #100 with startOverride.0=100")

    doc.batch([
        para("Numbered starting from 100", numId=num_c, ilvl="0"),
        para("Continues from 101", numId=num_c, ilvl="0"),

        # ===== Section 4 heading =====
        para(""),
        para("4. Custom-styled bullet list (★ / ▶ / ●)",
             bold="true", size="14", spaceBefore="240", spaceAfter="120"),
    ])

    # abstractNum #200 — bullet list with custom glyphs and font color
    doc.send({"command": "add", "parent": "/numbering", "type": "abstractNum", "props": {
        "id": "200",
        "name": "StarBullet",
        "type": "hybridMultilevel",
        "level0.format": "bullet",
        "level0.text": "★",
        "level0.color": "E8B003",
        "level0.size": "12",
        "level1.format": "bullet",
        "level1.text": "▶",
        "level1.font": "Arial",
        "level1.color": "2E74B5",
        "level1.indent": "1440",
        "level2.format": "bullet",
        "level2.text": "●",
        "level2.color": "70AD47",
        "level2.indent": "2160",
    }})

    num_bullet = add_num(abstractNumId="200")

    doc.batch([
        para("Top-level milestone", numId=num_bullet, ilvl="0"),
        para("Sub-milestone with deliverable", numId=num_bullet, ilvl="1"),
        para("Nitty-gritty detail", numId=num_bullet, ilvl="2"),
        para("Another top-level milestone", numId=num_bullet, ilvl="0"),

        # ===== Section 5 heading =====
        para(""),
        para("5. Mode A — num auto-creates abstractNum",
             bold="true", size="14", spaceBefore="240", spaceAfter="120"),
    ])

    # Mode A — num auto-creates a matching abstractNum on the fly
    num_auto = add_num(**{
        "level0.format": "upperRoman",
        "level0.text": "%1.",
        "level0.indent": "720",
        "level0.size": "12",
        "level0.color": "7030A0",
        "level0.bold": "true",
    })
    print(f"  Mode A created num #{num_auto} + matching abstractNum")

    doc.batch([
        para("The first part of the proposal", numId=num_auto, ilvl="0"),
        para("The second part", numId=num_auto, ilvl="0"),
        para("The third part", numId=num_auto, ilvl="0"),

        # ===== Section 6 heading =====
        para(""),
        para("6. Style-borne numbering (paragraph inherits via pStyle)",
             bold="true", size="14", spaceBefore="240", spaceAfter="120"),
    ])

    # Build a dedicated abstractNum + num for the style
    doc.send({"command": "add", "parent": "/numbering", "type": "abstractNum", "props": {
        "id": "300",
        "name": "StyleBorne",
        "level0.format": "decimalZero",
        "level0.text": "%1.",
        "level0.indent": "720",
        "level0.color": "C00000",
    }})
    num_style = add_num(abstractNumId="300")

    # Style holds the numPr; paragraphs reference the style without their own numId
    doc.send({"command": "add", "parent": "/styles", "type": "style", "props": {
        "id": "ShowcaseListItem",
        "name": "Showcase List Item",
        "type": "paragraph",
        "basedOn": "Normal",
        "numId": num_style,
        "ilvl": "0",
    }})

    doc.batch([
        para("Inherits numbering through style", style="ShowcaseListItem"),
        para("Second item, also via style", style="ShowcaseListItem"),
        para("Third item — note: paragraphs themselves have no numPr", style="ShowcaseListItem"),

        # ===== Section 7 heading =====
        para(""),
        para("7. Modify abstractNum after creation",
             bold="true", size="14", spaceBefore="240", spaceAfter="120"),
    ])

    # Set after create — override abstractNum #100 level 3 with green + larger size
    doc.send({"command": "set",
              "path": "/numbering/abstractNum[@id=100]/level[3]",
              "props": {"format": "decimal", "text": "Step %4 ⇒",
                        "color": "70AD47", "bold": "true", "size": "12"}})

    num_deep = add_num(abstractNumId="100")

    doc.batch([
        para("Outer step", numId=num_deep, ilvl="0"),
        para("Mid step", numId=num_deep, ilvl="1"),
        para("Inner step", numId=num_deep, ilvl="2"),
        para("Deepest step (modified after creation)", numId=num_deep, ilvl="3"),

        # ===== Section 8 heading =====
        para(""),
        para("8. styleLink, numStyleLink, level<N>.start, direction, isLgl, lvlRestart",
             bold="true", size="14", spaceBefore="240", spaceAfter="120"),
    ])

    # abstractNum #400 — styleLink + numStyleLink + per-level start
    doc.send({"command": "add", "parent": "/numbering", "type": "abstractNum", "props": {
        "id": "400",
        "name": "CoverageAbs",
        "type": "multilevel",
        "styleLink": "CoverageStyle",
        "numStyleLink": "OutlineRef",
        "level0.format": "decimal",
        "level0.text": "%1.",
        "level0.start": "1",
        "level1.format": "lowerLetter",
        "level1.text": "%2)",
        "level1.start": "3",
        "level2.format": "lowerRoman",
        "level2.text": "%3.",
        "level2.start": "5",
    }})

    # Set direction=rtl, isLgl=true, lvlRestart=0 on individual levels via Set
    doc.send({"command": "set",
              "path": "/numbering/abstractNum[@id=400]/level[1]",
              "props": {"direction": "rtl"}})
    doc.send({"command": "set",
              "path": "/numbering/abstractNum[@id=400]/level[2]",
              "props": {"isLgl": "true", "lvlRestart": "0"}})

    num_cov = add_num(abstractNumId="400")
    print(f"  Created num #{num_cov} → abstractNum #400 (coverage)")

    doc.batch([
        para("Item starting at 1 (level0.start=1)", numId=num_cov, ilvl="0"),
        para("Sub-item starting at c (level1.start=3, direction=rtl)", numId=num_cov, ilvl="1"),
        para("Deep item starting at v (level2.start=5, isLgl, lvlRestart=0)", numId=num_cov, ilvl="2"),

        # ===== Closer =====
        para(""),
        para("End of showcase. Open in Word/Google Docs to see all numbering rendered.",
             italic="true", color="666666", align="center"),
    ])

print(f"Generated: {FILE}")
