"""
GUI format → API format converter + canlı render test.
Mini ayarlar (W=512, H=320, L=25) ile workflow'u submit edip kuyruğa atar.
"""
import json, urllib.request, urllib.parse, time, copy, sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")

API = "http://127.0.0.1:8188"
WF_PATH = r"D:\Comfy\App\ComfyUI_windows_portable\ComfyUI\user\default\workflows\FikoYT_MVD_Muhtesem_Video_Merkezi.json"

# === Fetch object_info to learn input definitions ===
print("Fetching /object_info ...")
oinfo = json.loads(urllib.request.urlopen(f"{API}/object_info", timeout=30).read())
print(f"  classes available: {len(oinfo)}")

wf = json.load(open(WF_PATH, "r", encoding="utf-8"))

# Index links by id: id -> (from_node, from_slot, to_node, to_slot, type)
links = {l[0]: l for l in wf["links"]}
# Index nodes by id
nodes = {n["id"]: n for n in wf["nodes"]}

def get_widget_names(class_type):
    """Return ordered widget names (excluding INPUT-only sockets) for a class."""
    info = oinfo.get(class_type)
    if info is None:
        return []
    inputs = info.get("input", {})
    required = inputs.get("required", {})
    optional = inputs.get("optional", {})
    # Order: required first then optional, by their key order
    widget_names = []
    for k, v in list(required.items()) + list(optional.items()):
        if not isinstance(v, list) or len(v) == 0:
            continue
        t = v[0]
        # If t is a list, it's a COMBO; if a string like 'INT'/'FLOAT'/'STRING'/'BOOLEAN', it's a widget
        if isinstance(t, list):
            widget_names.append(k)
        elif t in ("INT", "FLOAT", "STRING", "BOOLEAN", "COMBO"):
            widget_names.append(k)
        # else: socket input (MODEL, IMAGE, etc) — not a widget
    return widget_names

# First pass: determine which nodes will be skipped (muted + virtual + missing class)
skipped = []
for nid, n in nodes.items():
    ct = n["type"]
    if n.get("mode") == 4:
        skipped.append(nid); continue
    if ct in ("MarkdownNote", "Reroute"):
        skipped.append(nid); continue
    if ct not in oinfo:
        skipped.append(nid); continue
pre_skipped_set = {str(s) for s in skipped}

api = {}
for nid, n in nodes.items():
    ct = n["type"]
    if str(nid) in pre_skipped_set:
        continue

    inputs_dict = {}
    # 1) collect socket inputs from links
    socket_inputs = {}  # name -> [from_node_str, from_slot]
    widget_overrides = {}  # name -> value (when a widget is wired via link)
    for inp in n.get("inputs") or []:
        link_id = inp.get("link")
        if link_id is None:
            continue
        link = links.get(link_id)
        if link is None:
            continue
        _, from_nid, from_slot, _, _, _ = link
        # Follow reroutes
        while nodes.get(from_nid) and nodes[from_nid]["type"] == "Reroute":
            rn = nodes[from_nid]
            r_in = rn["inputs"][0].get("link")
            if r_in is None:
                break
            rl = links.get(r_in)
            if rl is None:
                break
            _, from_nid, from_slot, _, _, _ = rl
        # If source node is in pre-skipped (muted/virtual), don't wire it; fall back to widget value
        if str(from_nid) in pre_skipped_set:
            continue
        # If this input has a widget attribute, it's a widget-via-link
        if inp.get("widget"):
            widget_overrides[inp["name"]] = [str(from_nid), from_slot]
        else:
            socket_inputs[inp["name"]] = [str(from_nid), from_slot]

    # 2) widget values
    widget_vals = n.get("widgets_values") or []
    widget_names = get_widget_names(ct)
    # Some classes have widgets that act like (value, mode) tuples (e.g. seed nodes with 'randomize'):
    # We just map positionally, skipping extras.
    wd = {}
    wi = 0
    for name in widget_names:
        if wi >= len(widget_vals):
            break
        wd[name] = widget_vals[wi]
        wi += 1
        # If we have an extra control "randomize"/"fixed" string after a seed/int, skip it
        if wi < len(widget_vals) and isinstance(widget_vals[wi], str) and widget_vals[wi] in ("randomize", "fixed", "increment", "decrement"):
            wi += 1

    # Merge: widget_overrides take precedence (these come from socket-wired widgets)
    for k, v in wd.items():
        inputs_dict[k] = v
    for k, v in widget_overrides.items():
        inputs_dict[k] = v
    for k, v in socket_inputs.items():
        inputs_dict[k] = v

    api[str(nid)] = {
        "class_type": ct,
        "inputs": inputs_dict,
    }

