#!/usr/bin/env python3
"""
ResolveScripter Bridge Server
─────────────────────────────
Startet einen lokalen HTTP-Server auf Port 5050.
Spricht mit DaVinci Resolve über die offizielle Python API
und stellt die Daten als REST-Endpunkte für die WebApp bereit.

Starten:
    python3 resolve_bridge.py

Voraussetzungen:
    pip install flask flask-cors
    DaVinci Resolve muss geöffnet sein (Free oder Studio)
"""

from flask import Flask, jsonify, request
from flask_cors import CORS
import sys
import os

app = Flask(__name__)
CORS(app)  # Erlaubt Anfragen vom Browser (localhost)

# ── Resolve-Verbindung ──────────────────────────────────────────────────────

def get_resolve():
    """Verbindung zu DaVinci Resolve herstellen."""
    try:
        import DaVinciResolveScript as bmd
        resolve = bmd.scriptapp("Resolve")
        if resolve is None:
            return None, "Resolve läuft nicht oder API ist nicht aktiv"
        return resolve, None
    except ImportError:
        # Fallback: Resolve-Pfad manuell hinzufügen
        resolve_paths = [
            # macOS
            "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting/Modules",
            # Windows
            r"C:\ProgramData\Blackmagic Design\DaVinci Resolve\Support\Developer\Scripting\Modules",
            # Linux
            "/opt/resolve/libs/Fusion/Modules",
        ]
        for p in resolve_paths:
            if os.path.exists(p) and p not in sys.path:
                sys.path.append(p)
        try:
            import DaVinciResolveScript as bmd
            resolve = bmd.scriptapp("Resolve")
            return resolve, None
        except Exception as e:
            return None, f"DaVinciResolveScript nicht gefunden: {e}"
    except Exception as e:
        return None, str(e)


def get_project_context():
    """Resolve, ProjectManager, Project und Timeline auf einmal holen."""
    resolve, err = get_resolve()
    if err:
        return None, None, None, None, err
    pm = resolve.GetProjectManager()
    project = pm.GetCurrentProject()
    if not project:
        return resolve, pm, None, None, "Kein Projekt geöffnet"
    tl = project.GetCurrentTimeline()
    return resolve, pm, project, tl, None


# ── Hilfsfunktion ──────────────────────────────────────────────────────────

def err_response(msg, code=500):
    return jsonify({"ok": False, "error": msg}), code

def ok_response(data):
    return jsonify({"ok": True, **data})


# ══════════════════════════════════════════════════════════════════════════════
# ENDPUNKTE
# ══════════════════════════════════════════════════════════════════════════════

# ── /status ── Verbindungstest ────────────────────────────────────────────────
@app.route("/status")
def status():
    resolve, err = get_resolve()
    if err:
        return err_response(err)
    pm = resolve.GetProjectManager()
    project = pm.GetCurrentProject()
    project_name = project.GetName() if project else "(kein Projekt)"
    tl = project.GetCurrentTimeline() if project else None
    tl_name = tl.GetName() if tl else "(keine Timeline)"
    return ok_response({
        "resolve_version": resolve.GetVersionString() if hasattr(resolve, "GetVersionString") else "unbekannt",
        "project": project_name,
        "timeline": tl_name,
    })


# ── /timeline ── Timeline-Info ────────────────────────────────────────────────
@app.route("/timeline")
def timeline_info():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)
    if not tl:
        return err_response("Keine aktive Timeline")

    return ok_response({
        "name":       tl.GetName(),
        "fps":        tl.GetSetting("timelineFrameRate"),
        "width":      tl.GetSetting("timelineResolutionWidth"),
        "height":     tl.GetSetting("timelineResolutionHeight"),
        "start":      tl.GetStartFrame(),
        "end":        tl.GetEndFrame(),
        "v_tracks":   tl.GetTrackCount("video"),
        "a_tracks":   tl.GetTrackCount("audio"),
        "s_tracks":   tl.GetTrackCount("subtitle"),
    })


# ── /timeline/tracks ── Alle Tracks ──────────────────────────────────────────
@app.route("/timeline/tracks")
def timeline_tracks():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)
    if not tl:
        return err_response("Keine aktive Timeline")

    tracks = []
    for track_type in ["video", "audio", "subtitle"]:
        count = tl.GetTrackCount(track_type)
        for i in range(1, count + 1):
            name = tl.GetTrackName(track_type, i)
            tracks.append({
                "type":  track_type,
                "index": i,
                "name":  name,
            })
    return ok_response({"tracks": tracks})


