#!/usr/bin/env python3
from importlib import metadata as importlib_metadata
import sys
import os
import time  # Import time für sleep
import traceback
import re

sys.path.append(os.path.dirname(os.path.abspath(__file__)))

def _read_version_from_pyproject(pyproject_path):
    """Read project version from a pyproject.toml file."""
    import tomllib

    try:
        with open(pyproject_path, "rb") as f:
            data = tomllib.load(f)
        return data.get("project", {}).get("version")
    except Exception:
        return None


def _read_pyproject_data(pyproject_path):
    """Read and return parsed pyproject data."""
    import tomllib

    try:
        with open(pyproject_path, "rb") as f:
            return tomllib.load(f)
    except Exception:
        return {}


def _get_pyproject_path():
    """Return the closest repo/installation pyproject.toml path if present."""
    return os.path.join(
        os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
        "pyproject.toml",
    )


def _normalize_distribution_name(requirement):
    """Extract the distribution name from a dependency specifier string."""
    requirement = (requirement or "").split(";", 1)[0].strip()
    requirement = requirement.split("[", 1)[0].strip()
    match = re.match(r"^[A-Za-z0-9_.-]+", requirement)
    return match.group(0) if match else requirement


def _normalize_cli_mode(mode):
    """Normalize CLI subcommands so hyphen and underscore variants behave the same."""
    return (mode or "").strip().replace("-", "_")


def _get_first_non_option_arg(args):
    """Return the first positional CLI argument from a list of args, or None."""
    for arg in args:
        if not arg.startswith("-"):
            return arg
    return None


def _get_installed_distribution_version(distribution_name):
    """Return installed version for a distribution name, or None if unavailable."""
    if not distribution_name:
        return None
    try:
        return importlib_metadata.version(distribution_name)
    except importlib_metadata.PackageNotFoundError:
        return None
    except Exception:
        return None


def _collect_dependency_versions():
    """Collect dependency versions from the current project's pyproject.toml."""
    pyproject_path = _get_pyproject_path()
    data = _read_pyproject_data(pyproject_path)
    dependencies = data.get("project", {}).get("dependencies", [])

    collected = []
    for dependency in dependencies:
        dist_name = _normalize_distribution_name(dependency)
        installed_version = _get_installed_distribution_version(dist_name)
        collected.append({
            "requirement": dependency,
            "distribution": dist_name,
            "version": installed_version,
        })
    return collected


def _build_version_report_lines():
    """Build detailed runtime and dependency version lines."""
    manager_dist_version = (
        _get_installed_distribution_version("kurmann-internetfilm-manager")
        or _get_installed_distribution_version("internetfilm-manager")
    )

    lines = [
        f"internetfilm-manager {get_version()}",
        f"python: {sys.version.split()[0]} ({sys.executable})",
        f"cli: {os.path.abspath(__file__)}",
    ]
    if manager_dist_version:
        lines.append(f"installed dist: {manager_dist_version}")

    lines.append("dependencies:")
    for item in _collect_dependency_versions():
        version_text = item["version"] or "not installed"
        lines.append(f"  - {item['distribution']}: {version_text}    [{item['requirement']}]")
    return lines


def _build_startup_banner_lines():
    """Build the compact dependency summary for the interactive startup banner."""
    dependencies = _collect_dependency_versions()
    dep_summary = ", ".join(
        f"{item['distribution']}={item['version'] or 'n/a'}"
        for item in dependencies
    )
    return [
        f"🔧 Abhängigkeiten: {dep_summary}",
    ]


def print_version_report():
    """Print detailed runtime and dependency version information."""
    for line in _build_version_report_lines():
        print(line)


def get_version():
    """Liest die Version bevorzugt aus dem lokalen Repo, sonst aus Paket-Metadaten."""
    # Wenn direkt aus dem Source-Tree gestartet wird, soll die Repo-Version
    # angezeigt werden und nicht eine evtl. parallel installierte alte Version.
    source_pyproject = _get_pyproject_path()
    if os.path.isfile(source_pyproject):
        source_version = _read_version_from_pyproject(source_pyproject)
        if source_version:
            return source_version

    try:
        for dist_name in ("kurmann-internetfilm-manager", "internetfilm-manager"):
            installed_version = _get_installed_distribution_version(dist_name)
            if installed_version:
                return installed_version
    except Exception:
        pass

    installed_pyproject = _get_pyproject_path()
    installed_version = _read_version_from_pyproject(installed_pyproject)
    if installed_version:
        return installed_version

    return "unknown"

from integrator.module_archive import build_master
from integrator.config import CONFIG, update_config, get_available_targets
from integrator.phase_fetch import fetch_video
from integrator.phase_curate import interactive_curation
from integrator.phase_build import build_dist
from integrator.phase_deploy import deploy
from integrator.utils import get_state, normalize_video_input
from integrator.phase_import import restore_multiple_archives, extract_mp3_from_dmg_archives, _lookup_urls_from_csv, _extract_url_from_dmg
from integrator.module_analyze import analyze_video_with_raw_output
from integrator.module_index import index_server_videos
from integrator.module_search import interactive_search_menu
from integrator.module_classification_rules import learn_classification_rules, suggest_target_for_video
from integrator.module_category_change import interactive_category_change
from integrator.module_delete import interactive_video_deletion

# Helper Funktion für Targets
def _get_targets():
    """Holt die verfügbaren Ziele aus der Config."""
    targets = CONFIG.get("video_targets", ["Allgemein"])[:] # Kopie erstellen
    music_target = CONFIG.get("music_target", "Musikvideos")
    if music_target and music_target not in targets:
        targets.append(music_target)
    return targets

def list_jobs():
    work_dir = CONFIG.get("work_dir")
    if not work_dir or not os.path.exists(work_dir): return []
    jobs = []
    for f in sorted(os.listdir(work_dir)):
        path = os.path.join(work_dir, f)
        if os.path.isdir(path) and f.startswith("job_"):
            title = f
            json_path = os.path.join(path, "original.info.json")
            if os.path.exists(json_path):
                try:
                    import json
                    with open(json_path, 'r') as jf:
                        meta = json.load(jf)
                        title = meta.get('title', f)[:60]
                except: pass
            state = get_state(path)
            is_curated = state.get("is_curated", False)
            
            # Wenn kuratiert, zeige den finalen Titel an (ohne Pipes, etc.)
            if is_curated and state.get("curated_title"):
                title = state.get("curated_title")[:60]
                
            jobs.append((f, title, path, is_curated))
    return jobs

def _select_target_interactive():
    # Targets abrufen
    targets = _get_targets()
    
    # Check if auto-classification is enabled
    classification_enabled = CONFIG.get("classification_enabled", False)
    
    # If classification is enabled, add "Auto" option at the beginning
    if classification_enabled:
        display_targets = ["Auto"] + targets
        default_option = "Auto"
    else:
        display_targets = targets
        default_option = targets[0]
    
    print("   Ziel wählen:")
    for i, t in enumerate(display_targets, 1):
        print(f"   {i}. {t}")
    print("   z. Abbrechen")
    
    if classification_enabled:
        print("   ℹ️  'Auto': Kategorie wird bei Kuratierung durch K.I. festgelegt")
    
    t_sel = input(f"   Auswahl (1-{len(display_targets)} oder 'z' für Abbrechen) [Default: {default_option}]: ").strip()
    
    if t_sel.lower() == 'z':
        return None  # Signal to cancel
    
    if t_sel.isdigit():
        idx = int(t_sel) - 1
        if 0 <= idx < len(display_targets):
            return display_targets[idx]
    
    return default_option # Default

