# -*- coding: utf-8 -*-
"""
ComfyUI-H3-Music-Video-AutoShot
================================

H3 Studio (ComfyUI-CGlide / CSGlideCastCS) icin, ComfyUI arayuzunun
ICINDEN calisan, sarkiyi beat'lerine gore otomatik sahnelere bolen node.

Kullanim:
  LoadAudio -> H3 Music Video AutoShot -> (Queue Prompt)

Node calistiginda:
  - Sarkiyi beat/tempo analiziyle en fazla max_shot_seconds uzunlugunda,
    beat sinirlarina yapisik parcalara boler.
  - Her parcanin GERCEK ses dilimini ComfyUI/input/cglide/ altina wav
    olarak yazar.
  - Kaynak H3 Studio workflow JSON'unu okuyup CSGlideCastCS node'unun
    gcast_project.shots listesini bu sahnelerle DEGISTIRIR (audio
    referansi + dogru frame uzunlugu + "bu sesi birebir kullan" prompt
    sablonuyla) ve yeni bir workflow JSON dosyasi olarak yazar.

Not: H3 Studio'nun proje verisi o node'un kendi JS arayuzunde tutuluyor,
calisan bir ComfyUI grafiginden canli olarak o arayuze yazilamiyor. Bu
yuzden bu node CIKTI OLARAK YENI BIR WORKFLOW DOSYASI uretir; sen bu
dosyayi ComfyUI'da (File > Open) actiginda sahneler hazir gelir.
"""

import json
import os
import random
import string

import numpy as np

FPS = 24


def _make_shot_id():
    return "".join(random.choices(string.ascii_lowercase + string.digits, k=7))


def _build_prompt(shot_index, duration_s):
    return (
        f"[SAHNE {shot_index} - buraya gorsel/sahne aciklamasini yaz: "
        f"karakter(ler), mekan, aksiyon, kamera hareketi]\n\n"
        f"@audio1 bu sahnenin SES KAYNAGIDIR ve KESIN HUKUMDUR: bu sesi/muzigi "
        f"OLDUGU GIBI, degistirmeden, yeniden yorumlamadan kullan. Kendi muzigini "
        f"veya farkli bir ses uretme. Karakterlerin dudak hareketlerini, "
        f"kostumleri ve varsa dansi/hareketini bu sesin ritmine ve suresine "
        f"({duration_s:.2f} saniye) birebir senkronla. Sahne, sesin bittigi anda "
        f"bitmelidir."
    )


def _detect_segments(y, sr, max_shot_seconds, min_shot_seconds):
    import librosa

    duration = float(len(y)) / float(sr)
    tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr)
    try:
        tempo_val = float(tempo)
    except TypeError:
        tempo_val = float(tempo[0]) if len(tempo) else 0.0
    beat_times = librosa.frames_to_time(beat_frames, sr=sr).tolist()

    candidates = sorted(set([0.0] + [round(t, 3) for t in beat_times] + [round(duration, 3)]))

    segments = []
    seg_start = 0.0
    while seg_start < duration - 0.05:
        best = None
        for t in candidates:
            if t <= seg_start + min_shot_seconds:
                continue
            if t - seg_start <= max_shot_seconds:
                best = t
            else:
                break
        if best is None:
            best = min(seg_start + max_shot_seconds, duration)
        segments.append((seg_start, best))
        seg_start = best

    return tempo_val, segments