# ── /timeline/clips ── Clips eines Tracks ────────────────────────────────────
@app.route("/timeline/clips")
def timeline_clips():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)
    if not tl:
        return err_response("Keine aktive Timeline")

    track_type  = request.args.get("type", "video")
    track_index = int(request.args.get("index", 1))

    items = tl.GetItemListInTrack(track_type, track_index)
    clips = []
    for item in (items or []):
        clips.append({
            "name":     item.GetName(),
            "start":    item.GetStart(),
            "end":      item.GetEnd(),
            "duration": item.GetDuration(),
            "color":    item.GetClipColor() if hasattr(item, "GetClipColor") else "",
            "flags":    item.GetFlagList() if hasattr(item, "GetFlagList") else [],
        })
    return ok_response({"clips": clips, "track_type": track_type, "track_index": track_index})


# ── /timeline/markers ── Alle Marker lesen ────────────────────────────────────
@app.route("/timeline/markers", methods=["GET"])
def get_markers():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)
    if not tl:
        return err_response("Keine aktive Timeline")

    raw = tl.GetMarkers() or {}
    markers = [
        {
            "frame":    int(frame),
            "name":     data.get("name", ""),
            "color":    data.get("color", "Blue"),
            "note":     data.get("note", ""),
            "duration": data.get("duration", 1),
            "customData": data.get("customData", ""),
        }
        for frame, data in raw.items()
    ]
    markers.sort(key=lambda m: m["frame"])
    return ok_response({"markers": markers})


# ── /timeline/markers/add ── Marker hinzufügen ────────────────────────────────
@app.route("/timeline/markers/add", methods=["POST"])
def add_marker():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)
    if not tl:
        return err_response("Keine aktive Timeline")

    body = request.get_json() or {}
    frame    = int(body.get("frame", 0))
    color    = body.get("color", "Blue")
    name     = body.get("name", "Marker")
    note     = body.get("note", "")
    duration = int(body.get("duration", 1))
    custom   = body.get("customData", "")

    result = tl.AddMarker(frame, color, name, note, duration, custom)
    if result:
        return ok_response({"message": f"Marker '{name}' an Frame {frame} gesetzt"})
    return err_response("Marker konnte nicht gesetzt werden (Frame bereits belegt?)")


# ── /timeline/markers/delete ── Marker löschen ───────────────────────────────
@app.route("/timeline/markers/delete", methods=["POST"])
def delete_marker():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)
    if not tl:
        return err_response("Keine aktive Timeline")

    body  = request.get_json() or {}
    frame = body.get("frame")
    color = body.get("color")

    if frame is not None:
        result = tl.DeleteMarkerAtFrame(int(frame))
        return ok_response({"message": f"Marker an Frame {frame} gelöscht"}) if result else err_response("Kein Marker an diesem Frame")
    elif color:
        tl.DeleteMarkersByColor(color)
        return ok_response({"message": f"Alle {color}-Marker gelöscht"})
    return err_response("Bitte 'frame' oder 'color' angeben")


# ── /timeline/set ── Timeline-Setting ändern ──────────────────────────────────
@app.route("/timeline/set", methods=["POST"])
def set_timeline():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)
    if not tl:
        return err_response("Keine aktive Timeline")

    body = request.get_json() or {}
    key  = body.get("key")
    val  = body.get("value")
    if not key:
        return err_response("'key' fehlt")

    result = tl.SetSetting(key, str(val))
    return ok_response({"message": f"{key} = {val}"}) if result else err_response(f"Setting '{key}' konnte nicht gesetzt werden")


# ── /projects ── Alle Projekte ────────────────────────────────────────────────
@app.route("/projects")
def list_projects():
    resolve, err = get_resolve()
    if err:
        return err_response(err)
    pm = resolve.GetProjectManager()

    # Projekte im aktuellen Ordner
    projects = pm.GetProjectListInCurrentFolder()
    current  = pm.GetCurrentProject()
    cur_name = current.GetName() if current else ""

    return ok_response({
        "projects": projects or [],
        "current":  cur_name,
    })


# ── /timelines ── Alle Timelines im Projekt ───────────────────────────────────
@app.route("/timelines")
def list_timelines():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)

    count = project.GetTimelineCount()
    cur_name = tl.GetName() if tl else ""
    timelines = []
    for i in range(1, count + 1):
        t = project.GetTimelineByIndex(i)
        timelines.append({
            "index":   i,
            "name":    t.GetName(),
            "current": t.GetName() == cur_name,
        })
    return ok_response({"timelines": timelines})