def settings_menu():
    while True:
        print("\n--- ⚙️  EINSTELLUNGEN ---")
        claude_key = CONFIG.get('claude_key', '')
        key_display = f"{claude_key[:10]}...******" if claude_key else "(Nicht gesetzt)"
        print(f"1. 🔑 Claude API Key:        {key_display}")
        print(f"2. 🤖 Claude Modell:         {CONFIG.get('claude_model', 'claude-sonnet-4-20250514')}")
        light_model = CONFIG.get('claude_model_light', '')
        light_display = light_model if light_model else "(Nicht gesetzt – nutzt Hauptmodell)"
        print(f"3. 🤖 Claude Light-Modell:   {light_display}")
        print(f"4. 📂 Work Directory:        {CONFIG.get('work_dir')}")
        print(f"5. 📡 Rclone Media-Pfad:     {CONFIG.get('rclone_root')}")
        print(f"6. 📦 Rclone Master-Pfad:    {CONFIG.get('rclone_master_root')}")
        print(f"7. 🔄 Retry bei Fehler:      {'Aktiviert' if CONFIG.get('retry_on_failure') else 'Deaktiviert'}")
        print(f"8. ⏱️  Retry Wartezeit:       {CONFIG.get('retry_wait_minutes', 30)} Minuten")
        print(f"9. 🔢 Max. Retries:          {CONFIG.get('max_retries', 3)}")
        print(f"10. 🔁 Batch Auto-Retry:      {'Aktiviert' if CONFIG.get('batch_auto_retry', True) else 'Deaktiviert'}")
        print(f"11. 🔢 Max. Batch-Retries:   {CONFIG.get('max_batch_retries', 10)}")
        print(f"12. 🎯 Video-Kategorien:     {', '.join(CONFIG.get('video_targets', []))}")
        print(f"13. 🎵 Musik-Ordnername:     {CONFIG.get('music_target', 'Musikvideos')}")
        print(f"14. 🧠 Auto-Klassifikation:  {'Aktiviert' if CONFIG.get('classification_enabled', False) else 'Deaktiviert'}")
        print(f"15. 📊 Klassifikations-Schwelle: {CONFIG.get('classification_rules_threshold', 10)} Videos")
        playlists_count = len(CONFIG.get('auto_download_playlists', []))
        print(f"16. 📋 Auto-Download Playlists: {playlists_count} konfiguriert")
        print(f"17. 🔖 Kapitel-Erstellung:    {'Aktiviert' if CONFIG.get('chapters_enabled', True) else 'Deaktiviert'}")
        print("z. Zurück")
        
        sel = input("\nAuswahl: ").strip().lower()
        
        if sel == 'z': break
            
        elif sel == '1':
            print(f"\nAktuell: {CONFIG.get('claude_key', '')}")
            new_val = input("Neuer API Key: ").strip()
            if new_val: update_config("claude_key", new_val)

        elif sel == '2':
            print(f"\nAktuelles Modell: {CONFIG.get('claude_model', 'claude-sonnet-4-20250514')}")
            print("\nModellname eingeben (z.B. claude-sonnet-4-20250514, claude-haiku-4-20250514):")
            new_val = input("Modellname: ").strip()
            if new_val:
                update_config("claude_model", new_val)

        elif sel == '3':
            print(f"\nAktuelles Light-Modell: {CONFIG.get('claude_model_light', '(Nicht gesetzt)')}")
            print("\nGünstigeres Modell für einfache Aufgaben wie Spracherkennung und Musik-Analyse.")
            print("Leer lassen = Hauptmodell verwenden.")
            print("\nBeispiele: claude-haiku-4-20250514 (empfohlen)")
            new_val = input("Light-Modellname (oder Enter für leer): ").strip()
            update_config("claude_model_light", new_val)

        elif sel == '4':
            print(f"\nAktuell: {CONFIG.get('work_dir')}")
            new_val = input("Neuer Pfad: ").strip().strip('"').strip("'")
            if new_val: update_config("work_dir", os.path.expanduser(new_val))

        elif sel == '5':
            print(f"\nAktuell: {CONFIG.get('rclone_root')}")
            print("Beispiel: lys-nas:/Emby/media/Internetfilme")
            new_val = input("Neuer Pfad: ").strip().strip('"').strip("'")
            if new_val: update_config("rclone_root", new_val)

        elif sel == '6':
            print(f"\nAktuell: {CONFIG.get('rclone_master_root')}")
            print("Pfad für DMG-Archive (Master).")
            new_val = input("Neuer Pfad: ").strip().strip('"').strip("'")
            if new_val: update_config("rclone_master_root", new_val)

        elif sel == '7':
            current = CONFIG.get('retry_on_failure', False)
            print(f"\nAktuell: {'Aktiviert' if current else 'Deaktiviert'}")
            print("Bei Aktivierung werden fehlgeschlagene Downloads automatisch wiederholt.")
            new_val = input("Retry aktivieren? [y/N]: ").strip().lower()
            if new_val in ['y', 'j', 'ja']:
                update_config("retry_on_failure", True)
            elif new_val in ['n', 'nein', 'no']:
                update_config("retry_on_failure", False)

        elif sel == '8':
            print(f"\nAktuell: {CONFIG.get('retry_wait_minutes', 30)} Minuten")
            print("Wie lange soll zwischen Retry-Versuchen gewartet werden?")
            new_val = input("Wartezeit in Minuten: ").strip()
            if new_val.isdigit():
                update_config("retry_wait_minutes", int(new_val))

        elif sel == '9':
            print(f"\nAktuell: {CONFIG.get('max_retries', 3)}")
            print("Wie oft soll ein fehlgeschlagener Download wiederholt werden?")
            new_val = input("Maximale Anzahl Retries: ").strip()
            if new_val.isdigit():
                update_config("max_retries", int(new_val))

        elif sel == '10':
            current = CONFIG.get('batch_auto_retry', True)
            print(f"\nAktuell: {'Aktiviert' if current else 'Deaktiviert'}")
            print("Bei Aktivierung werden Batch-Kuratierungen automatisch wiederholt,")
            print("wenn noch nicht-kuratierte Jobs übrig bleiben und Fortschritt gemacht wurde.")
            new_val = input("Batch Auto-Retry aktivieren? [Y/n]: ").strip().lower()
            if new_val in ['y', 'j', 'ja', 'yes', '']:
                update_config("batch_auto_retry", True)
            elif new_val in ['n', 'nein', 'no']:
                update_config("batch_auto_retry", False)

        elif sel == '11':
            print(f"\nAktuell: {CONFIG.get('max_batch_retries', 10)}")
            print("Maximale Anzahl automatischer Wiederholungen bei Batch-Kuratierung.")
            print("Verhindert Endlosschleifen bei wiederkehrenden Fehlern.")
            new_val = input("Maximale Anzahl Batch-Retries: ").strip()
            if new_val.isdigit() and int(new_val) > 0:
                update_config("max_batch_retries", int(new_val))

        elif sel == '12':
            current_targets = CONFIG.get('video_targets', [])
            print(f"\nAktuelle Kategorien: {', '.join(current_targets)}")
            print("Kategorien mit Komma getrennt eingeben (z.B. Patrick, Kinder, Eltern).")
            print("Leer lassen um nichts zu ändern.")
            new_val = input("Neue Kategorien: ").strip()
            if new_val:
                # Split by comma and strip whitespace
                new_targets = [t.strip() for t in new_val.split(",") if t.strip()]
                if new_targets:
                    update_config("video_targets", new_targets)
                    print(f"✅ Kategorien aktualisiert: {new_targets}")
                else:
                    print("⚠️  Ungültige Eingabe (Liste leer).")

        elif sel == '13':
            print(f"\nAktueller Musik-Ordner: {CONFIG.get('music_target')}")
            new_val = input("Neuer Name für Musikvideos-Ordner: ").strip()
            if new_val:
                update_config("music_target", new_val)
        
        elif sel == '14':
            print(f"\nAktuelle Einstellung: {'Aktiviert' if CONFIG.get('classification_enabled', False) else 'Deaktiviert'}")
            print("Automatische Klassifikation schlägt Zielverzeichnisse basierend auf")
            print("gelernten Regeln vor (Genre, Keywords, Künstler).")
            new_val = input("Auto-Klassifikation aktivieren? [Y/n]: ").strip().lower()
            if new_val in ['y', 'j', 'ja', 'yes', '']:
                update_config("classification_enabled", True)
            elif new_val in ['n', 'nein', 'no']:
                update_config("classification_enabled", False)
        
        elif sel == '15':
            print(f"\nAktuelle Schwelle: {CONFIG.get('classification_rules_threshold', 10)} Videos")
            print("Nach wie vielen neu integrierten Videos sollen die Klassifikationsregeln")
            print("automatisch neu gelernt werden?")
            new_val = input("Neue Schwelle (Anzahl Videos): ").strip()
            if new_val.isdigit() and int(new_val) > 0:
                update_config("classification_rules_threshold", int(new_val))
        
        elif sel == '16':
            # Auto-Download Playlists Management
            while True:
                playlists = CONFIG.get('auto_download_playlists', [])
                print("\n--- 📋 AUTO-DOWNLOAD PLAYLISTS ---")
                
                if playlists:
                    for i, pl in enumerate(playlists, 1):
                        url_short = pl['url'][:60] + "..." if len(pl['url']) > 60 else pl['url']
                        print(f"{i}. {pl['category']:15s} | {url_short}")
                else:
                    print("(Keine Playlists konfiguriert)")
                
                print("\na. Playlist hinzufügen")
                print("r. Playlist entfernen")
                print("z. Zurück")
                
                sub_sel = input("\nAuswahl: ").strip().lower()
                
                if sub_sel == 'z':
                    break
                elif sub_sel == 'a':
                    print("\n--- NEUE PLAYLIST HINZUFÜGEN ---")
                    url = input("Playlist URL: ").strip()
                    if not url:
                        print("⚠️  URL darf nicht leer sein.")
                        continue
                    
                    # Get available targets using the config module's function
                    targets = get_available_targets()
                    print("\nVerfügbare Kategorien:")
                    for i, t in enumerate(targets, 1):
                        print(f"  {i}. {t}")
                    
                    # Determine default category based on whether 'Auto' is available
                    default_category = "Auto" if "Auto" in targets else (targets[0] if targets else "Allgemein")
                    
                    category_input = input(f"\nKategorie wählen (1-{len(targets)}) oder Namen eingeben [Default: {default_category}]: ").strip()
                    
                    if category_input.isdigit():
                        idx = int(category_input) - 1
                        if 0 <= idx < len(targets):
                            category = targets[idx]
                        else:
                            category = default_category
                    elif category_input:
                        category = category_input
                    else:
                        category = default_category
                    
                    # Add to playlists
                    playlists.append({"url": url, "category": category})
                    update_config("auto_download_playlists", playlists)
                    print(f"✅ Playlist hinzugefügt: {category}")
                    
                elif sub_sel == 'r':
                    if not playlists:
                        print("⚠️  Keine Playlists zum Entfernen.")
                        continue
                    
                    remove_input = input(f"Nummer der zu entfernenden Playlist (1-{len(playlists)}): ").strip()
                    if remove_input.isdigit():
                        idx = int(remove_input) - 1
                        if 0 <= idx < len(playlists):
                            removed = playlists.pop(idx)
                            update_config("auto_download_playlists", playlists)
                            print(f"✅ Playlist entfernt: {removed['category']}")
                        else:
                            print("❌ Ungültige Nummer")
                    else:
                        print("❌ Ungültige Eingabe")

        elif sel == '17':
            current = CONFIG.get('chapters_enabled', True)
            print(f"\nAktuell: {'Aktiviert' if current else 'Deaktiviert'}")
            print("Bei Deaktivierung werden keine Kapitel generiert, was die API-Kosten reduziert.")
            print("Kapitel helfen aber beim Navigieren in langen Videos.")
            new_val = input("Kapitel-Erstellung aktivieren? [Y/n]: ").strip().lower()
            if new_val in ['y', 'j', 'ja', 'yes', '']:
                update_config("chapters_enabled", True)
            elif new_val in ['n', 'nein', 'no']:
                update_config("chapters_enabled", False)

