#!/usr/bin/env python3
"""One-shot migration: inline `image`/`icon` payloads -> top-level `asset` nodes.

Rewrites documents written in the pre-asset KDL format:

    image x=1.0 y=2.0 w=8.0 h=8.0 {
        svg r#"<svg …>"#
    }

into the current one, with identical content stored once:

    image "i1" x=1.0 y=2.0 w=8.0 h=8.0
    …
    asset "i1" {
        svg r#"<svg …>"#
    }

Throwaway tooling — the parser deliberately has no migration path. Usage:

    python3 migrate_assets.py FILE...        # rewrites in place, .bak alongside
"""

import re
import shutil
import sys

# An `image`/`icon` node opening a body, e.g. `    icon x=1.0 y=2.0 w=3.0 h=4.0 {`.
NODE = re.compile(r"^([ \t]*)(image|icon|symbol)((?:\s+[a-z]+=[-0-9.]+)+)\s*\{[ \t]*$")
PROP = re.compile(r"([a-z]+)=([-0-9.]+)")


def payload(lines, i):
    """The `svg`/`png` child starting at `lines[i]`, and the index after it.

    Handles a KDL v1 raw string spanning any number of lines (`r#"…"#`, fence
    widened to taste) as well as a single-line quoted `png "…"`.
    """
    line = lines[i]
    svg = re.match(r'^\s*svg\s+(r(#*)")', line)
    if svg:
        opener, closer = svg.group(1), '"' + svg.group(2)
        head = line[line.index(opener) + len(opener):]
        chunk = [line[line.index(opener):]]
        while closer not in head:
            i += 1
            head = lines[i]
            chunk.append(head)
        return "svg", "\n".join(chunk), i + 1
    png = re.match(r'^\s*png\s+(".*")\s*$', line)
    if png:
        return "png", png.group(1), i + 1
    raise SystemExit(f"unrecognized image payload: {line!r}")


def migrate(path):
    src = open(path).read()
    lines = src.split("\n")
    out, assets, ids = [], [], {}
    i = 0
    changed = False
    while i < len(lines):
        m = NODE.match(lines[i])
        if not m:
            out.append(lines[i])
            i += 1
            continue
        indent, kind, props = m.group(1), m.group(2), dict(PROP.findall(m.group(3)))
        kind = "image" if kind == "symbol" else kind
        form, content, i = payload(lines, i + 1)
        while lines[i].strip() != "}":  # tolerate blank lines before the close
            if lines[i].strip():
                raise SystemExit(f"unexpected node content: {lines[i]!r}")
            i += 1
        i += 1
        key = (form, content)
        if key not in ids:
            ids[key] = f"i{len(ids) + 1}"
            assets.append((ids[key], form, content))
        # A legacy square `size=` becomes an equal w/h box.
        if "w" not in props and "size" in props:
            props["w"] = props["h"] = props["size"]
        geom = " ".join(f"{k}={props[k]}" for k in ("x", "y", "w", "h"))
        out.append(f'{indent}{kind} "{ids[key]}" {geom}')
        changed = True

    if not changed:
        print(f"{path}: no inline images")
        return

    while out and not out[-1].strip():
        out.pop()
    for asset_id, form, content in assets:
        out += [f'', f'asset "{asset_id}" {{', f"    {form} {content}", "}"]
    out.append("")

    shutil.copyfile(path, path + ".bak")
    open(path, "w").write("\n".join(out))
    print(f"{path}: {len(assets)} asset(s) from {sum(1 for l in out if re.match(r'^\s*(image|icon) "i', l))} placement(s)")


for arg in sys.argv[1:]:
    migrate(arg)
