"""FikoYT MSR API — canli workflow dosyalarini Flutter UI'dan suren sablon motoru.

- GET  /fikoyt/msr/list           → FikoYT_LTX23_*.json workflow'lari + otomatik tespit edilen parametreleri
- POST /fikoyt/msr/submit         → {workflow, params, client_id} → UI-graph yukle→API'ye cevir→yama→/prompt'a gonder→prompt_id

Parametreler workflow'dan OTOMATIK tespit edilir (node tipine gore) → yeni workflow eklenince UI'da otomatik cikar.
"""
import os, json, glob, urllib.request

try:
    from server import PromptServer
    from aiohttp import web
    _OK = True
except Exception:
    _OK = False

_WF_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "user", "default", "workflows")
_COMFY = "http://127.0.0.1:8188"
_WIDGET = {"INT", "FLOAT", "STRING", "BOOLEAN", "COMBO"}
_SEED_CTRL = {"fixed", "randomize", "increment", "decrement"}
_PASS = ("GetNode", "Reroute", "SetNode")

# ---- object_info cache (widget sirasi icin) ----
_oi = {}
def _oinfo(t):
    if t in _oi:
        return _oi[t]
    try:
        import urllib.parse
        v = json.load(urllib.request.urlopen(f"{_COMFY}/object_info/{urllib.parse.quote(t)}", timeout=8))[t]
    except Exception:
        v = None
    _oi[t] = v
    return v


def _to_api(wf):
    nd = {n["id"]: n for n in wf["nodes"]}
    lm = {L[0]: (L[1], L[2]) for L in wf["links"]}
    setmap = {}
    for n in wf["nodes"]:
        if n["type"] == "SetNode" and n.get("inputs"):
            inl = n["inputs"][0].get("link")
            if inl in lm:
                setmap[n["widgets_values"][0]] = lm[inl]

    def resolve(s, seen=0):
        while s and nd.get(s[0], {}).get("type") in _PASS and seen < 20:
            seen += 1
            src = nd[s[0]]
            if src["type"] == "GetNode":
                s = setmap.get(src["widgets_values"][0])
            else:
                ins = src.get("inputs", [])
                s = lm.get(ins[0].get("link")) if ins else None
        return s

    api = {}
    for i, n in nd.items():
        t = n["type"]
        if t in ("SetNode", "GetNode", "Reroute", "Note", "MarkdownNote"):
            continue
        info = _oinfo(t)
        if not info:
            return None
        order = list(info["input"].get("required", {}).items()) + list(info["input"].get("optional", {}).items())
        linked = {}
        for x in n.get("inputs", []):
            if x.get("link") is None:
                continue
            linked[x["name"]] = resolve(lm.get(x["link"]))
        wv = list(n.get("widgets_values", []))
        wi = 0
        d = {}
        for name, spec in order:
            typ = spec[0] if isinstance(spec, (list, tuple)) else spec
            isw = isinstance(typ, list) or (isinstance(typ, str) and typ in _WIDGET)
            if name in linked and linked[name]:
                d[name] = [str(linked[name][0]), linked[name][1]]
                if isw and wi < len(wv):
                    wi += 1
                continue
            if isw and wi < len(wv):
                d[name] = wv[wi]
                wi += 1
                o = spec[1] if (isinstance(spec, (list, tuple)) and len(spec) > 1 and isinstance(spec[1], dict)) else {}
                if (o.get("control_after_generate") or "seed" in name.lower()) and wi < len(wv) and isinstance(wv[wi], str) and wv[wi] in _SEED_CTRL:
                    wi += 1
        api[str(i)] = {"class_type": t, "inputs": d}
    for i, n in nd.items():
        if n["type"] == "LoadImage":
            api[str(i)]["inputs"] = {"image": n["widgets_values"][0]}
        if n["type"] == "LoadAudio":
            api[str(i)]["inputs"] = {"audio": n["widgets_values"][0]}
    return api


def _neg(text):
    t = (text or "").lower()
    return any(k in t for k in ("worst quality", "blurry", "deformed", "low quality", "bad anatomy", "jittery"))