def import_menu():
    while True:
        print("\n--- 📦 IMPORT ---")
        print("1. 🔙 Restore aus bestehendem Archiv (DMG)")
        print("2. 🎵 MP3 aus DMG extrahieren")
        print("z. Zurück")
        
        sel = input("\nAuswahl: ").strip().lower()
        
        if sel == 'z': 
            break
        
        elif sel == '1':
            print("\n--- RESTORE (Kopie erstellen) ---")
            print("Mehrere Video-IDs oder Titel nacheinander eingeben.")
            print("  • Video-ID: z.B. '5fk8XXCw' oder 'youtube_5fk8XXCw'")
            print("  • Titel: z.B. 'Hydroelectric Turbine Manufacturing'")
            print("Ähnlich wie bei der Download-Queue.")
            
            # Queue-based ID/title collection
            video_inputs = []
            while True:
                video_input = input("\nVideo-ID oder Titel eingeben (oder [ENTER] zum Starten): ").strip()
                if not video_input:
                    break
                video_inputs.append(video_input)
                print(f"   ✅ Zur Queue hinzugefügt. (Aktuell: {len(video_inputs)} Videos)")
                print("   [ENTER] startet Restore, oder nächste Eingabe...")
            
            if not video_inputs:
                print("⚠️  Keine IDs oder Titel eingegeben. Abbruch.")
                continue
            
            # Execute restore for all inputs (IDs or titles)
            restore_result = restore_multiple_archives(video_inputs)
            
            # Display summary
            print(f"\n{'='*60}")
            print(f"📊 ZUSAMMENFASSUNG")
            print(f"{'='*60}")
            
            successful = restore_result.get("successful", [])
            not_found = restore_result.get("not_found", [])
            failed = restore_result.get("failed", [])
            skipped = restore_result.get("skipped_duplicates", [])
            
            if successful:
                print(f"\n✅ Erfolgreich importiert ({len(successful)}):")
                for item in successful:
                    print(f"   • {item['id']} → {os.path.basename(item['job_path'])}")
            
            if skipped:
                print(f"\n⏭️  Übersprungen - bereits im Archiv ({len(skipped)}):")
                for video_id in skipped:
                    print(f"   • {video_id}")
            
            if not_found:
                print(f"\n❌ Nicht im Archiv gefunden ({len(not_found)}):")
                for video_id in not_found:
                    print(f"   • {video_id}")
                
                # Offer to redownload missing archives
                print(f"\n💡 Möchtest du die fehlenden Archive neu herunterladen?")
                print(f"   Die Original-URLs werden aus dem Metadaten-Index geladen.")
                redownload_choice = input(f"   Fehlende Archive zur Download-Queue hinzufügen? [y/N]: ").strip().lower()
                
                if redownload_choice in ['y', 'j', 'ja', 'yes']:
                    # Look up URLs from CSV
                    print(f"\n🔍 Suche URLs im Metadaten-Index...")
                    url_map = _lookup_urls_from_csv(not_found)
                    
                    # Extract URLs from DMG archives
                    videos_with_urls = []
                    videos_without_urls = []
                    
                    for video_id in not_found:
                        if video_id in url_map:
                            dmg_filepath = url_map[video_id].get('dmg_filepath')
                            title = url_map[video_id].get('title', video_id)
                            
                            if dmg_filepath:
                                print(f"   📦 Extrahiere URL für {video_id}...")
                                url = _extract_url_from_dmg(dmg_filepath)
                                
                                if url:
                                    videos_with_urls.append({
                                        'id': video_id,
                                        'url': url,
                                        'title': title
                                    })
                                    print(f"      ✅ URL gefunden: {url[:60]}...")
                                else:
                                    videos_without_urls.append(video_id)
                                    print(f"      ⚠️  Keine URL in DMG gefunden")
                            else:
                                videos_without_urls.append(video_id)
                                print(f"   ⚠️  Kein DMG-Pfad für {video_id} im Index")
                        else:
                            videos_without_urls.append(video_id)
                            print(f"   ⚠️  {video_id} nicht im Metadaten-Index gefunden")
                    
                    # Display results and confirm download
                    if videos_with_urls:
                        print(f"\n📋 {len(videos_with_urls)} URL(s) gefunden:")
                        for item in videos_with_urls:
                            print(f"   • {item['id']}: {item['title'][:50]}")
                            print(f"     {item['url'][:70]}...")
                        
                        # Reuse target from restore operation
                        target = restore_result.get('target')
                        if not target:
                            # Fallback: ask user to select target if not available
                            print(f"\n--- 🎯 ZIEL FÜR DOWNLOADS WÄHLEN ---")
                            targets = _get_targets()
                            for i, t in enumerate(targets, 1):
                                print(f"{i}. {t}")
                            target_sel = input(f"Wähle Ziel (1-{len(targets)}) [Default: {targets[0]}]: ").strip()
                            
                            if target_sel.isdigit():
                                idx = int(target_sel) - 1
                                if 0 <= idx < len(targets):
                                    target = targets[idx]
                                else:
                                    target = targets[0]
                            else:
                                target = targets[0]
                        
                        confirm = input(f"\n🚀 {len(videos_with_urls)} Video(s) nach '{target}' herunterladen? [Y/n]: ").strip().lower()
                        
                        if confirm in ['', 'y', 'j', 'ja', 'yes']:
                            print(f"\n⬇️  Starte Downloads...")
                            successful_downloads = 0
                            failed_downloads = 0
                            
                            for idx, item in enumerate(videos_with_urls, 1):
                                print(f"\n[{idx}/{len(videos_with_urls)}] {item['url']} -> {target}")
                                result = fetch_video(item['url'], target)
                                
                                if result is None:
                                    failed_downloads += 1
                                    print(f"❌ Download fehlgeschlagen: {item['id']}")
                                else:
                                    successful_downloads += 1
                                    print(f"✅ Download erfolgreich: {item['id']}")
                                
                                # Pause zwischen Downloads
                                if idx < len(videos_with_urls):
                                    pause_sec = CONFIG.get("wait_time_download_queue_sec", 5)
                                    print(f"   ⏳ Warte {pause_sec} Sekunden...")
                                    time.sleep(pause_sec)
                            
                            print(f"\n📊 Download-Zusammenfassung:")
                            print(f"   ✅ Erfolgreich: {successful_downloads}/{len(videos_with_urls)}")
                            if failed_downloads > 0:
                                print(f"   ❌ Fehlgeschlagen: {failed_downloads}/{len(videos_with_urls)}")
                        else:
                            print(f"   Download abgebrochen.")
                    
                    if videos_without_urls:
                        print(f"\n⚠️  Keine URLs gefunden für {len(videos_without_urls)} Video(s):")
                        for video_id in videos_without_urls:
                            print(f"   • {video_id}")
                        print(f"   💡 Diese Videos müssen manuell heruntergeladen werden.")
            
            if failed:
                print(f"\n⚠️  Import fehlgeschlagen ({len(failed)}):")
                for video_id in failed:
                    print(f"   • {video_id}")
            
            total_issues = len(not_found) + len(failed)
            total_processed = len(successful) + len(skipped)
            if total_processed == len(video_inputs) and not total_issues:
                print(f"\n🎉 Alle {len(video_inputs)} Anfragen erfolgreich verarbeitet!")
                if skipped:
                    print(f"   ({len(successful)} importiert, {len(skipped)} bereits vorhanden)")
            elif successful or skipped:
                print(f"\n⚠️  {total_processed} von {len(video_inputs)} verarbeitet, {total_issues} mit Problemen.")
            else:
                print(f"\n❌ Keine Archive erfolgreich importiert.")
            
            input("\n[ENTER] zum Fortfahren...")
            continue
            
        elif sel == '2':
            print("\n--- MP3 EXTRAHIEREN (DMG) ---")
            print("Mehrere Video-IDs oder Titel nacheinander eingeben.")
            print("  • Video-ID: z.B. '5fk8XXCw' oder 'youtube_5fk8XXCw'")
            print("  • Titel: z.B. 'Hydroelectric Turbine Manufacturing'")
            print("Ähnlich wie bei der Download-Queue.")
            
            video_inputs = []
            while True:
                video_input = input("\nVideo-ID oder Titel eingeben (oder [ENTER] zum Starten): ").strip()
                if not video_input:
                    break
                video_inputs.append(video_input)
                print(f"   ✅ Zur Queue hinzugefügt. (Aktuell: {len(video_inputs)} Videos)")
                print("   [ENTER] startet Extraktion, oder nächste Eingabe...")
            
            if not video_inputs:
                print("⚠️  Keine IDs oder Titel eingegeben. Abbruch.")
                continue
            
            extract_result = extract_mp3_from_dmg_archives(video_inputs)
            
            print(f"\n{'='*60}")
            print(f"📊 ZUSAMMENFASSUNG")
            print(f"{'='*60}")
            
            successful = extract_result.get("successful", [])
            not_found = extract_result.get("not_found", [])
            failed = extract_result.get("failed", [])
            
            if successful:
                print(f"\n✅ Erfolgreich extrahiert ({len(successful)}):")
                for item in successful:
                    print(f"   • {item['id']} → {os.path.basename(item['file'])}")
            
            if not_found:
                print(f"\n❌ Nicht im Archiv gefunden ({len(not_found)}):")
                for video_id in not_found:
                    print(f"   • {video_id}")
            
            if failed:
                print(f"\n⚠️  Extraktion fehlgeschlagen ({len(failed)}):")
                for video_id in failed:
                    print(f"   • {video_id}")
            
            input("\n[ENTER] zum Fortfahren...")
            continue