# ── /render/add ── Render-Job hinzufügen ─────────────────────────────────────
@app.route("/render/add", methods=["POST"])
def render_add():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)

    body   = request.get_json() or {}
    fmt    = body.get("format", "mov")
    codec  = body.get("codec",  "H.265 Master")
    path   = body.get("targetDir", "/tmp")
    name   = body.get("customName", "Export")
    all_f  = body.get("selectAllFrames", True)

    project.SetCurrentRenderFormatAndCodec(fmt, codec)
    settings = {
        "SelectAllFrames": all_f,
        "TargetDir":       path,
        "CustomName":      name,
    }
    project.SetRenderSettings(settings)
    job_id = project.AddRenderJob()
    return ok_response({"job_id": job_id, "message": f"Job '{name}' zur Queue hinzugefügt"})


# ── /render/start ── Render starten ──────────────────────────────────────────
@app.route("/render/start", methods=["POST"])
def render_start():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)

    result = project.StartRendering()
    return ok_response({"message": "Render gestartet"}) if result else err_response("Render konnte nicht gestartet werden")


# ── /render/status ── Render-Fortschritt ─────────────────────────────────────
@app.route("/render/status")
def render_status():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)

    running = project.IsRenderingInProgress()
    jobs    = project.GetRenderJobList() or []
    job_statuses = []
    for job in jobs:
        jid    = job.get("JobId", "")
        status = project.GetRenderJobStatus(jid) if jid else {}
        job_statuses.append({
            "id":         jid,
            "name":       job.get("RenderJobName", ""),
            "completion": status.get("CompletionPercentage", 0),
            "status":     status.get("JobStatus", ""),
        })
    return ok_response({"rendering": running, "jobs": job_statuses})


# ── /render/clear ── Queue leeren ─────────────────────────────────────────────
@app.route("/render/clear", methods=["POST"])
def render_clear():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)
    project.DeleteAllRenderJobs()
    return ok_response({"message": "Render-Queue geleert"})


# ── /export/timeline ── Timeline exportieren ──────────────────────────────────
@app.route("/export/timeline", methods=["POST"])
def export_timeline():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)
    if not tl:
        return err_response("Keine aktive Timeline")

    body      = request.get_json() or {}
    fmt_str   = body.get("format", "EDL")       # EDL | FCPXML | AAF | OTIO
    path      = body.get("path", "/tmp/export")

    fmt_map = {
        "EDL":    resolve.EXPORT_EDL,
        "FCPXML": resolve.EXPORT_FCPXML,
        "AAF":    resolve.EXPORT_AAF,
        "OTIO":   resolve.EXPORT_OTIO if hasattr(resolve, "EXPORT_OTIO") else resolve.EXPORT_EDL,
    }
    fmt_const = fmt_map.get(fmt_str.upper(), resolve.EXPORT_EDL)

    result = tl.Export(path, fmt_const, resolve.EXPORT_NONE)
    return ok_response({"message": f"Export als {fmt_str} nach: {path}"}) if result else err_response("Export fehlgeschlagen")


# ── /mediapool ── MediaPool-Inhalt ────────────────────────────────────────────
@app.route("/mediapool")
def mediapool_info():
    resolve, pm, project, tl, err = get_project_context()
    if err:
        return err_response(err)

    pool = project.GetMediaPool()
    root = pool.GetRootFolder()

    def folder_to_dict(folder):
        clips = folder.GetClipList() or []
        sub   = folder.GetSubFolderList() or []
        return {
            "name":      folder.GetName(),
            "clip_count": len(clips),
            "clips": [{"name": c.GetName()} for c in clips[:20]],  # max 20
            "subfolders": [folder_to_dict(s) for s in sub[:10]],    # max 10
        }

    return ok_response({"mediapool": folder_to_dict(root)})


# ══════════════════════════════════════════════════════════════════════════════
# START
# ══════════════════════════════════════════════════════════════════════════════

if __name__ == "__main__":
    print("=" * 55)
    print("  ResolveScripter Bridge Server")
    print("  http://localhost:5050")
    print("  DaVinci Resolve muss geöffnet sein")
    print("=" * 55)
    print()
    app.run(host="127.0.0.1", port=5050, debug=False)