# UI'da mantikli siralama: dusuk = once (gorseller/prompt/sure/cozunurluk), yuksek = sonra (gelismis)
_SIRA = {
    "pozitif": 10, "negatif": 11, "ceviri": 12, "ceviri_model": 13,
    "saniye": 20, "genislik": 30, "yukseklik": 31,
    "cerceve": 40,
    "ses_modu": 50, "mp3": 51, "mp3_baslangic": 52,
    "prompt_birlestir": 59, "oto_tarif": 60, "auto_frame": 61, "ollama_model": 62,
    "cfg": 80, "frame_count": 81, "checkpoint": 90,
}


def _detect_params(wf):
    """UI-graph'i tarayip UI'da gosterilecek parametre listesini cikar."""
    nd = {n["id"]: n for n in wf["nodes"]}
    lm = {L[0]: (L[1], L[2]) for L in wf["links"]}

    # SetNode harita (GetNode -> kaynak cozumlemesi icin)
    setmap = {}
    for n in wf["nodes"]:
        if n["type"] == "SetNode" and n.get("inputs"):
            inl = n["inputs"][0].get("link")
            if inl in lm:
                setmap[n["widgets_values"][0]] = lm[inl]

    def kaynak(s, seen=0):
        # link kaynagini Reroute/GetNode/SetNode uzerinden gercek node'a kadar takip et
        while s and nd.get(s[0], {}).get("type") in _PASS and seen < 20:
            seen += 1
            src = nd[s[0]]
            if src["type"] == "GetNode":
                s = setmap.get(src["widgets_values"][0])
            else:
                ins = src.get("inputs", [])
                s = lm.get(ins[0].get("link")) if ins else None
        return s  # (node_id, out_slot) veya None

    # link -> (dst_node, dst_input_name)  (LoadImage rolu icin tek hedef yeterli)
    dst = {}
    for L in wf["links"]:
        dn = nd.get(L[3])
        if dn:
            ins = dn.get("inputs", [])
            if 0 <= L[4] < len(ins):
                dst[(L[1], L[2])] = (L[3], ins[L[4]]["name"])

    params = []
    keys = set()

    def add(key, tip, label, node, field, **extra):
        if key in keys:  # ayni key tekrar eklenmez (CFG cift / cakisma engeli)
            return
        keys.add(key)
        p = {"key": key, "tip": tip, "label": label, "node": str(node), "field": field}
        p.update(extra)
        params.append(p)

    # 1) Cozunurluk: EmptyLTXVLatentVideo'dan dogrudan cik (width/height bagliysa INTConstant'i,
    #    degilse Empty node'un kendi widget'ini parametre yap). Her workflow'da MUTLAKA cikar.
    for n in wf["nodes"]:
        if n["type"] != "EmptyLTXVLatentVideo":
            continue
        ew = n.get("widgets_values", [])
        ins = {i["name"]: i for i in n.get("inputs", [])}
        for fld, key, lbl, vw in (("width", "genislik", "Genişlik", 0), ("height", "yukseklik", "Yükseklik", 1)):
            link = ins.get(fld, {}).get("link") if fld in ins else None
            src = kaynak(lm.get(link)) if link is not None else None
            if src and nd.get(src[0], {}).get("type") == "INTConstant":
                iw = nd[src[0]].get("widgets_values", [])
                vd = iw[0] if iw else (1280 if fld == "width" else 720)
                add(key, "sayi", lbl, src[0], "value", varsayilan=vd, min=256, max=2048, adim=32)
            else:
                vd = ew[vw] if vw < len(ew) else (1280 if fld == "width" else 720)
                add(key, "sayi", lbl, n["id"], fld, varsayilan=vd, min=256, max=2048, adim=32)

    img_idx = 0
    for n in wf["nodes"]:
        t = n["type"]
        nid = n["id"]
        w = n.get("widgets_values", [])
        if t == "CLIPTextEncode":
            txt = w[0] if w else ""
            if _neg(txt):
                add("negatif", "metin", "Negatif Prompt", nid, "text", varsayilan=txt, satir=True)
            else:
                add("pozitif", "metin", "Prompt (sahneyi anlat)", nid, "text", varsayilan=txt, satir=True)
        elif t == "FikoYTVideoSuresi":
            add("saniye", "sayi", "Video Süresi (sn)", nid, "saniye", varsayilan=(w[0] if w else 6.0), min=0.5, max=30, adim=0.5)
        elif t == "CFGGuider":
            # Iki asamali workflow'da birden fazla CFGGuider var -> sadece ilki (ana sampler)
            add("cfg", "sayi", "CFG (rehberlik)", nid, "cfg", varsayilan=(w[0] if w else 1), min=1, max=8, adim=0.5, gelismis=True)
        elif t == "FikoYTMSRCompose":
            # cerceve combo widget'i widget sirasinda: width,height,frame_count,cerceve (opt)
            add("cerceve", "secim", "Çerçeve modu", nid, "cerceve",
                secenekler=["oto", "kirp", "sigdir", "doldur"], varsayilan="oto")
        elif t == "FikoYTMSRStudio":
            info = _oinfo(t) or {}
            opt = info.get("input", {}).get("optional", {}) if info else {}
            ms = opt.get("ollama_model")
            models = ms[0] if isinstance(ms, (list, tuple)) and ms and isinstance(ms[0], list) else ["llava:13b"]
            # aksiyon bir Sahne Prompt node'una bağlıysa pozitifi oradan al (çift gösterme)
            _ak = next((x for x in n.get("inputs", []) if x.get("name") == "aksiyon"), None)
            if not (_ak and _ak.get("link") is not None):
                add("pozitif", "metin", "Sahne / Aksiyon (yönetmen)", nid, "aksiyon",
                    varsayilan=(w[0] if w else ""), satir=True)
            add("prompt_birlestir", "secim", "Prompt birleştir (Kapalı = direkt/Ollama'sız)", nid, "prompt_birlestir",
                secenekler=["Açık", "Kapalı"], varsayilan="Açık")
            add("oto_tarif", "secim", "Otomatik tarif (VLM)", nid, "otomatik_tarif",
                secenekler=["Açık", "Kapalı"], varsayilan="Açık")
            add("auto_frame", "secim", "Akıllı kare sayısı", nid, "auto_frame",
                secenekler=["Açık", "Kapalı"], varsayilan="Açık")
            add("cerceve", "secim", "Çerçeve modu", nid, "cerceve",
                secenekler=["oto", "kirp", "sigdir", "doldur"], varsayilan="oto")
            add("ollama_model", "secim", "Ollama modeli", nid, "ollama_model",
                secenekler=models, varsayilan=(models[0] if models else "llava:13b"))
            add("frame_count", "sayi", "Referans kare (manuel)", nid, "frame_count",
                varsayilan=41, min=5, max=513, adim=1, gelismis=True)
        elif t == "FikoYTSahnePrompt":
            info = _oinfo(t) or {}
            opt = info.get("input", {}).get("optional", {}) if info else {}
            ms = opt.get("ollama_model")
            models = ms[0] if isinstance(ms, (list, tuple)) and ms and isinstance(ms[0], list) else ["llama3.1:8b"]
            add("pozitif", "metin", "Sahne / Aksiyon (Türkçe olabilir)", nid, "metin",
                varsayilan=(w[0] if w else ""), satir=True)
            add("ceviri", "secim", "İngilizce'ye çevir (Ollama)", nid, "ceviri",
                secenekler=["Açık", "Kapalı"], varsayilan="Açık")
            add("ceviri_model", "secim", "Çeviri modeli", nid, "ollama_model",
                secenekler=models, varsayilan=(models[0] if models else "llama3.1:8b"))
        elif t == "LiconMSR":
            add("frame_count", "secim", "Referans kare", nid, "frame_count",
                secenekler=["17", "25", "33", "41"], varsayilan="41", gelismis=True)
        elif t == "easy anythingIndexSwitch":
            add("ses_modu", "secim", "Ses kaynağı", nid, "index",
                secenekler=["0 = LTX üretsin", "1 = MP3 ver"], varsayilan="0 = LTX üretsin")
        elif t == "LoadAudio":
            add("mp3", "ses_dosya", "MP3 dosyası", nid, "audio", varsayilan=(w[0] if w else ""))
        elif t == "TrimAudioDuration":
            add("mp3_baslangic", "sayi", "MP3 başlangıç (sn)", nid, "start_index",
                varsayilan=(w[0] if w else 0), min=0, max=600, adim=1)
        elif t == "LoadImage":
            d = dst.get((nid, 0))
            rol = "Görsel"
            slot = None
            bg = False
            if d:
                dn = nd.get(d[0], {})
                dt = dn.get("type", "")
                fld = d[1]
                if dt in ("FikoYTMSRCompose", "LiconMSR", "FikoYTMSRStudio"):
                    if fld == "background":
                        rol = "Arka plan / mekan"
                        bg = True
                    elif fld.startswith("ozne_"):
                        slot = int(fld.split("_")[1])
                        rol = "Özne " + str(slot)
                    elif fld in ("1", "2", "3", "4"):
                        slot = int(fld)
                        rol = "Özne " + fld
                elif dt == "LTXVImgToVideoConditionOnly":
                    rol = "Anahtar kare (I2V)"
            img_idx += 1
            add(f"gorsel_{nid}", "gorsel", rol, nid, "image", varsayilan=(w[0] if w else ""),
                sira=img_idx, slot=slot, bg=bg)
        elif t == "FikoYTOzne":
            # tek node: resim + ad + açıklama. Rol, FIKOYT_OZNE'nin Studio'da bağlandığı alandan.
            d = dst.get((nid, 0))
            rol, slot, bg = "Özne", None, False
            if d:
                fld = d[1]
                if fld == "background":
                    rol, bg = "Arka plan / mekan", True
                elif fld.startswith("ozne_"):
                    slot = int(fld.split("_")[1])
                    rol = "Özne " + str(slot)
            img_idx += 1
            # gorsel param + companion ad/aciklama (UI resmin altında gösterir, gizli kutu değil)
            add(f"gorsel_{nid}", "gorsel", rol, nid, "image", varsayilan=(w[0] if w else ""),
                sira=img_idx, slot=slot, bg=bg, adlandirma=True,
                ad_key=f"ad_{nid}", aciklama_key=f"aciklama_{nid}")
            add(f"ad_{nid}", "metin", "Ad", nid, "ad", varsayilan=(w[1] if len(w) > 1 else ""), gizli=True)
            add(f"aciklama_{nid}", "metin", "Açıklama", nid, "aciklama", varsayilan=(w[2] if len(w) > 2 else ""), gizli=True)
        elif t in ("CheckpointLoaderSimple", "LowVRAMCheckpointLoader"):
            add("checkpoint", "secim_model", "Model (checkpoint)", nid, "ckpt_name",
                varsayilan=(w[0] if w else ""), gelismis=True)

    # gorseller en uste, sonra _SIRA'ya gore; bilinmeyen key'ler sona
    params.sort(key=lambda p: (0, p.get("sira", 0)) if p["tip"] == "gorsel" else (1, _SIRA.get(p["key"], 999)))
    return params