def analysis_menu():
    """Menü für Analyse und Bestandspflege."""
    while True:
        print("\n--- 📊 ANALYSE UND BESTANDSPFLEGE ---")
        print("1. 🔍 Video analysieren (M4V/MP4)")
        print("2. 📊 Metadata-Indexierung (Server)")
        print("3. 🔎 Video-Suche (Metadaten)")
        print("4. 🧠 Klassifikationsregeln lernen")
        print("5. 📂 Kategorie ändern")
        print("6. 🗑️  Video löschen")
        print("7. 🖼️  Poster neu generieren")
        print("z. Zurück")
        
        sel = input("\nAuswahl: ").strip().lower()
        
        if sel == 'z':
            break
        
        elif sel == '1':
            print("\n--- VIDEO ANALYSE ---")
            print("Wähle ein Video zum Analysieren:")
            print()
            
            # Option 1: Aus vorhandenen Jobs
            print("1. Aus vorhandenem Job")
            print("2. Manueller Pfad")
            print("z. Zurück")
            
            choice = input("\nAuswahl (oder 'z' für Zurück): ").strip().lower()
            
            if choice == 'z':
                continue
            
            video_path = None
            
            if choice == '1':
                # List jobs and let user select
                jobs = list_jobs()
                if not jobs:
                    print("⚠️ Keine Jobs vorhanden.")
                    continue
                
                print("\nVerfügbare Jobs:")
                for i, (jid, title, path, is_curated) in enumerate(jobs, 1):
                    mark = "✅" if is_curated else "⭕️"
                    print(f"{i}. {mark} {title}")
                print("z. Zurück")
                
                job_sel = input("\nJob wählen (oder 'z' für Zurück): ").strip().lower()
                
                if job_sel == 'z':
                    continue
                
                if job_sel.isdigit() and 1 <= int(job_sel) <= len(jobs):
                    job_path = jobs[int(job_sel)-1][2]
                    
                    # Find video file in job directory
                    # Look for dist/video.m4v first, then original
                    dist_video = os.path.join(job_path, "dist", "video.m4v")
                    if os.path.exists(dist_video):
                        video_path = dist_video
                    else:
                        # Find original video
                        for f in os.listdir(job_path):
                            if f.startswith("original.") and f.endswith((".mkv", ".mp4", ".webm", ".m4v", ".mov")):
                                video_path = os.path.join(job_path, f)
                                break
                    
                    if not video_path:
                        print("⚠️  Kein Video in diesem Job gefunden.")
                        continue
                else:
                    print("⚠️  Ungültige Auswahl.")
                    continue
            
            elif choice == '2':
                path_input = input("\nPfad zur Video-Datei: ").strip().strip('"').strip("'")
                if path_input:
                    video_path = os.path.expanduser(path_input)
                else:
                    continue
            
            else:
                print("⚠️  Ungültige Auswahl.")
                continue
            
            # Analyze the video
            if video_path and os.path.exists(video_path):
                try:
                    analyze_video_with_raw_output(video_path)
                    input("\n✅ Analyse abgeschlossen. [ENTER] zum Fortfahren...")
                except Exception as e:
                    print(f"\n❌ Fehler bei Analyse: {e}")
                    input("[ENTER] zum Fortfahren...")
            else:
                print(f"⚠️  Video nicht gefunden: {video_path}")
                input("[ENTER] zum Fortfahren...")
        
        elif sel == '2':
            print("\n--- METADATA-INDEXIERUNG ---")
            print("Erstellt eine CSV-Datei mit allen Metadaten der Videos auf dem Server.")
            print()
            confirm = input("Indexierung starten? [Y/n]: ").strip().lower()
            
            if confirm in ['', 'y', 'j', 'ja', 'yes']:
                try:
                    csv_path = index_server_videos()
                    if csv_path:
                        print(f"\n✅ Indexierung erfolgreich abgeschlossen!")
                        print(f"📄 CSV-Datei: {csv_path}")
                    else:
                        print("\n⚠️  Indexierung fehlgeschlagen oder keine Videos gefunden.")
                except Exception as e:
                    print(f"\n❌ Fehler bei Indexierung: {e}")
                    traceback.print_exc()
                
                input("\n[ENTER] zum Fortfahren...")
            else:
                print("   Abgebrochen.")
        
        elif sel == '3':
            # Video search menu
            try:
                interactive_search_menu()
            except Exception as e:
                print(f"\n❌ Fehler bei Suche: {e}")
                if CONFIG.get('debug_mode', False):
                    traceback.print_exc()
                input("\n[ENTER] zum Fortfahren...")
        
        elif sel == '4':
            print("\n--- 🧠 KLASSIFIKATIONSREGELN LERNEN ---")
            print("Analysiert die video_metadata_index.csv und generiert Regeln zur")
            print("automatischen Zuweisung von Zielverzeichnissen basierend auf:")
            print("  • Genre-Mustern")
            print("  • Keyword-Mustern")
            print("  • Künstler/Kanal-Mustern")
            print()
            print("Die Regeln werden in classification_rules.json gespeichert und")
            print("können bei der Integration neuer Videos verwendet werden.")
            print()
            
            confirm = input("Regeln lernen? [Y/n]: ").strip().lower()
            
            if confirm in ['', 'y', 'j', 'ja', 'yes']:
                try:
                    success = learn_classification_rules()
                    if success:
                        print("\n✅ Regeln erfolgreich gelernt!")
                    else:
                        print("\n⚠️  Fehler beim Lernen der Regeln.")
                except Exception as e:
                    print(f"\n❌ Fehler: {e}")
                    if CONFIG.get('debug_mode', False):
                        traceback.print_exc()
                
                input("\n[ENTER] zum Fortfahren...")
            else:
                print("   Abgebrochen.")
        
        elif sel == '5':
            # Category change menu
            try:
                interactive_category_change()
            except Exception as e:
                print(f"\n❌ Fehler bei Kategorie-Änderung: {e}")
                if CONFIG.get('debug_mode', False):
                    traceback.print_exc()
                input("\n[ENTER] zum Fortfahren...")
        
        elif sel == '6':
            # Video deletion menu
            try:
                interactive_video_deletion()
            except Exception as e:
                print(f"\n❌ Fehler bei Video-Löschung: {e}")
                if CONFIG.get('debug_mode', False):
                    traceback.print_exc()
                input("\n[ENTER] zum Fortfahren...")

        elif sel == '7':
            # Poster rebuild menu
            print("\n--- 🖼️  POSTER NEU GENERIEREN ---")
            print("Pfad zur M4V-Datei oder Verzeichnis (lokal oder rclone Remote).")
            print("Beispiele:")
            print("  /pfad/zur/video.m4v")
            print("  lys-nas:/Internetfilme/Patrick/SpaceX/video.m4v")
            print("  /pfad/zum/verzeichnis  (mit Batch-Modus)")
            print()
            path_input = input("Pfad: ").strip().strip('"').strip("'")
            if not path_input:
                print("   Abgebrochen.")
                continue

            batch_mode = input("Batch-Modus (ganzes Verzeichnis)? [y/N]: ").strip().lower() in ('y', 'j', 'ja', 'yes')
            new_frame = input("Neuen Frame von K.I. wählen lassen? [y/N]: ").strip().lower() in ('y', 'j', 'ja', 'yes')
            fanart_input = input("Fanart erzwingen? [y/N/config]: ").strip().lower()
            if fanart_input in ('y', 'j', 'ja', 'yes'):
                fanart = True
            elif fanart_input in ('n', 'no', 'nein'):
                fanart = False
            else:
                fanart = None  # use config default

            try:
                from integrator.module_poster_rebuild import rebuild_poster, rebuild_posters_batch
                if batch_mode:
                    rebuild_posters_batch(path_input, new_frame=new_frame, fanart=fanart)
                else:
                    rebuild_poster(path_input, new_frame=new_frame, fanart=fanart)
            except Exception as e:
                print(f"\n❌ Fehler beim Poster-Rebuild: {e}")
                if CONFIG.get('debug_mode', False):
                    traceback.print_exc()
            input("\n[ENTER] zum Fortfahren...")

