import re
#!/usr/bin/env python3
"""Shared .brand pack parsing — importable WITHOUT executing the engine.

The engine, slicer, and QR tool all read packs through this one parser so the
format has a single definition.
"""


def parse_pack(path):
    """Parse a bash-style KEY="value" pack file (same family as .pack files)."""
    out = {}
    with open(path, "r", encoding="utf-8") as f:
        for raw in f:
            line = raw.strip()
            if not line or line.startswith("#"):
                continue
            if "=" not in line:
                continue
            key, _, val = line.partition("=")
            key = key.strip()
            if key.startswith("export "):   # bash honours `export KEY=...`; so must the parser
                key = key[len("export "):].strip()
            val = val.strip()
            # A quoted value may be followed by a comment, which bash strips when it sources
            # the pack and this parser did not: QR_URL="https://labs.google/flow/about"  # note
            # came back WITH its quotes, so every URL check compared '"x"' against 'x' and the
            # lint refused a good pack (2026-08-31, the first product URL that had a path).
            m = re.match(r"""^("[^"]*"|'[^']*')\s*(?:#.*)?$""", val)
            if m:
                val = m.group(1)[1:-1]
            else:
                val = val.split(" #")[0].strip()
            out[key] = val
    return out


def hex_rgb(v, fallback=None):
    v = (v or "").lstrip("#")
    if len(v) != 6:
        if fallback is None:
            raise SystemExit(f"pack: bad hex colour {v!r}")
        return fallback
    return tuple(int(v[i:i + 2], 16) for i in (0, 2, 4))


def _wordish(ch):
    """A letter, a combining mark or a digit — part of a word in any script.
    `\\w` misses Devanagari vowel signs (U+093F etc.), so "क" looked like a
    whole word inside "कि"."""
    import unicodedata
    return ch == "_" or unicodedata.category(ch)[0] in "LMN"


def word_at(line, word):
    """Index of the first `word` in `line` that stands as a whole word (not inside
    a longer word, in any script), or -1.

    POSTER_UNDERLINE names one word of POSTER_LINE_2. A bare substring match put
    the stroke under the "it" inside "with" when that came first."""
    if not word:
        return -1
    i = line.find(word)
    while i >= 0:
        before = line[i - 1] if i > 0 else ""
        after = line[i + len(word)] if i + len(word) < len(line) else ""
        # only the word's own edges decide: "everywhere." ends in punctuation
        if not (before and _wordish(before) and _wordish(word[0])) and \
           not (after and _wordish(after) and _wordish(word[-1])):
            return i
        i = line.find(word, i + 1)
    return -1


def not_a_brand_film(path, P):
    """Why this .brand file is not a brand-explainer film, or None if it is one.

    Two kinds of file share the extension and the packs directory: the blank
    TEMPLATE, and packs for another renderer (RENDERER="vox" — the vox-explainer
    collage engine, whose beats use a different field grammar). Neither can be
    rendered by this engine, so the lint skips them and the engine refuses them."""
    import os
    renderer = (P.get("RENDERER", "") or "").strip().lower()
    # "beat" is the name the vox-explainer plan gives this engine's layout
    # (docs/design/specs/2026-07-27-vox-explainer-plan.md), so it is a film too.
    if renderer and renderer not in ("brand", "beat"):
        return (f"RENDERER={renderer!r}: a {renderer} pack, not a brand-explainer film "
                f"(its beats use that renderer's grammar)")
    if os.path.basename(path) == "_template.brand":
        return "the pack TEMPLATE, not a film: copy it to <brand>.brand and fill it in"
    return None