def _desc(name):
    n = name.lower()
    if "devi2v" in n:
        return "Tek görsel → dev modelle keskin video (I2V)"
    if "sesli_segment" in n:
        return "Çoklu özne + MP3 segment ses (NSFW destekli)"
    if "sinirsiz_sesli" in n:
        return "Sınırsız özne + MP3 segment ses"
    if "net48fps" in n:
        return "Çok katmanlı upscale, 48fps (en kaliteli, yavaş)"
    if "hizli" in n:
        return "Hızlı tek-kademe + opsiyonel ses (önerilen)"
    if "sinirsiz" in n:
        return "Sınırsız özne + çoklu açı (model sesi)"
    if "esnek" in n:
        return "1-4 özne opsiyonel + detailer"
    if "cokozne" in n or "cok_ozne" in n:
        return "Sabit 4 özne"
    return "FikoYT LTX 2.3 workflow"


def _list_workflows():
    out = []
    for f in sorted(glob.glob(os.path.join(_WF_DIR, "FikoYT_LTX23_*.json"))):
        try:
            wf = json.load(open(f, encoding="utf-8"))
            if "nodes" not in wf:
                continue
            params = _detect_params(wf)
            out.append({
                "dosya": os.path.basename(f),
                "ad": os.path.basename(f).replace("FikoYT_LTX23_", "").replace(".json", "").replace("_", " "),
                "aciklama": _desc(os.path.basename(f)),
                "parametreler": params,
            })
        except Exception as e:
            out.append({"dosya": os.path.basename(f), "hata": str(e)})
    return out