def batch_auto_curate_all(deploy_after_curate=False):
    """Auto-curate all non-curated jobs with summary report.
    
    Args:
        deploy_after_curate: If True, deploy each job immediately after successful curation
    """
    jobs = list_jobs()
    if not jobs:
        print("⚠️ Keine Jobs vorhanden.")
        return
    
    # Filter non-curated jobs
    non_curated = [(jid, title, path, is_curated) for jid, title, path, is_curated in jobs if not is_curated]
    
    if not non_curated:
        print("✅ Alle Jobs sind bereits kuratiert!")
        return
    
    mode_label = "BATCH AUTO-KURATIERUNG + DEPLOYMENT" if deploy_after_curate else "BATCH AUTO-KURATIERUNG"
    print(f"\n--- 🤖 {mode_label} ---")
    print(f"Gefunden: {len(non_curated)} nicht-kuratierte Jobs")
    
    # Check if auto-retry is enabled and inform user
    auto_retry_enabled = CONFIG.get('batch_auto_retry', True)
    if auto_retry_enabled:
        print(f"🔁 Auto-Retry: Aktiviert (Max. {CONFIG.get('max_batch_retries', 10)} Durchläufe)")
    
    print()
    
    for i, (jid, title, path, is_curated) in enumerate(non_curated, 1):
        print(f"{i}. {title}")
    
    if deploy_after_curate:
        print(f"\nStarte Auto-Kuratierung + Deployment für {len(non_curated)} Jobs?")
        print("ℹ️  Jedes Video wird nach erfolgreicher Kuratierung sofort deployed.")
    else:
        print(f"\nStarte Auto-Kuratierung für {len(non_curated)} Jobs?")
    if auto_retry_enabled:
        print("ℹ️  Bei verbleibenden Fehlern wird automatisch wiederholt, solange Fortschritt gemacht wird.")
    confirm = input("[Y/n]: ").strip().lower()
    
    if confirm not in ['', 'y', 'j', 'ja', 'yes']:
        print("   Abgebrochen.")
        return
    
    # Track the initial set of uncurated job paths for retry logic
    initial_uncurated_paths = set(path for jid, title, path, is_curated in non_curated)
    
    # Retry loop: continue as long as progress is made (if enabled)
    run_number = 1
    max_runs = CONFIG.get('max_batch_retries', 10) if auto_retry_enabled else 1  # Single run if disabled
    previous_remaining_count = len(initial_uncurated_paths)
    
    # Accumulate totals across all runs
    total_successful = []
    total_deployed = []
    total_deploy_failed = []
    
    while run_number <= max_runs:
        # Get current state of initially uncurated jobs
        current_jobs = list_jobs()
        remaining_uncurated = []
        
        for jid, title, path, is_curated in current_jobs:
            if path in initial_uncurated_paths and not is_curated:
                remaining_uncurated.append((jid, title, path, is_curated))
        
        if not remaining_uncurated:
            # All jobs from initial set are now curated
            print(f"\n🎉 Alle {len(initial_uncurated_paths)} Jobs erfolgreich kuratiert!")
            break
        
        if run_number > 1:
            # Check if we made progress
            if len(remaining_uncurated) >= previous_remaining_count:
                # No progress made, stop retrying
                print(f"\n⚠️  Keine weiteren Fortschritte möglich.")
                print(f"   Verbleibende nicht-kuratierte Jobs: {len(remaining_uncurated)}")
                break
            else:
                # Progress made, show status
                print(f"\n🔄 WIEDERHOLUNG {run_number}/{max_runs}")
                print(f"   Verbleibende nicht-kuratierte Jobs: {len(remaining_uncurated)}/{len(initial_uncurated_paths)}")
                print(f"   Fortschritt: {previous_remaining_count - len(remaining_uncurated)} weitere Jobs kuratiert")
        
        # Process each remaining uncurated job
        successful = []
        failed = []
        deployed = []
        deploy_failed = []
        
        print(f"\n{'='*50}")
        for i, (jid, title, path, is_curated) in enumerate(remaining_uncurated, 1):
            print(f"\n[{i}/{len(remaining_uncurated)}] Verarbeite: {title[:60]}")
            print(f"{'='*50}")
            
            try:
                interactive_curation(path, auto_mode=True)
                successful.append((jid, title, path))
                print(f"✅ Kuratierung erfolgreich: {title[:60]}")
                
                # Deploy immediately after curation if requested
                if deploy_after_curate:
                    try:
                        print(f"   🚀 Starte Deployment...")
                        build_master(path)
                        build_dist(path)
                        deploy(path)
                        deployed.append((jid, title, path))
                        print(f"   ✅ Deployment erfolgreich: {title[:60]}")
                    except Exception as deploy_error:
                        deploy_failed.append((jid, title, path, str(deploy_error)))
                        print(f"   ❌ Deployment fehlgeschlagen: {title[:60]}")
                        print(f"      Fehler: {deploy_error}")
                        if CONFIG.get('debug_mode', False):
                            traceback.print_exc()
                        
            except Exception as e:
                failed.append((jid, title, path, str(e)))
                print(f"❌ Kuratierung fehlgeschlagen: {title[:60]}")
                print(f"   Fehler: {e}")
                if CONFIG.get('debug_mode', False):
                    traceback.print_exc()
                print(f"   → Fahre mit nächstem Job fort...")
        
        # Add to totals
        total_successful.extend(successful)
        total_deployed.extend(deployed)
        total_deploy_failed.extend(deploy_failed)
        
        # Summary for this run
        print(f"\n{'='*50}")
        print(f"📊 ZUSAMMENFASSUNG (Durchlauf {run_number})")
        print(f"{'='*50}")
        print(f"✅ Erfolgreich kuratiert:     {len(successful)}/{len(remaining_uncurated)}")
        if failed:
            print(f"❌ Kuratierung fehlgeschlagen: {len(failed)}/{len(remaining_uncurated)}")
        
        if deploy_after_curate:
            print(f"🚀 Erfolgreich deployed:      {len(deployed)}/{len(successful)}")
            if deploy_failed:
                print(f"❌ Deployment fehlgeschlagen:  {len(deploy_failed)}/{len(successful)}")
        
        # Update tracking for next iteration
        # Store the count at START of this iteration (before processing) for comparison in next iteration
        # This allows us to detect if progress was made: if next iteration's count < this iteration's count
        # Example: Iter1 has 5 jobs before processing, Iter2 has 3 jobs before processing → Progress!
        previous_remaining_count = len(remaining_uncurated)
        run_number += 1
        
        # Loop continues to next iteration automatically
        # Will exit at start of next iteration if:
        # - No more remaining uncurated jobs
        # - No progress made
        # - Max runs reached
    
    # Final summary across all runs
    print(f"\n{'='*50}")
    print(f"📊 GESAMT-ZUSAMMENFASSUNG")
    print(f"{'='*50}")
    print(f"🔢 Durchläufe insgesamt:      {run_number - 1}")
    print(f"✅ Erfolgreich kuratiert:     {len(total_successful)}/{len(initial_uncurated_paths)}")
    
    # Calculate remaining count for final report
    remaining_count = len(initial_uncurated_paths) - len(total_successful)
    if remaining_count > 0:
        print(f"❌ Kuratierung fehlgeschlagen: {remaining_count}/{len(initial_uncurated_paths)}")
    
    if deploy_after_curate:
        print(f"🚀 Erfolgreich deployed:      {len(total_deployed)}/{len(total_successful)}")
        if total_deploy_failed:
            print(f"❌ Deployment fehlgeschlagen:  {len(total_deploy_failed)}/{len(total_successful)}")
    
    if total_successful:
        print(f"\n✅ Erfolgreich kuratiert:")
        for jid, title, path in total_successful:
            print(f"   • {title[:60]}")
    
    if total_deployed:
        print(f"\n🚀 Erfolgreich deployed:")
        for jid, title, path in total_deployed:
            print(f"   • {title[:60]}")
    
    # Show remaining failures
    if remaining_count > 0:
        # Get final state to show which jobs failed
        final_jobs = list_jobs()
        still_uncurated = []
        for jid, title, path, is_curated in final_jobs:
            if path in initial_uncurated_paths and not is_curated:
                still_uncurated.append((jid, title, path))
        
        print(f"\n❌ Kuratierung fehlgeschlagen:")
        for jid, title, path in still_uncurated:
            print(f"   • {title[:60]}")
        print(f"\n💡 Empfehlung: Du kannst die fehlgeschlagenen Jobs")
        print(f"   - erneut mit Auto-Kuratierung versuchen, oder")
        print(f"   - manuell einzeln kuratieren.")
    
    if total_deploy_failed:
        print(f"\n❌ Deployment fehlgeschlagen (aber kuratiert):")
        for jid, title, path, error in total_deploy_failed:
            print(f"   • {title[:60]}")
            print(f"     Fehler: {error[:100]}")
        print(f"\n💡 Diese Jobs sind kuratiert und können manuell deployed werden.")
    
    input(f"\n[ENTER] zum Fortfahren...")