class H3MusicVideoAutoShot:
    """Sarkiyi beat'lerine gore H3 Studio sahnelerine otomatik boler."""

    CATEGORY = "CGlide/MusicVideo"
    RETURN_TYPES = ("STRING",)
    RETURN_NAMES = ("report",)
    FUNCTION = "run"
    OUTPUT_NODE = True

    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "audio": ("AUDIO",),
                "workflow_path": ("STRING", {
                    "default": r"D:\Comfy\App\ComfyUI_windows_portable\ComfyUI\user\default\workflows\MiniMax_H3_00125_preview.json",
                    "multiline": False,
                }),
                "out_path": ("STRING", {
                    "default": r"D:\Comfy\App\ComfyUI_windows_portable\ComfyUI\user\default\workflows\MiniMax_H3_music_video_auto.json",
                    "multiline": False,
                }),
                "comfyui_root": ("STRING", {
                    "default": r"D:\Comfy\App\ComfyUI_windows_portable\ComfyUI",
                    "multiline": False,
                }),
                "max_shot_seconds": ("FLOAT", {"default": 8.0, "min": 1.0, "max": 15.0, "step": 0.5}),
                "min_shot_seconds": ("FLOAT", {"default": 2.0, "min": 0.5, "max": 10.0, "step": 0.5}),
            }
        }

    def run(self, audio, workflow_path, out_path, comfyui_root,
             max_shot_seconds, min_shot_seconds):

        waveform = audio["waveform"]  # [batch, channels, samples]
        sr = int(audio["sample_rate"])
        wav = waveform[0].detach().cpu().numpy()
        if wav.ndim > 1:
            y = wav.mean(axis=0)
        else:
            y = wav
        y = y.astype(np.float32)

        if not os.path.isfile(workflow_path):
            msg = f"HATA: workflow bulunamadi: {workflow_path}"
            return (msg,)
        if not os.path.isdir(comfyui_root):
            msg = f"HATA: comfyui_root bulunamadi: {comfyui_root}"
            return (msg,)

        audio_out_dir = os.path.join(comfyui_root, "input", "cglide")
        os.makedirs(audio_out_dir, exist_ok=True)

        with open(workflow_path, "r", encoding="utf-8") as f:
            wf = json.load(f)

        cast_node = None
        for node in wf.get("nodes", []):
            if node.get("type") == "CSGlideCastCS":
                cast_node = node
                break
        if cast_node is None:
            return ("HATA: workflow icinde CSGlideCastCS (H3 Studio) node'u bulunamadi.",)

        project = cast_node["properties"].get("gcast_project")
        if not project or not project.get("shots"):
            return ("HATA: gcast_project.shots bos.",)

        template_shot = project["shots"][0]
        template_state = template_shot["state"]
        width = template_state.get("width", 640)
        height = template_state.get("height", 960)
        template_images = template_state.get("slots", {}).get("images", [{}] * 9)

        tempo, segments = _detect_segments(y, sr, max_shot_seconds, min_shot_seconds)

        import soundfile as sf

        new_shots = []
        lines = [f"Tempo ~{tempo:.1f} BPM, {len(segments)} sahne:"]
        for i, (start_s, end_s) in enumerate(segments, start=1):
            dur = end_s - start_s
            frames = max(1, round(dur * FPS))
            audio_filename = f"song_shot_{i:02d}.wav"
            audio_out_path = os.path.join(audio_out_dir, audio_filename)
            i0 = int(start_s * sr)
            i1 = int(end_s * sr)
            sf.write(audio_out_path, y[i0:i1], sr)

            shot = {
                "id": _make_shot_id(),
                "name": f"Sahne {i}",
                "off": False,
                "state": {
                    "mode": "ref2va",
                    "width": width,
                    "height": height,
                    "length": frames,
                    "ref_image_size": "max",
                    "pace": template_state.get("pace", 2.5),
                    "prompt": _build_prompt(i, dur),
                    "slots": {
                        "first": {},
                        "last": {},
                        "images": template_images,
                        "videos": [{}, {}, {}],
                        "audios": [{"file": f"cglide/{audio_filename}"}, {}, {}],
                    },
                    "cont": {},
                },
            }
            new_shots.append(shot)
            lines.append(f"  #{i}: {start_s:.2f}s-{end_s:.2f}s ({dur:.2f}s, {frames} frame) -> {audio_filename}")

        project["shots"] = new_shots
        project["idx"] = 0
        cast_node["properties"]["gcast_project"] = project
        cast_node["properties"].pop("gcast_project_prev", None)
        if "widgets_values" in cast_node and cast_node["widgets_values"]:
            cast_node["widgets_values"][0] = json.dumps(new_shots[0]["state"], ensure_ascii=False)
        if "widgets_values_named" in cast_node and "h3_data" in cast_node.get("widgets_values_named", {}):
            cast_node["widgets_values_named"]["h3_data"] = json.dumps(new_shots[0]["state"], ensure_ascii=False)

        os.makedirs(os.path.dirname(out_path), exist_ok=True)
        with open(out_path, "w", encoding="utf-8") as f:
            json.dump(wf, f, ensure_ascii=False)

        lines.append(f"Yazildi: {out_path}")
        lines.append("Bu dosyayi ComfyUI'da File > Open ile ac; sahneler hazir gelir.")
        report = "\n".join(lines)
        print("[H3MusicVideoAutoShot]\n" + report)
        return (report,)


NODE_CLASS_MAPPINGS = {
    "H3MusicVideoAutoShot": H3MusicVideoAutoShot,
}

NODE_DISPLAY_NAME_MAPPINGS = {
    "H3MusicVideoAutoShot": "H3 Music Video AutoShot",
}