def _patch_and_submit(dosya, params, client_id):
    path = os.path.join(_WF_DIR, dosya)
    if not os.path.isfile(path):
        return {"error": "workflow yok: " + dosya}
    wf = json.load(open(path, encoding="utf-8"))
    spec = {p["key"]: p for p in _detect_params(wf)}
    api = _to_api(wf)
    if api is None:
        return {"error": "API'ye cevrilemedi (ComfyUI calisiyor mu?)"}
    # Prompt Düzenle node'u dialog'dan pause EDEMEZ (Devam butonu grafikte) → otomatik kapat
    for _nd in api.values():
        if _nd.get("class_type") == "FikoYTPromptDuzenle":
            _nd.setdefault("inputs", {})["aktif"] = False
    # yama
    for key, val in (params or {}).items():
        p = spec.get(key)
        if not p:
            continue
        node, field = p["node"], p["field"]
        if node not in api:
            continue
        tip = p["tip"]
        # ses_modu: "0 = ..." / "1 = ..." -> int index (0/1)
        if tip == "secim" and (key == "ses_modu" or field == "index"):
            try:
                val = int(str(val).split("=")[0].strip())
            except Exception:
                val = 0
        # Açık/Kapalı secim -> BOOLEAN
        elif tip == "secim" and key in ("oto_tarif", "auto_frame", "ceviri", "prompt_birlestir"):
            val = str(val).strip().lower().startswith("a")  # "Açık" -> True
        elif tip == "sayi":
            # genislik/yukseklik/saniye/mp3_baslangic -> dogru sayisal tip
            try:
                fv = float(val)
                # adim ondalikliysa float, degilse int (genislik=32, saniye=0.5)
                adim = p.get("adim", 1)
                val = fv if (isinstance(adim, float) and adim != int(adim)) else int(round(fv))
            except Exception:
                pass
        api[node]["inputs"][field] = val
    body = {"prompt": api}
    if client_id:
        body["client_id"] = client_id
    try:
        req = urllib.request.Request(f"{_COMFY}/prompt", data=json.dumps(body).encode(),
                                     headers={"Content-Type": "application/json"})
        r = json.load(urllib.request.urlopen(req, timeout=30))
        return {"prompt_id": r.get("prompt_id"), "node_errors": r.get("node_errors") or {}}
    except urllib.error.HTTPError as e:
        try:
            det = json.load(e)
        except Exception:
            det = {"error": str(e)}
        return {"error": "ComfyUI reddetti", "detay": det.get("node_errors") or det.get("error")}
    except Exception as e:
        return {"error": str(e)}


if _OK:
    rt = PromptServer.instance.routes

    @rt.get("/fikoyt/msr/list")
    async def _msr_list(request):
        return web.json_response({"workflows": _list_workflows()})

    @rt.post("/fikoyt/msr/submit")
    async def _msr_submit(request):
        try:
            data = await request.json()
        except Exception:
            data = {}
        res = _patch_and_submit(data.get("workflow", ""), data.get("params", {}), data.get("client_id"))
        return web.json_response(res)

NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