def interactive_menu():
    show_runtime_details = True
    while True:
        print("\n========================================")
        print(f"🎬 INTERNETFILM MANAGER - v{get_version()}")
        if show_runtime_details:
            for line in _build_startup_banner_lines():
                print(line)
            show_runtime_details = False
        print("========================================")
        print("1. ⬇️  Neues Video herunterladen (Queue)")
        print("2. 📦 Importieren (Archiv)")
        print("3. 🎨 Vorhandene Jobs kuratieren")
        print("4. 🚀 Jobs deployen")
        print("5. 📊 Analyse und Bestandspflege")
        print("6. ⚙️  Einstellungen")
        print("q. Beenden")
        
        choice = input("\nWähle eine Option: ").strip().lower()
        
        if choice == 'q': break
            
        elif choice == '1':
            # --- QUEUE LOGIK ---
            queue = []
            print("\n--- ⬇️  DOWNLOAD QUEUE ---")
            
            # Check if auto-download playlists are configured
            auto_playlists = CONFIG.get('auto_download_playlists', [])
            if auto_playlists:
                print(f"\n📋 {len(auto_playlists)} Auto-Download Playlist(s) konfiguriert:")
                for i, pl in enumerate(auto_playlists, 1):
                    print(f"   {i}. {pl['category']}")
                
                use_auto = input("\n   Auto-Download Playlists zur Queue hinzufügen? [y/N]: ").strip().lower()
                if use_auto in ['y', 'j', 'ja', 'yes']:
                    for pl in auto_playlists:
                        queue.append({"url": pl['url'], "target": pl['category']})
                    print(f"   ✅ {len(auto_playlists)} Playlist(s) zur Queue hinzugefügt.")
            
            while True:
                url_input = input("\nURL eingeben (oder [ENTER] zum Starten): ").strip()
                if not url_input: break
                
                # Normalize input: Convert IDs to full URLs
                url = normalize_video_input(url_input)
                
                # Notify user if ID was converted to URL
                if url != url_input:
                    print(f"   ℹ️  ID erkannt: {url}")
                
                target = _select_target_interactive()
                if target is None:
                    print("   ⚠️  Abgebrochen. URL verworfen.")
                    continue
                queue.append({"url": url, "target": target})
                print(f"   ✅ Zur Queue hinzugefügt. (Aktuell: {len(queue)} Videos)")
                print("   [ENTER] startet Download, oder nächste URL eingeben...")

            if queue:
                # Ask user if they want auto-curation and auto-deployment
                print(f"\n📋 {len(queue)} Video(s) in der Queue.")
                print("\n   Download-Optionen:")
                print("   [d] Nur Download (Standard)")
                print("   [c] Download + Auto-Kuratierung")
                print("   [cd] Download + Auto-Kuratierung + Auto-Deployment")
                
                mode_sel = input("\n   Option wählen [d/c/cd]: ").strip().lower()
                
                auto_curate = mode_sel in ['c', 'cd']
                auto_deploy = mode_sel == 'cd'
                
                if auto_curate:
                    if auto_deploy:
                        print(f"\n🚀 Starte Download + Auto-Kuratierung + Auto-Deployment für {len(queue)} Videos...")
                    else:
                        print(f"\n🚀 Starte Download + Auto-Kuratierung für {len(queue)} Videos...")
                else:
                    print(f"\n🚀 Starte Download von {len(queue)} Videos...")
                
                # Modulvariable für Pause (kann später in Config)
                DOWNLOAD_PAUSE_SEC = CONFIG.get("wait_time_download_queue_sec", 5)
                
                successful_downloads = 0
                failed_downloads = 0
                total_expected_downloads = 0  # Track actual number of videos to download
                successful_curations = 0
                failed_curations = 0
                successful_deployments = 0
                failed_deployments = 0
                downloaded_jobs = []
                
                for i, item in enumerate(queue, 1):
                    print(f"\n[{i}/{len(queue)}] {item['url']} -> {item['target']}")
                    result = fetch_video(item['url'], item['target'])
                    
                    # Check if download was successful
                    if result is None:
                        # Download failed - count this single failed download
                        failed_downloads += 1
                        total_expected_downloads += 1
                        print(f"\n❌ Download fehlgeschlagen. Queue wird gestoppt.")
                        print(f"   Erfolgreich: {successful_downloads}, Fehlgeschlagen: {failed_downloads}")
                        print(f"   Verbleibende Downloads in Queue: {len(queue) - i}")
                        break
                    elif isinstance(result, dict) and result.get("type") == "playlist_completed":
                        # Playlist was processed - now handle curation/deployment for each job
                        playlist_jobs = result.get("job_paths", [])
                        # Defensive validation: ensure only valid job paths (existing directories) are processed
                        # While phase_fetch.py already validates, this guards against any unexpected issues
                        playlist_jobs = [job for job in playlist_jobs if isinstance(job, str) and job and os.path.isdir(job)]
                        
                        # Update counters with actual playlist results
                        # The result structure is defined in phase_fetch.py (lines 181-186)
                        # and contains: type, job_paths, successful, and failed keys
                        playlist_successful = result.get("successful", 0)
                        playlist_failed = result.get("failed", 0)
                        successful_downloads += playlist_successful
                        failed_downloads += playlist_failed
                        total_expected_downloads += playlist_successful + playlist_failed
                        
                        # Add all playlist jobs to downloaded_jobs list
                        downloaded_jobs.extend(playlist_jobs)
                        
                        # Auto-curate and deploy each job if requested
                        if auto_curate and playlist_jobs:
                            print(f"\n   🎨 Starte Auto-Kuratierung für {len(playlist_jobs)} Playlist-Videos...")
                            for job_idx, job_path in enumerate(playlist_jobs, 1):
                                print(f"\n   [{job_idx}/{len(playlist_jobs)}] Kuratiere: {os.path.basename(job_path)}")
                                try:
                                    interactive_curation(job_path, auto_mode=True)
                                    successful_curations += 1
                                    print(f"   ✅ Kuratierung erfolgreich")
                                    
                                    # Auto-deploy if requested
                                    if auto_deploy:
                                        print(f"\n   🚀 Starte Auto-Deployment...")
                                        try:
                                            build_master(job_path)
                                            build_dist(job_path)
                                            deploy(job_path)
                                            successful_deployments += 1
                                            print(f"   ✅ Deployment erfolgreich")
                                        except Exception as deploy_error:
                                            failed_deployments += 1
                                            print(f"   ❌ Deployment fehlgeschlagen: {deploy_error}")
                                            if CONFIG.get('debug_mode', False):
                                                traceback.print_exc()
                                            print(f"   → Job ist kuratiert und kann später manuell deployed werden.")
                                    
                                except Exception as curate_error:
                                    failed_curations += 1
                                    print(f"   ❌ Kuratierung fehlgeschlagen: {curate_error}")
                                    if CONFIG.get('debug_mode', False):
                                        traceback.print_exc()
                                    print(f"   → Job kann später manuell kuratiert werden.")
                        
                        # No additional pause after playlist (already handled in fetch_video)
                        continue
                    elif isinstance(result, str) and result == "playlist_skipped":
                        # Playlist was skipped (all videos already downloaded)
                        # No additional processing needed - this doesn't count toward expected downloads
                        continue
                    else:
                        # Download successful - result is a job directory path
                        successful_downloads += 1
                        total_expected_downloads += 1
                        downloaded_jobs.append(result)
                        
                        # Auto-curate if requested
                        if auto_curate:
                            print(f"\n   🎨 Starte Auto-Kuratierung...")
                            try:
                                interactive_curation(result, auto_mode=True)
                                successful_curations += 1
                                print(f"   ✅ Kuratierung erfolgreich")
                                
                                # Auto-deploy if requested
                                if auto_deploy:
                                    print(f"\n   🚀 Starte Auto-Deployment...")
                                    try:
                                        build_master(result)
                                        build_dist(result)
                                        deploy(result)
                                        successful_deployments += 1
                                        print(f"   ✅ Deployment erfolgreich")
                                    except Exception as deploy_error:
                                        failed_deployments += 1
                                        print(f"   ❌ Deployment fehlgeschlagen: {deploy_error}")
                                        if CONFIG.get('debug_mode', False):
                                            traceback.print_exc()
                                        print(f"   → Job ist kuratiert und kann später manuell deployed werden.")
                                
                            except Exception as curate_error:
                                failed_curations += 1
                                print(f"   ❌ Kuratierung fehlgeschlagen: {curate_error}")
                                if CONFIG.get('debug_mode', False):
                                    traceback.print_exc()
                                print(f"   → Job kann später manuell kuratiert werden.")
                    
                    # Pause zwischen Videos (aber nicht nach dem letzten)
                    if i < len(queue):
                        print(f"   ⏳ Warte {DOWNLOAD_PAUSE_SEC} Sekunden (YouTube Rate-Limit)...")
                        try:
                            time.sleep(DOWNLOAD_PAUSE_SEC)
                        except KeyboardInterrupt:
                            print("\n   ⚠️  Pause übersprungen.")
                
                # Summary
                print(f"\n{'='*60}")
                print(f"📊 ZUSAMMENFASSUNG")
                print(f"{'='*60}")
                
                if failed_downloads > 0:
                    print(f"⚠️  Download-Queue wurde wegen Fehler gestoppt.")
                else:
                    print(f"✅ Alle Downloads abgeschlossen.")
                
                # Use total_expected_downloads for the denominator if it's been set (for playlists/videos)
                # When total_expected_downloads is 0, it means only skipped playlists were processed
                # In that case, show "0/0" instead of "0/N" to avoid confusion
                if total_expected_downloads == 0:
                    # All playlists were skipped - no actual downloads attempted
                    total_downloads = 0
                else:
                    total_downloads = total_expected_downloads
                print(f"\n📥 Downloads:        {successful_downloads}/{total_downloads} erfolgreich")
                if failed_downloads > 0:
                    print(f"                     {failed_downloads} fehlgeschlagen")
                
                if auto_curate:
                    print(f"🎨 Kuratierungen:    {successful_curations}/{len(downloaded_jobs)} erfolgreich")
                    if failed_curations > 0:
                        print(f"                     {failed_curations} fehlgeschlagen")
                
                if auto_deploy:
                    print(f"🚀 Deployments:      {successful_deployments}/{successful_curations} erfolgreich")
                    if failed_deployments > 0:
                        print(f"                     {failed_deployments} fehlgeschlagen")
                
                input("\n[ENTER]...")
            else:
                print("   (Keine URL eingegeben)")

        elif choice == '2':
            import_menu()

        elif choice == '3':
            jobs = list_jobs()
            if not jobs: print("⚠️ Keine Jobs.")
            else:
                print("\nOffene Jobs:")
                for i, (jid, title, path, is_curated) in enumerate(jobs, 1):
                    mark = "✅" if is_curated else "⭕️"
                    print(f"{i}. {mark} {title}")
                
                print("\n   Kuratierungs-Modus:")
                print("   [m] Manuell (mit Bestätigungen)")
                print("   [a] Automatisch (K.I. trifft Entscheidungen)")
                print("   [d] Automatisch + Deploy (K.I. kuratiert und deployed)")
                print("   [b] Batch: Alle nicht-kuratierten Jobs automatisch kuratieren")
                print("   [bd] Batch + Deploy: Automatisch kuratieren und deployen")
                print("   [z] Zurück")
                
                mode_sel = input("\n   Modus wählen: ").strip().lower()
                
                if mode_sel == 'z':
                    continue
                elif mode_sel == 'b':
                    # Batch auto-curate all non-curated jobs
                    batch_auto_curate_all(deploy_after_curate=False)
                    continue
                elif mode_sel == 'bd':
                    # Batch auto-curate + deploy all non-curated jobs
                    batch_auto_curate_all(deploy_after_curate=True)
                    continue
                elif mode_sel not in ['m', 'a', 'd']:
                    print("⚠️  Ungültige Auswahl.")
                    continue
                
                auto_mode = (mode_sel in ['a', 'd'])
                deploy_after_curation = (mode_sel == 'd')
                
                sel = input("\nJob wählen (oder 'z' für Zurück): ").strip().lower()
                
                if sel == 'z':
                    continue
                
                if sel.isdigit() and 1 <= int(sel) <= len(jobs):
                    try:
                        job_path = jobs[int(sel)-1][2]
                        interactive_curation(job_path, auto_mode=auto_mode)
                        
                        # Deploy immediately after successful curation if requested
                        if deploy_after_curation:
                            print(f"\n🚀 Starte Deployment...")
                            try:
                                build_master(job_path)
                                build_dist(job_path)
                                deploy(job_path)
                                print(f"✅ Deployment erfolgreich abgeschlossen!")
                            except Exception as deploy_error:
                                print(f"❌ Deployment fehlgeschlagen: {deploy_error}")
                                print("💡 Job ist kuratiert und kann später manuell deployed werden.")
                                if CONFIG.get('debug_mode', False):
                                    traceback.print_exc()
                        
                    except Exception as e:
                        if auto_mode:
                            print(f"\n❌ Auto-Kuratierung fehlgeschlagen: {e}")
                            print("💡 Empfehlung: Bitte führe die Kuratierung manuell durch.")
                            if CONFIG.get('debug_mode', False):
                                traceback.print_exc()
                        else:
                            raise

        elif choice == '4':
             jobs = list_jobs()
             if not jobs: print("⚠️ Keine Jobs.")
             else:
                 print("\nJobs bereit zum Deployen:")
                 curated_indices = []
                 for i, (jid, title, path, is_curated) in enumerate(jobs, 1):
                     mark = "✅" if is_curated else "⚠️ "
                     print(f"{i}. {mark} {title}")
                     if is_curated: curated_indices.append(i-1)
                 
                 print("\n[ENTER] für ALLE deployen (nur kuratierte!).")
                 print("'z' für Zurück")
                 sel = input("Oder Nummer wählen: ").strip().lower()
                 
                 if sel == 'z':
                     continue
                 
                 jobs_to_process = []
                 if not sel: 
                     # Nur kuratierte Jobs
                     if not curated_indices:
                         print("⚠️  Keine kuratierten Jobs vorhanden.")
                         continue
                     jobs_to_process = [jobs[i] for i in curated_indices]
                     print(f"🚀 Starte Deployment für {len(jobs_to_process)} kuratierte Jobs...")
                 elif sel.isdigit() and 1 <= int(sel) <= len(jobs):
                     # Einzelner Job (auch unkuratiert möglich, falls gewünscht)
                     jobs_to_process = [jobs[int(sel)-1]]
                 else: continue

                 for idx, (jid, title, path, is_curated) in enumerate(jobs_to_process, 1):
                     print(f"\n⚙️  [{idx}/{len(jobs_to_process)}] Verarbeite: {title}")
                     try:
                         build_master(path)
                         build_dist(path)
                         deploy(path)
                         print(f"✅ Erledigt: {title}")
                     except Exception as e:
                         print(f"❌ FEHLER: {e}")
                 input("\n✅ Fertig. [ENTER]...")

        elif choice == '5':
            analysis_menu()

        elif choice == '6':
            settings_menu()

