#!/usr/bin/env python3
"""Convert a `node_dump.js` JSON result into one component YAML per node.

Values are copied from the Figma dump programmatically, never retyped, so the
YAML matches Figma exactly. Run it once per dump:

    python3 tools/figma-spec/dump_to_yaml.py DUMP.json OUT_DIR \
        --page "Checkout" --block A1 --url "https://figma.com/design/..."

Each top-level node in the dump becomes OUT_DIR/<slug>.yml. The subtree is
kept under `children`. Existing files are overwritten, but the
`implementation` section of an existing file is preserved so harvesting again
does not lose implementation tracking.
"""

import argparse
import hashlib
import json
import os
import re
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import yaml_io  # noqa: E402

SCHEMA_VERSION = 1


def slug(text):
    s = re.sub(r"[^A-Za-z0-9]+", "-", text).strip("-").lower()
    return s or "node"


def collect(node, strings, tokens, assets):
    text = node.get("text")
    if text and text.get("characters"):
        strings.append({"node_id": node["id"], "name": node["name"], "text": text["characters"]})
    for key, name in (node.get("variables") or {}).items():
        names = name if isinstance(name, list) else [name]
        for n in names:
            if n:
                tokens.setdefault(n, set()).add(key)
    fills = node.get("fills") or []
    if any(f.get("type") == "IMAGE" for f in fills) or node.get("type") == "VECTOR":
        assets.append({"node_id": node["id"], "name": node["name"], "kind": "image" if node.get("type") != "VECTOR" else "vector"})
    for child in node.get("children") or []:
        collect(child, strings, tokens, assets)


def fingerprint(node):
    payload = json.dumps(node, sort_keys=True, ensure_ascii=False).encode("utf-8")
    return hashlib.sha256(payload).hexdigest()[:16]


def convert(node, args, dumped_at, file_name):
    strings, tokens, assets = [], {}, []
    collect(node, strings, tokens, assets)
    return {
        "schema_version": SCHEMA_VERSION,
        "component": {
            "name": node["name"],
            "node_id": node["id"],
            "type": node["type"],
            "page": args.page,
            "block": args.block,
            "figma_url": args.url,
            "file_name": file_name,
        },
        "source": {
            "dumped_at": dumped_at,
            "max_depth": args.max_depth,
            "fingerprint": fingerprint(node),
        },
        "frame": {"width": node.get("width"), "height": node.get("height")},
        "design": {k: v for k, v in node.items() if k not in ("id", "name", "type", "children", "x", "y")},
        "strings": strings,
        "tokens": [{"name": k, "used_for": sorted(v)} for k, v in sorted(tokens.items())],
        "assets": assets,
        "children": node.get("children") or [],
        "reference_screenshot": f"screenshots/{slug(node['name'])}--{slug(node['id'])}.png",
        "verification": {"values_crosscheck": "pending", "screenshot_capture": "pending"},
        "implementation": {"status": "not-started", "files": [], "tests": [], "screenshot_match": None},
    }


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("dump")
    ap.add_argument("out_dir")
    ap.add_argument("--page", default=None)
    ap.add_argument("--block", default=None)
    ap.add_argument("--url", default=None)
    ap.add_argument("--max-depth", type=int, default=None)
    args = ap.parse_args()

    with open(args.dump, encoding="utf-8") as fh:
        raw = json.load(fh)
    if isinstance(raw, str):
        raw = json.loads(raw)
    os.makedirs(args.out_dir, exist_ok=True)
    if args.max_depth is None:
        args.max_depth = raw.get("maxDepth")

    if not raw.get("nodes") or raw.get("missing"):
        print("Dump has no nodes or reports missing nodes; refusing incomplete conversion", file=sys.stderr)
        sys.exit(2)
    ids = [n["id"] for n in raw["nodes"]]
    if len(ids) != len(set(ids)):
        raise ValueError("duplicate node IDs")
    written = []
    for node in raw.get("nodes", []):
        doc = convert(node, args, raw.get("dumpedAt"), raw.get("fileName"))
        name = f"{slug(node['name'])}--{slug(node['id'])}.yml"
        path = os.path.join(args.out_dir, name)
        if os.path.exists(path):
            previous = yaml_io.load(path) or {}
            if previous.get("implementation"):
                doc["implementation"] = previous["implementation"]
                if previous.get("source", {}).get("fingerprint") != doc["source"]["fingerprint"]:
                    doc["implementation"] = dict(doc["implementation"], status="needs-reverification", screenshot_match=None)
        yaml_io.dump(doc, path, header=f"Generated by tools/figma-spec/dump_to_yaml.py from Figma node {node['id']}.\nDo not hand-edit design values; harvest again instead.")
        written.append(path)

    for path in written:
        print(path)
    if raw.get("missing"):
        print("MISSING NODES:", ", ".join(raw["missing"]), file=sys.stderr)
        sys.exit(2)


if __name__ == "__main__":
    main()