# Remove references to skipped (muted) nodes from inputs
skipped_set = {str(s) for s in skipped}
def is_skipped_ref(v):
    return isinstance(v, list) and len(v) == 2 and isinstance(v[0], str) and v[0] in skipped_set
def has_skipped(node_inputs):
    return any(is_skipped_ref(v) for v in node_inputs.values())

# Iteratively prune nodes whose inputs reference skipped nodes
changed = True
while changed:
    changed = False
    bad = [k for k, v in api.items() if has_skipped(v["inputs"])]
    for k in bad:
        skipped_set.add(k)
        del api[k]
        changed = True

# Set mini test resolution
def set_widget(node_id_str, name, value):
    if node_id_str in api:
        api[node_id_str]["inputs"][name] = value

# Width=512, Height=320 (16:10ish), Length=25 (~1 sec @ 24fps) — quick test
# Find PrimitiveInt #40 (Width) #25 (Height) #27 (Length)
set_widget("40", "value", 512)
set_widget("25", "value", 320)
# Saniye node (id 79) → 1.0 sec @ 24fps → length 17
set_widget("79", "value", 1.0)
# Force I2V mode (boolean #28 = False)
set_widget("28", "value", False)

json.dump(api, open(r"D:\Comfy\App\ComfyUI_windows_portable\api_dump.json","w",encoding="utf-8"), indent=2, ensure_ascii=False)
print(f"\nAPI nodes: {len(api)}")
print(f"Skipped (muted+virtual): {len(skipped_set)}")
print("Failing nodes detail:")
for fnid in ["5","4","16","17","6","39","45"]:
    if fnid in api:
        print(f"  #{fnid} {api[fnid]['class_type']}: {api[fnid]['inputs']}")
        # what does oinfo say about required?
        info = oinfo.get(api[fnid]['class_type'], {})
        req = info.get('input',{}).get('required',{})
        print(f"     REQUIRED in oinfo: {list(req.keys())}")

# Debug: who exists in api?
print("\nAPI node ids/classes:")
for k, v in api.items():
    title = nodes.get(int(k), {}).get("title", "")
    print(f"  {k:>3}  {v['class_type']:<35}  {title}")
print("\nSkipped node ids:")
for s in sorted(skipped_set, key=lambda x: int(x) if x.isdigit() else 9999):
    if s.isdigit():
        nn = nodes.get(int(s))
        if nn:
            print(f"  {s:>3}  {nn['type']:<35}  mode={nn.get('mode')}  title={nn.get('title','')}")

# === Submit prompt ===
payload = {"prompt": api, "client_id": "fikoyt_mvd_test"}
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(f"{API}/prompt", data=data, headers={"Content-Type": "application/json"})
try:
    resp = json.loads(urllib.request.urlopen(req, timeout=30).read())
    print("\nPrompt accepted:")
    print(json.dumps(resp, indent=2)[:600])
    pid = resp.get("prompt_id")
    if pid:
        print(f"\nPrompt ID: {pid}")
        print("Polling /history ...")
        for i in range(120):
            time.sleep(5)
            h = json.loads(urllib.request.urlopen(f"{API}/history/{pid}", timeout=10).read())
            if h:
                status = h[pid].get("status", {})
                completed = status.get("completed")
                msgs = status.get("messages") or []
                print(f"  t={i*5}s completed={completed} msgs={len(msgs)}")
                if completed:
                    outs = h[pid].get("outputs", {})
                    print("\n=== OUTPUTS ===")
                    for nid, out in outs.items():
                        print(f"  node {nid}: {list(out.keys())}")
                    break
            else:
                # still queued or running
                q = json.loads(urllib.request.urlopen(f"{API}/queue", timeout=5).read())
                running = q.get("queue_running") or []
                pending = q.get("queue_pending") or []
                print(f"  t={i*5}s queue_running={len(running)} pending={len(pending)}")
except urllib.error.HTTPError as e:
    body = e.read().decode("utf-8", errors="replace")
    print(f"\nHTTP {e.code}: {body[:2000]}")
except Exception as e:
    print(f"\nERR: {type(e).__name__}: {e}")