def main():
    # Version anzeigen
    if len(sys.argv) > 1 and sys.argv[1] in ["--version", "-V"]:
        print_version_report()
        sys.exit(0)
    
    if len(sys.argv) < 2:
        interactive_menu()
        sys.exit(0)
    
    # Batch/Direct Mode Arguments
    mode = _normalize_cli_mode(sys.argv[1])
    
    if mode == "fetch":
        if len(sys.argv) < 4: return
        fetch_video(sys.argv[2], sys.argv[3])
    elif mode == "curate":
        # Support optional --auto flag for command line usage
        auto_mode = "--auto" in sys.argv
        interactive_curation(sys.argv[2], auto_mode=auto_mode)
    elif mode == "build_master":
        build_master(sys.argv[2])
    elif mode == "build_dist":
        build_dist(sys.argv[2])
    elif mode == "deploy":
        deploy(sys.argv[2])
    elif mode == "rebuild_poster":
        args = sys.argv[2:]
        target_path = _get_first_non_option_arg(args)
        if not target_path:
            print("❌ Verwendung: internetfilm-manager rebuild-poster <pfad> [--batch] [--recursive] [--new-frame] [--fanart]")
            sys.exit(1)

        from integrator.module_poster_rebuild import rebuild_poster, rebuild_posters_batch

        batch_mode = "--batch" in args
        recursive = "--recursive" in args
        new_frame = "--new-frame" in args
        fanart = True if "--fanart" in args else None

        if batch_mode:
            rebuild_posters_batch(target_path, new_frame=new_frame, fanart=fanart, recursive=recursive)
        else:
            success = rebuild_poster(target_path, new_frame=new_frame, fanart=fanart)
            sys.exit(0 if success else 1)
    else:
        print(f"❌ Unbekannter Befehl: {sys.argv[1]}")
        sys.exit(1)

if __name__ == "__main__":
    main()
