"""
prepare_template.py
-------------------
Single-pass preparation of a Scratch sprite template JSON:
  1. Renames all opaque IDs to human-readable cattorch_ prefixed names
  2. Empties all list contents and zeroes all variables
  3. Converts numeric string literals in block inputs to int or float
  4. Resets the topLevel root block position to (0, 0)
  5. Strips sprite metadata after blocks (comments, costumes, sounds, etc.)

Works with both full sprite JSON and blocks-only JSON.

Usage:
  python -m cattorch.templates.processing_tools.prepare_template input.json [output.json]
"""

import argparse
import json
import copy


# ---------------------------------------------------------------------------
# Step 1: Rename IDs
# ---------------------------------------------------------------------------

def is_blocks_only(data: dict) -> bool:
    if "isStage" in data:
        return False
    first = next(iter(data.values()), None)
    return isinstance(first, dict) and "opcode" in first


def build_id_map(data: dict, blocks_only: bool) -> dict[str, str]:
    id_map = {}
    block_counter = 1
    var_counter = 1
    list_counter = 1
    comment_counter = 1

    if blocks_only:
        for sid in data:
            id_map[sid] = f"cattorch_block_{block_counter}"
            block_counter += 1
    else:
        for sid in data.get("variables", {}):
            id_map[sid] = f"cattorch_var_{var_counter}"
            var_counter += 1
        for sid in data.get("lists", {}):
            id_map[sid] = f"cattorch_list_{list_counter}"
            list_counter += 1
        for sid in data.get("blocks", {}):
            id_map[sid] = f"cattorch_block_{block_counter}"
            block_counter += 1
        for sid in data.get("comments", {}):
            id_map[sid] = f"cattorch_comment_{comment_counter}"
            comment_counter += 1

    return id_map


def rename_ids(data: dict) -> dict:
    blocks_only = is_blocks_only(data)
    id_map = build_id_map(data, blocks_only)
    raw = json.dumps(data)
    for old, new in sorted(id_map.items(), key=lambda x: -len(x[0])):
        raw = raw.replace(old, new)
    return json.loads(raw)


# ---------------------------------------------------------------------------
# Step 2: Empty lists
# ---------------------------------------------------------------------------

def empty_lists(data: dict) -> dict:
    for entry in data.get("lists", {}).values():
        entry[1] = []
    return data


def zero_variables(data: dict) -> dict:
    for entry in data.get("variables", {}).values():
        entry[1] = 0
    return data


def reset_toplevel_position(data: dict) -> dict:
    for block in data.get("blocks", {}).values():
        if block.get("topLevel"):
            block["x"] = 0
            block["y"] = 0
    return data


def strip_sprite_metadata(data: dict) -> dict:
    """Remove everything after blocks that isn't needed for templates."""
    keep = {"isStage", "name", "variables", "lists", "broadcasts", "blocks"}
    for key in list(data.keys()):
        if key not in keep:
            del data[key]
    return data


# ---------------------------------------------------------------------------
# Step 3: Convert numeric string literals
# ---------------------------------------------------------------------------

def try_numeric(value):
    if not isinstance(value, str) or value == "":
        return value
    try:
        return int(value)
    except ValueError:
        pass
    try:
        return float(value)
    except ValueError:
        pass
    return value


def process_input_value(val):
    if isinstance(val, list):
        result = [val[0]]  # type code — always keep as-is
        for i, item in enumerate(val[1:], 1):
            if isinstance(item, list):
                result.append(process_input_value(item))
            elif isinstance(item, str) and i == 1:
                result.append(try_numeric(item))
            else:
                result.append(item)
        return result
    return val


def process_inputs(inputs: dict) -> dict:
    result = {}
    for key, inp in inputs.items():
        if not isinstance(inp, list):
            result[key] = inp
            continue
        new_inp = [inp[0]]
        for item in inp[1:]:
            if isinstance(item, list):
                new_inp.append(process_input_value(item))
            else:
                new_inp.append(item)
        result[key] = new_inp
    return result


def convert_numerics(data: dict) -> dict:
    for block in data.get("blocks", {}).values():
        if "inputs" in block:
            block["inputs"] = process_inputs(block["inputs"])
    return data


# ---------------------------------------------------------------------------
# Combined pipeline
# ---------------------------------------------------------------------------

def prepare(data: dict) -> dict:
    data = copy.deepcopy(data)
    data = rename_ids(data)
    data = empty_lists(data)
    data = zero_variables(data)
    data = convert_numerics(data)
    data = reset_toplevel_position(data)
    data = strip_sprite_metadata(data)
    return data


def main():
    parser = argparse.ArgumentParser(
        description="Prepare a Scratch sprite template JSON for use with cattorch."
    )
    parser.add_argument("input", help="Path to the input JSON file")
    parser.add_argument(
        "output",
        nargs="?",
        default=None,
        help="Path to the output JSON file (default: overwrite input)",
    )
    args = parser.parse_args()

    dst = args.output or args.input

    with open(args.input) as f:
        data = json.load(f)

    result = prepare(data)

    with open(dst, "w") as f:
        json.dump(result, f, indent=2)

    print(f"Prepared {args.input} -> {dst}")


if __name__ == "__main__":
    main()