#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
#     "PyQt5==5.15.9",
#     "PyInstaller==5.13.2",
#     "requests==2.31.0",
# ]
# ///
import sys
import os
import json
import subprocess
import re
import urllib.request
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                            QLabel, QLineEdit, QPushButton, QComboBox, QTextEdit, 
                            QFileDialog, QMessageBox, QListWidget, QDialog, QFormLayout,
                            QDialogButtonBox, QInputDialog, QGroupBox, QCheckBox, QTabWidget)
from PyQt5.QtCore import QThread, pyqtSignal, Qt

# Hilfsfunktionen für den Ressourcenpfad
def get_base_path():
    """Gibt den Basispfad für Ressourcen zurück, funktioniert sowohl für PyInstaller als auch für reguläre Ausführung."""
    if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
        # PyInstaller-Bundled-Modus
        return sys._MEIPASS
    else:
        # Regulärer Modus
        return os.path.dirname(os.path.abspath(__file__))

def get_user_data_dir():
    if getattr(sys, 'frozen', False):
        return os.path.dirname(sys.executable)
    else:
        return os.path.dirname(os.path.abspath(__file__))

def get_user_presets_dir():
    # Presets-Ordner neben der EXE (bzw. Script)
    base = get_user_data_dir()
    path = os.path.join(base, "presets")
    if not os.path.exists(path):
        os.makedirs(path, exist_ok=True)
    return path

CONFIG_FILE = os.path.join(get_user_data_dir(), "config.json")
PRESETS_DIR = os.path.join(get_base_path(), "presets")  # Nur für Lesezugriff auf mitgelieferte Presets
DEFAULT_CONFIG = {
    "output_dir": "",
    "use_local_ytdlp": True,
    "last_preset": "",
    "presets": [],
    "ytdlp_flags": {
        "ignore_config": False,
        "remux_mkv": False,
        "embed_metadata": False
    },
    "hide_default_presets": False
}

# Template-Variablen für Serien
SERIES_TEMPLATE = "{series} S{season}E{episode}{extension}"

class DownloadThread(QThread):
    update_signal = pyqtSignal(str)
    finished_signal = pyqtSignal(bool, str)

    def __init__(self, url, output_dir, cmd_args, use_local_ytdlp, output_filename=None):
        super().__init__()
        self.url = url
        self.output_dir = output_dir
        self.cmd_args = cmd_args
        self.use_local_ytdlp = use_local_ytdlp
        self.output_filename = output_filename

    def run(self):
        try:
            # Bestimme den Pfad zu yt-dlp
            if self.use_local_ytdlp:
                ytdlp_path = os.path.join(get_base_path(), "bin", "yt-dlp.exe")
                if not os.path.exists(ytdlp_path):
                    ytdlp_path = "yt-dlp"  # Fallback auf PATH
            else:
                ytdlp_path = "yt-dlp"
            
            cmd = [ytdlp_path]
            
            # Debug-Ausgabe
            self.update_signal.emit(f"Debug - self.cmd_args: {self.cmd_args}")
            self.update_signal.emit(f"Debug - self.output_dir: {self.output_dir}")
            self.update_signal.emit(f"Debug - self.output_filename: {self.output_filename}")
            
            if self.cmd_args:
                # Argumente per Split aufteilen, dabei aber Anführungszeichen berücksichtigen
                args = []
                in_quotes = False
                current_arg = ""
                
                for char in self.cmd_args:
                    if char == '"' or char == "'":
                        in_quotes = not in_quotes
                        current_arg += char
                    elif char.isspace() and not in_quotes:
                        if current_arg:
                            args.append(current_arg)
                            current_arg = ""
                    else:
                        current_arg += char
                        
                if current_arg:
                    args.append(current_arg)
                
                # Debug-Ausgabe
                self.update_signal.emit(f"Debug - Parsed args: {args}")    
                cmd.extend(args)
            
            if self.output_dir:
                if self.output_filename:
                    output_path = os.path.join(self.output_dir, self.output_filename)
                    self.update_signal.emit(f"Debug - Vollständiger Ausgabepfad: {output_path}")
                    cmd.extend(["-o", output_path])
                else:
                    default_path = os.path.join(self.output_dir, "%(title)s.%(ext)s")
                    self.update_signal.emit(f"Debug - Standard-Ausgabepfad: {default_path}")
                    cmd.extend(["-o", default_path])
            elif self.output_filename:
                self.update_signal.emit(f"Debug - Nur Ausgabedateiname: {self.output_filename}")
                cmd.extend(["-o", self.output_filename])
            
            cmd.append(self.url)
            
            # Debug-Ausgabe vor der Formatierung
            self.update_signal.emit(f"Debug - Befehlszeile vor Formatierung: {cmd}")
            
            # Formatiere die Befehlszeile mit Anführungszeichen für Elemente mit Leerzeichen
            formatted_cmd = []
            for arg in cmd:
                if " " in arg and not (arg.startswith('"') and arg.endswith('"')) and not (arg.startswith("'") and arg.endswith("'")):
                    formatted_cmd.append(f'"{arg}"')
                else:
                    formatted_cmd.append(arg)
            
            self.update_signal.emit(f"Ausführen: {' '.join(formatted_cmd)}")
            
            # Unterdrücke das CMD-Fenster unter Windows
            creationflags = subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
            process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
                bufsize=1,
                universal_newlines=True,
                creationflags=creationflags
            )
            
            for line in process.stdout:
                self.update_signal.emit(line.strip())
            
            process.wait()
            
            if process.returncode == 0:
                self.finished_signal.emit(True, "Download erfolgreich abgeschlossen!")
            else:
                self.finished_signal.emit(False, f"Download fehlgeschlagen mit Exitcode {process.returncode}")
                
        except Exception as e:
            self.update_signal.emit(f"Fehler: {str(e)}")
            self.finished_signal.emit(False, f"Fehler: {str(e)}")


class PresetDialog(QDialog):
    def __init__(self, parent=None, preset_data=None):
        super().__init__(parent)
        self.setWindowTitle("Preset erstellen/bearbeiten")
        self.resize(500, 450)
        
        self.preset_data = preset_data or {
            "name": "", 
            "description": "", 
            "args": "",
            "has_series_template": False,
            "series_template": SERIES_TEMPLATE,
            "series": "",
            "season": "1",
            "episode": "1",
            "extension": ".mkv",
            "custom_path": "",
            "is_audio": False
        }
        
        self.setup_ui()
        
    def setup_ui(self):
        layout = QVBoxLayout()
        form_layout = QFormLayout()
        
        self.name_edit = QLineEdit(self.preset_data["name"])
        self.description_edit = QLineEdit(self.preset_data["description"])
        self.args_edit = QTextEdit(self.preset_data["args"])
        
        form_layout.addRow("Name:", self.name_edit)
        form_layout.addRow("Beschreibung:", self.description_edit)
        form_layout.addRow("yt-dlp Argumente:", self.args_edit)
        
        # Audio-Preset Checkbox
        self.is_audio_cb = QCheckBox("Ist Audio-Preset (kein Remux nach MKV)")
        self.is_audio_cb.setChecked(self.preset_data.get("is_audio", False))
        form_layout.addRow(self.is_audio_cb)
        
        # Referer
        self.referer_edit = QLineEdit(self.preset_data.get("referer", ""))
        self.referer_edit.setPlaceholderText("Optional: Referer-Link für --referer=")
        form_layout.addRow("Referer:", self.referer_edit)
        
        # HLS-ffmpeg Checkbox
        self.hls_ffmpeg_cb = QCheckBox("HLS-Streams mit ffmpeg herunterladen (--downloader ffmpeg --hls-use-mpegts)")
        self.hls_ffmpeg_cb.setChecked(self.preset_data.get("hls_ffmpeg", False))
        form_layout.addRow(self.hls_ffmpeg_cb)
        
        # Untertitel-Optionen
        self.sublang_edit = QLineEdit(self.preset_data.get("sublang", ""))
        self.sublang_edit.setPlaceholderText("z.B. de, en, de,en ...")
        form_layout.addRow("Untertitelsprache:", self.sublang_edit)
        self.embed_subs_cb = QCheckBox("Untertitel einbetten (--embed-subs)")
        self.embed_subs_cb.setChecked(self.preset_data.get("embed_subs", False))
        form_layout.addRow(self.embed_subs_cb)
        
        # Untertitel-Format Dropdown
        self.subformat_combo = QComboBox()
        self.subformat_combo.addItem("(keine Konvertierung)", "")
        self.subformat_combo.addItem("srt", "srt")
        self.subformat_combo.addItem("ass", "ass")
        self.subformat_combo.addItem("tx3g", "tx3g")
        # Vorbelegen
        subformat = self.preset_data.get("subformat", "")
        idx = self.subformat_combo.findData(subformat)
        if idx >= 0:
            self.subformat_combo.setCurrentIndex(idx)
        form_layout.addRow("Untertitel-Format:", self.subformat_combo)
        
        # Eigener Pfad (immer sichtbar, mit Durchsuchen)
        self.custom_path_edit = QLineEdit(self.preset_data.get("custom_path", ""))
        self.custom_path_edit.setPlaceholderText("Optional: Eigener Zielordner für Download")
        custom_path_hbox = QHBoxLayout()
        custom_path_hbox.addWidget(self.custom_path_edit)
        self.custom_path_browse_btn = QPushButton("Durchsuchen...")
        self.custom_path_browse_btn.clicked.connect(self.browse_custom_path)
        custom_path_hbox.addWidget(self.custom_path_browse_btn)
        form_layout.addRow("Eigener Pfad:", custom_path_hbox)
        
        # Login-Felder
        self.username_edit = QLineEdit(self.preset_data.get("username", ""))
        self.username_edit.setPlaceholderText("Optional: Benutzername für Login (-u)")
        form_layout.addRow("Benutzername:", self.username_edit)
        pw_hbox = QHBoxLayout()
        self.password_edit = QLineEdit(self.preset_data.get("password", ""))
        self.password_edit.setEchoMode(QLineEdit.Password)
        self.password_edit.setPlaceholderText("Optional: Passwort für Login (-p)")
        self.show_pw_cb = QCheckBox("Passwort anzeigen")
        self.show_pw_cb.toggled.connect(self.toggle_password_visible)
        pw_hbox.addWidget(self.password_edit)
        pw_hbox.addWidget(self.show_pw_cb)
        form_layout.addRow("Passwort:", pw_hbox)
        pw_hint = QLabel("Hinweis: Passwörter werden im Klartext lokal gespeichert!")
        pw_hint.setStyleSheet("color: red;")
        form_layout.addRow(pw_hint)
        
        # Serien-Template
        self.series_box = QGroupBox("Serien-Template aktivieren")
        self.series_box.setCheckable(True)
        self.series_box.setChecked(self.preset_data.get("has_series_template", False))
        
        series_layout = QFormLayout()
        
        self.template_edit = QLineEdit(self.preset_data.get("series_template", SERIES_TEMPLATE))
        self.series_edit = QLineEdit(self.preset_data.get("series", ""))
        self.season_edit = QLineEdit(self.preset_data.get("season", "1"))
        self.episode_edit = QLineEdit(self.preset_data.get("episode", "1"))
        
        series_layout.addRow("Template:", self.template_edit)
        series_layout.addRow("Serie:", self.series_edit)
        series_layout.addRow("Staffel:", self.season_edit)
        series_layout.addRow("Folge:", self.episode_edit)
        
        help_text = QLabel("Verwende {series}, {season}, {episode}, %(ext)s und {path} im Template.")
        series_layout.addRow(help_text)
        
        self.series_box.setLayout(series_layout)
        
        layout.addLayout(form_layout)
        layout.addWidget(self.series_box)
        
        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        
        layout.addWidget(buttons)
        self.setLayout(layout)
        
    def toggle_password_visible(self, checked):
        self.password_edit.setEchoMode(QLineEdit.Normal if checked else QLineEdit.Password)

    def get_preset_data(self):
        return {
            "name": self.name_edit.text(),
            "description": self.description_edit.text(),
            "args": self.args_edit.toPlainText(),
            "has_series_template": self.series_box.isChecked(),
            "series_template": self.template_edit.text(),
            "series": self.series_edit.text(),
            "season": self.season_edit.text(),
            "episode": self.episode_edit.text(),
            "custom_path": self.custom_path_edit.text(),
            "is_audio": self.is_audio_cb.isChecked(),
            "username": self.username_edit.text(),
            "password": self.password_edit.text(),
            "referer": self.referer_edit.text(),
            "hls_ffmpeg": self.hls_ffmpeg_cb.isChecked(),
            "sublang": self.sublang_edit.text(),
            "embed_subs": self.embed_subs_cb.isChecked(),
            "subformat": self.subformat_combo.currentData()
        }

    def browse_custom_path(self):
        directory = QFileDialog.getExistingDirectory(self, "Eigenen Zielordner auswählen", self.custom_path_edit.text() or "")
        if directory:
            self.custom_path_edit.setText(directory)


class YtDlpDownloadThread(QThread):
    finished_signal = pyqtSignal(bool, str)
    def run(self):
        try:
            url = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe"
            bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
            os.makedirs(bin_dir, exist_ok=True)
            dest_path = os.path.join(bin_dir, "yt-dlp.exe")
            urllib.request.urlretrieve(url, dest_path)
            self.finished_signal.emit(True, f"yt-dlp.exe wurde erfolgreich nach {dest_path} heruntergeladen.")
        except Exception as e:
            self.finished_signal.emit(False, f"Fehler beim Herunterladen von yt-dlp.exe: {str(e)}")


class OptionenDialog(QDialog):
    def __init__(self, output_dir, use_local_ytdlp, parent=None, ytdlp_flags=None):
        super().__init__(parent)
        self.setWindowTitle("Optionen")
        self.resize(420, 250)
        self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
        self.selected_output_dir = output_dir
        self.selected_use_local_ytdlp = use_local_ytdlp
        self.selected_flags = ytdlp_flags or {
            "ignore_config": False,
            "remux_mkv": False,
            "embed_metadata": False
        }
        self.setup_ui()

    def setup_ui(self):
        main_layout = QVBoxLayout()
        tabs = QTabWidget()

        # Tab 1: Allgemein
        tab_allgemein = QWidget()
        layout = QFormLayout()
        self.output_dir_input = QLineEdit(self.selected_output_dir)
        browse_btn = QPushButton("Durchsuchen...")
        browse_btn.clicked.connect(self.browse_output_dir)
        hbox = QHBoxLayout()
        hbox.addWidget(self.output_dir_input)
        hbox.addWidget(browse_btn)
        layout.addRow("Standardpfad:", hbox)
        self.ytdlp_source_combo = QComboBox()
        self.ytdlp_source_combo.addItems(["Lokal (bin/yt-dlp.exe)", "System (PATH)"])
        self.ytdlp_source_combo.setCurrentIndex(0 if self.selected_use_local_ytdlp else 1)
        layout.addRow("yt-dlp-Quelle:", self.ytdlp_source_combo)
        # Default-Presets ausblenden
        self.hide_defaults_cb = QCheckBox("Default-Presets ausblenden")
        self.hide_defaults_cb.setChecked(self.parent().config.get("hide_default_presets", False) if self.parent() else False)
        layout.addRow(self.hide_defaults_cb)
        bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
        ytdlp_path = os.path.join(bin_dir, "yt-dlp.exe")
        # Button 1: Herunterladen
        self.download_btn = QPushButton("yt-dlp.exe herunterladen")
        self.download_btn.clicked.connect(self.download_ytdlp)
        self.download_btn.setEnabled(not os.path.exists(ytdlp_path))
        layout.addRow(self.download_btn)
        # Button 2: Updaten
        self.update_btn = QPushButton("yt-dlp.exe updaten")
        self.update_btn.clicked.connect(self.update_ytdlp)
        layout.addRow(self.update_btn)
        tab_allgemein.setLayout(layout)
        tabs.addTab(tab_allgemein, "Allgemein")

        # Tab 2: yt-dlp-Flags
        tab_flags = QWidget()
        flags_layout = QVBoxLayout()
        self.cb_ignore_config = QCheckBox("--ignore-config")
        self.cb_ignore_config.setChecked(self.selected_flags.get("ignore_config", False))
        flags_layout.addWidget(self.cb_ignore_config)
        flags_layout.addWidget(QLabel("Ignoriert die systemweite yt-dlp-Konfiguration."))
        self.cb_remux_mkv = QCheckBox("--remux-video mkv")
        self.cb_remux_mkv.setChecked(self.selected_flags.get("remux_mkv", False))
        flags_layout.addWidget(self.cb_remux_mkv)
        flags_layout.addWidget(QLabel("Remuxt das Video ins MKV-Format."))
        self.cb_embed_metadata = QCheckBox("--embed-metadata")
        self.cb_embed_metadata.setChecked(self.selected_flags.get("embed_metadata", False))
        flags_layout.addWidget(self.cb_embed_metadata)
        flags_layout.addWidget(QLabel("Betten Metadaten in die Ausgabedatei ein."))
        tab_flags.setLayout(flags_layout)
        tabs.addTab(tab_flags, "yt-dlp-Flags")

        # Tab 3: Info
        tab_info = QWidget()
        info_layout = QVBoxLayout()
        info_text = (
            "<b>Version:</b> 1.0<br>"
            "<b>&copy; 2025 Akamaru</b><br>"
            "<b>Sourcecode:</b> <a href='https://git.ponywave.de/Akamaru/video-download-helper'>https://git.ponywave.de/Akamaru/video-download-helper</a><br>"
            "<b>Erstellt mit Hilfe von Claude, GPT &amp; Gemini</b>"
        )
        info_label = QLabel(info_text)
        info_label.setOpenExternalLinks(True)
        info_label.setTextFormat(Qt.RichText)
        info_layout.addWidget(info_label)
        info_layout.addStretch(1)
        tab_info.setLayout(info_layout)
        tabs.addTab(tab_info, "Info")

        main_layout.addWidget(tabs)
        # Stelle sicher, dass QDialogButtonBox nur einmal erstellt und verbunden wird
        if hasattr(self, 'button_box'):
            try:
                main_layout.removeWidget(self.button_box)
                self.button_box.deleteLater()
            except Exception:
                pass
        self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        main_layout.addWidget(self.button_box)
        self.setLayout(main_layout)

    def browse_output_dir(self):
        directory = QFileDialog.getExistingDirectory(self, "Standardpfad auswählen", self.output_dir_input.text() or os.path.expanduser("~"))
        if directory:
            self.output_dir_input.setText(directory)

    def get_values(self):
        return (
            self.output_dir_input.text(),
            self.ytdlp_source_combo.currentIndex() == 0,
            {
                "ignore_config": self.cb_ignore_config.isChecked(),
                "remux_mkv": self.cb_remux_mkv.isChecked(),
                "embed_metadata": self.cb_embed_metadata.isChecked()
            },
            self.hide_defaults_cb.isChecked()
        )

    def download_ytdlp(self):
        self.download_btn.setEnabled(False)
        self.download_btn.setText("Lade herunter...")
        self.thread = YtDlpDownloadThread()
        self.thread.finished_signal.connect(self.download_finished)
        self.thread.start()

    def download_finished(self, success, message):
        bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
        ytdlp_path = os.path.join(bin_dir, "yt-dlp.exe")
        self.download_btn.setEnabled(not os.path.exists(ytdlp_path))
        self.download_btn.setText("yt-dlp.exe herunterladen")
        if success:
            QMessageBox.information(self, "Erfolg", message)
        else:
            QMessageBox.critical(self, "Fehler", message)

    def update_ytdlp(self):
        self.update_btn.setEnabled(False)
        self.update_btn.setText("Aktualisiere...")
        # Bestimme, ob lokal oder systemweit
        use_local = self.ytdlp_source_combo.currentIndex() == 0
        if use_local:
            bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
            ytdlp_path = os.path.join(bin_dir, "yt-dlp.exe")
            cmd = [ytdlp_path, "-U"]
        else:
            cmd = ["yt-dlp", "-U"]
        try:
            creationflags = subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
            result = subprocess.run(cmd, capture_output=True, text=True, creationflags=creationflags)
            if result.returncode == 0:
                QMessageBox.information(self, "Erfolg", f"yt-dlp wurde aktualisiert.\n\n{result.stdout}")
            else:
                QMessageBox.warning(self, "Fehler", f"Fehler beim Updaten von yt-dlp:\n{result.stderr or result.stdout}")
        except Exception as e:
            QMessageBox.critical(self, "Fehler", f"Fehler beim Ausführen von yt-dlp -U: {str(e)}")
        self.update_btn.setEnabled(True)
        self.update_btn.setText("yt-dlp.exe updaten")


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Video Download Helper")
        self.resize(800, 600)
        
        # Stelle sicher, dass Verzeichnisse existieren
        self.ensure_directories()
        
        # Default-Presets automatisch anlegen, falls keine vorhanden
        if not any(f.endswith('.json') for f in os.listdir(PRESETS_DIR)):
            self.create_default_presets()
        
        self.config = self.load_config()
        self.presets = self.load_presets()
        
        self.download_thread = None
        
        self.setup_ui()
        
    def ensure_directories(self):
        """Stellt sicher, dass alle benötigten Verzeichnisse existieren."""
        # Stelle sicher, dass der Presets-Ordner existiert
        os.makedirs(PRESETS_DIR, exist_ok=True)
        
        # Stelle sicher, dass der bin-Ordner existiert
        bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
        os.makedirs(bin_dir, exist_ok=True)
        
    def setup_ui(self):
        central_widget = QWidget()
        main_layout = QVBoxLayout()
        
        # URL Input
        url_layout = QHBoxLayout()
        url_layout.addWidget(QLabel("Video-URL:"))
        self.url_input = QLineEdit()
        url_layout.addWidget(self.url_input)
        main_layout.addLayout(url_layout)
        
        # Preset Selection
        preset_layout = QHBoxLayout()
        preset_layout.addWidget(QLabel("Preset:"))
        self.preset_combo = QComboBox()
        self.update_preset_combo()
        preset_layout.addWidget(self.preset_combo)
        
        # Preset Buttons
        self.add_preset_btn = QPushButton("Neu")
        self.add_preset_btn.clicked.connect(self.add_preset)
        preset_layout.addWidget(self.add_preset_btn)
        
        self.edit_preset_btn = QPushButton("Bearbeiten")
        self.edit_preset_btn.clicked.connect(self.edit_preset)
        preset_layout.addWidget(self.edit_preset_btn)
        
        self.delete_preset_btn = QPushButton("Löschen")
        self.delete_preset_btn.clicked.connect(self.delete_preset)
        preset_layout.addWidget(self.delete_preset_btn)
        
        main_layout.addLayout(preset_layout)
        
        # Serien-Einstellungen
        self.series_group = QGroupBox("Serien-Einstellungen")
        self.series_group.setVisible(False)
        
        series_form = QFormLayout()
        
        self.series_input = QLineEdit()
        self.season_input = QLineEdit()
        self.episode_input = QLineEdit()
        
        series_form.addRow("Serie:", self.series_input)
        series_form.addRow("Staffel:", self.season_input)
        series_form.addRow("Folge:", self.episode_input)
        
        self.custom_path_input = QLineEdit()
        
        series_form.addRow("Eigener Pfad:", self.custom_path_input)
        
        self.series_group.setLayout(series_form)
        main_layout.addWidget(self.series_group)
        
        # Options-Button statt Felder für Standardpfad und yt-dlp-Quelle
        optionen_layout = QHBoxLayout()
        self.optionen_btn = QPushButton("Optionen...")
        self.optionen_btn.clicked.connect(self.open_optionen_dialog)
        optionen_layout.addWidget(self.optionen_btn)
        main_layout.addLayout(optionen_layout)
        
        # Command Preview
        main_layout.addWidget(QLabel("Befehlsvorschau:"))
        self.cmd_preview = QTextEdit()
        self.cmd_preview.setReadOnly(True)
        self.cmd_preview.setMaximumHeight(60)
        main_layout.addWidget(self.cmd_preview)
        
        # Download Button
        self.download_btn = QPushButton("Download starten")
        self.download_btn.clicked.connect(self.start_download)
        main_layout.addWidget(self.download_btn)
        
        # Log Output
        main_layout.addWidget(QLabel("Ausgabe:"))
        self.log_output = QTextEdit()
        self.log_output.setReadOnly(True)
        main_layout.addWidget(self.log_output)
        
        # Connect signals
        self.url_input.textChanged.connect(self.update_cmd_preview)
        self.preset_combo.currentIndexChanged.connect(self.preset_changed)
        # self.optionen_btn.clicked.connect(self.open_optionen_dialog)  # Entfernt, um doppeltes Öffnen zu verhindern
        
        # Serie, Staffel, Folge Signals
        self.series_input.textChanged.connect(self.update_cmd_preview)
        self.season_input.textChanged.connect(self.update_cmd_preview)
        self.episode_input.textChanged.connect(self.update_cmd_preview)
        self.custom_path_input.textChanged.connect(self.update_cmd_preview)
        
        central_widget.setLayout(main_layout)
        self.setCentralWidget(central_widget)
        
        # Initial update
        self.preset_changed()
        self.update_cmd_preview()
        
    def load_config(self):
        if os.path.exists(CONFIG_FILE):
            try:
                with open(CONFIG_FILE, 'r') as f:
                    return json.load(f)
            except Exception:
                return DEFAULT_CONFIG.copy()
        return DEFAULT_CONFIG.copy()
    
    def save_config(self):
        self.config["output_dir"] = self.output_dir_input.text() if hasattr(self, 'output_dir_input') else self.config.get("output_dir", "")
        self.config["use_local_ytdlp"] = self.ytdlp_source_combo.currentIndex() == 0 if hasattr(self, 'ytdlp_source_combo') else self.config.get("use_local_ytdlp", True)
        if self.preset_combo.currentIndex() >= 0:
            self.config["last_preset"] = self.preset_combo.currentText()
        try:
            with open(CONFIG_FILE, 'w') as f:
                json.dump(self.config, f, indent=4)
        except Exception as e:
            self.log_output.append(f"Fehler beim Speichern der Konfiguration: {str(e)}")
    
    def load_presets(self):
        # Default-Presets immer aus dem Code laden
        defaults = [
            {
                "name": "Audio (MP3)",
                "description": "Nur Audio als MP3",
                "args": "-x --audio-format mp3",
                "has_series_template": False,
                "is_audio": True,
                "source": "default"
            },
            {
                "name": "Video (Best)",
                "description": "Beste Videoqualität",
                "args": "-f bestvideo+bestaudio",
                "has_series_template": False,
                "is_audio": False,
                "source": "default"
            }
        ]
        presets = defaults.copy()
        # User-Presets (können mitgelieferte überschreiben)
        user_presets_dir = get_user_presets_dir()
        for filename in os.listdir(user_presets_dir):
            if filename.endswith('.json'):
                try:
                    with open(os.path.join(user_presets_dir, filename), 'r', encoding='utf-8') as f:
                        preset = json.load(f)
                        preset["source"] = "user"
                        # Überschreibe ggf. gleichnamiges Preset
                        presets = [p for p in presets if p["name"] != preset["name"]]
                        presets.append(preset)
                except Exception as e:
                    self.log_output.append(f"Fehler beim Laden von User-Preset {filename}: {str(e)}")
        return presets
    
    def create_default_presets(self):
        defaults = [
            {
                "name": "Audio (MP3)",
                "description": "Nur Audio als MP3",
                "args": "-x --audio-format mp3",
                "has_series_template": False,
                "is_audio": True,
                "source": "default"
            },
            {
                "name": "Video (Best)",
                "description": "Beste Videoqualität",
                "args": "-f bestvideo+bestaudio",
                "has_series_template": False,
                "is_audio": False,
                "source": "default"
            }
        ]
        
        for preset in defaults:
            filename = f"{preset['name'].lower().replace(' ', '_')}.json"
            try:
                with open(os.path.join(PRESETS_DIR, filename), 'w') as f:
                    json.dump(preset, f, indent=4)
            except Exception as e:
                self.log_output.append(f"Fehler beim Erstellen von Standard-Preset {preset['name']}: {str(e)}")
    
    def update_preset_combo(self):
        self.preset_combo.clear()
        hide_defaults = self.config.get("hide_default_presets", False)
        for preset in self.presets:
            if hide_defaults and preset.get("source") == "default":
                continue
            self.preset_combo.addItem(preset["name"])
        # Letztes verwendetes Preset auswählen
        if self.config["last_preset"]:
            index = self.preset_combo.findText(self.config["last_preset"])
            if index >= 0:
                self.preset_combo.setCurrentIndex(index)
                
    def get_current_preset(self):
        if self.preset_combo.currentIndex() >= 0:
            preset_name = self.preset_combo.currentText()
            for preset in self.presets:
                if preset["name"] == preset_name:
                    return preset
        return None
    
    def add_preset(self):
        dialog = PresetDialog(self)
        if dialog.exec_() == QDialog.Accepted:
            preset_data = dialog.get_preset_data()
            if not preset_data["name"]:
                QMessageBox.warning(self, "Fehler", "Der Preset-Name darf nicht leer sein.")
                return
            for preset in self.presets:
                if preset["name"] == preset_data["name"]:
                    QMessageBox.warning(self, "Fehler", f"Ein Preset mit dem Namen '{preset_data['name']}' existiert bereits.")
                    return
            preset_data["source"] = "user"
            self.presets.append(preset_data)
            # Speichern des Presets im User-Presets-Ordner
            user_presets_dir = get_user_presets_dir()
            filename = f"{preset_data['name'].lower().replace(' ', '_')}.json"
            try:
                with open(os.path.join(user_presets_dir, filename), 'w', encoding='utf-8') as f:
                    json.dump(preset_data, f, indent=4, ensure_ascii=False)
                self.update_preset_combo()
                self.preset_combo.setCurrentText(preset_data["name"])
                self.update_cmd_preview()
            except Exception as e:
                self.log_output.append(f"Fehler beim Speichern des Presets: {str(e)}")
    
    def edit_preset(self):
        current_preset = self.get_current_preset()
        if not current_preset:
            QMessageBox.warning(self, "Warnung", "Kein Preset ausgewählt.")
            return
        dialog = PresetDialog(self, current_preset)
        if dialog.exec_() == QDialog.Accepted:
            new_preset_data = dialog.get_preset_data()
            if not new_preset_data["name"]:
                QMessageBox.warning(self, "Fehler", "Der Preset-Name darf nicht leer sein.")
                return
            if new_preset_data["name"] != current_preset["name"]:
                for preset in self.presets:
                    if preset["name"] == new_preset_data["name"] and preset != current_preset:
                        QMessageBox.warning(self, "Fehler", f"Ein Preset mit dem Namen '{new_preset_data['name']}' existiert bereits.")
                        return
            if new_preset_data["name"] != current_preset["name"]:
                old_filename = f"{current_preset['name'].lower().replace(' ', '_')}.json"
                try:
                    os.remove(os.path.join(get_user_presets_dir(), old_filename))
                except Exception as e:
                    self.log_output.append(f"Warnung: Konnte alte Preset-Datei nicht löschen: {str(e)}")
            for i, preset in enumerate(self.presets):
                if preset["name"] == current_preset["name"]:
                    new_preset_data["source"] = "user"
                    self.presets[i] = new_preset_data
                    break
            filename = f"{new_preset_data['name'].lower().replace(' ', '_')}.json"
            try:
                with open(os.path.join(get_user_presets_dir(), filename), 'w', encoding='utf-8') as f:
                    json.dump(new_preset_data, f, indent=4, ensure_ascii=False)
                old_current_index = self.preset_combo.currentIndex()
                self.update_preset_combo()
                new_index = self.preset_combo.findText(new_preset_data["name"])
                if new_index >= 0:
                    self.preset_combo.setCurrentIndex(new_index)
                else:
                    self.preset_combo.setCurrentIndex(old_current_index)
                self.update_cmd_preview()
            except Exception as e:
                self.log_output.append(f"Fehler beim Speichern des Presets: {str(e)}")
    
    def delete_preset(self):
        current_preset = self.get_current_preset()
        if not current_preset:
            QMessageBox.warning(self, "Warnung", "Kein Preset ausgewählt.")
            return
        reply = QMessageBox.question(
            self, 
            "Preset löschen",
            f"Möchten Sie das Preset '{current_preset['name']}' wirklich löschen?",
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No
        )
        if reply == QMessageBox.Yes:
            self.presets = [p for p in self.presets if p["name"] != current_preset["name"]]
            filename = f"{current_preset['name'].lower().replace(' ', '_')}.json"
            try:
                os.remove(os.path.join(get_user_presets_dir(), filename))
                self.update_preset_combo()
                self.update_cmd_preview()
            except Exception as e:
                self.log_output.append(f"Fehler beim Löschen des Presets: {str(e)}")
    
    def open_optionen_dialog(self):
        dialog = OptionenDialog(
            self.config["output_dir"],
            self.config["use_local_ytdlp"],
            self,
            self.config.get("ytdlp_flags", DEFAULT_CONFIG["ytdlp_flags"])
        )
        if dialog.exec_() == QDialog.Accepted:
            output_dir, use_local_ytdlp, ytdlp_flags, hide_defaults = dialog.get_values()
            self.config["output_dir"] = output_dir
            self.config["use_local_ytdlp"] = use_local_ytdlp
            self.config["ytdlp_flags"] = ytdlp_flags
            self.config["hide_default_presets"] = hide_defaults
            self.update_preset_combo()
            self.update_cmd_preview()
    
    def preset_changed(self):
        preset = self.get_current_preset()
        if not preset:
            self.series_group.setVisible(False)
            return
            
        # Aktualisiere Serien-UI basierend auf dem Preset
        has_series = preset.get("has_series_template", False)
        self.series_group.setVisible(has_series)
        
        if has_series:
            self.series_input.setText(preset.get("series", ""))
            self.season_input.setText(preset.get("season", "1"))
            self.episode_input.setText(preset.get("episode", "1"))
            self.custom_path_input.setText(preset.get("custom_path", ""))
        
        self.update_cmd_preview()
    
    def get_output_filename(self, preset):
        if preset.get("has_series_template", False):
            template = preset.get("series_template", SERIES_TEMPLATE)
            series = self.series_input.text() or preset.get("series", "")
            season = self.season_input.text() or preset.get("season", "1")
            episode = self.episode_input.text() or preset.get("episode", "1")
            custom_path = self.custom_path_input.text() or preset.get("custom_path", "")
            # Stelle sicher, dass der Pfad mit einem Trennzeichen endet, wenn er nicht leer ist
            if custom_path and not custom_path.endswith("/") and not custom_path.endswith("\\"):
                custom_path += "/"  # Verwende / als universellen Pfadtrenner
            output_name = template
            output_name = output_name.replace("{series}", series)
            output_name = output_name.replace("{season}", season)
            output_name = output_name.replace("{episode}", episode)
            output_name = output_name.replace("{path}", custom_path)
            # {extension} durch .%(ext)s ersetzen (auch rückwärtskompatibel)
            output_name = output_name.replace("{extension}", ".%(ext)s")
            # Falls jemand %(ext)s nicht im Template hat, ergänzen wir es am Ende
            if "%(ext)s" not in output_name:
                output_name += ".%(ext)s"
            # Entferne doppelte Schrägstriche
            while "//" in output_name:
                output_name = output_name.replace("//", "/")
            while "\\\\" in output_name:
                output_name = output_name.replace("\\\\", "\\")
            self.log_output.append("Ausgabedatei-Komponenten:")
            self.log_output.append(f"  Template: {template}")
            self.log_output.append(f"  Serie: {series}")
            self.log_output.append(f"  Staffel: {season}")
            self.log_output.append(f"  Folge: {episode}")
            self.log_output.append(f"  Eigener Pfad: {custom_path}")
            self.log_output.append(f"Generierter Dateiname: {output_name}")
            return output_name
        # Debug-Ausgabe auch im Nicht-Serienfall
        output_name = "%(title)s.%(ext)s"
        custom_path = self.custom_path_input.text() or preset.get("custom_path", "")
        if custom_path and not custom_path.endswith("/") and not custom_path.endswith("\\"):
            custom_path += "/"
        if custom_path:
            output_name = custom_path + output_name
        self.log_output.append("Ausgabedatei-Komponenten:")
        self.log_output.append(f"  Template: {output_name}")
        self.log_output.append(f"  Eigener Pfad: {custom_path}")
        self.log_output.append(f"Generierter Dateiname: {output_name}")
        return output_name
    
    def update_cmd_preview(self):
        preset = self.get_current_preset()
        if not preset:
            self.cmd_preview.setText("Kein Preset ausgewählt.")
            return
        if self.config["use_local_ytdlp"]:
            ytdlp_path = os.path.join(get_base_path(), "bin", "yt-dlp.exe")
            if not os.path.exists(ytdlp_path):
                ytdlp_path = "yt-dlp"  # Fallback auf PATH
        else:
            ytdlp_path = "yt-dlp"
        cmd = [ytdlp_path]
        flags = self.config.get("ytdlp_flags", {})
        is_audio = preset.get("is_audio", False)
        # Login-Optionen
        if preset.get("username"):
            cmd.extend(["-u", preset["username"]])
        if preset.get("password"):
            cmd.extend(["-p", preset["password"]])
        # Referer
        if preset.get("referer"):
            cmd.append(f"--referer={preset['referer']}")
        # HLS-ffmpeg
        if preset.get("hls_ffmpeg"):
            cmd.extend(["--downloader", "ffmpeg", "--hls-use-mpegts"])
        # Untertitel
        if preset.get("sublang"):
            cmd.extend(["--sub-lang", preset["sublang"]])
        if preset.get("embed_subs"):
            cmd.append("--embed-subs")
        if preset.get("subformat"):
            cmd.extend(["--convert-subs", preset["subformat"]])
        if flags.get("ignore_config"):
            cmd.append("--ignore-config")
        if flags.get("remux_mkv") and not is_audio:
            cmd.extend(["--remux-video", "mkv"])
        if flags.get("embed_metadata"):
            cmd.append("--embed-metadata")
        if preset["args"]:
            args = []
            in_quotes = False
            current_arg = ""
            for char in preset["args"]:
                if char == '"' or char == "'":
                    in_quotes = not in_quotes
                    current_arg += char
                elif char.isspace() and not in_quotes:
                    if current_arg:
                        args.append(current_arg)
                        current_arg = ""
                else:
                    current_arg += char
            if current_arg:
                args.append(current_arg)
            cmd.extend(args)
        output_dir = self.config["output_dir"]
        output_filename = self.get_output_filename(preset)
        if output_dir:
            output_path = os.path.join(output_dir, output_filename)
            cmd.extend(["-o", output_path])
        else:
            cmd.extend(["-o", output_filename])
        url = self.url_input.text() or "[URL]"
        cmd.append(url)
        formatted_cmd = []
        for arg in cmd:
            if " " in arg and not (arg.startswith('"') and arg.endswith('"')) and not (arg.startswith("'") and arg.endswith("'")):
                formatted_cmd.append(f'"{arg}"')
            else:
                formatted_cmd.append(arg)
        self.cmd_preview.setText(" ".join(formatted_cmd))
    
    def start_download(self):
        url = self.url_input.text()
        if not url:
            QMessageBox.warning(self, "Fehler", "Bitte geben Sie eine URL ein.")
            return
        preset = self.get_current_preset()
        if not preset:
            QMessageBox.warning(self, "Fehler", "Bitte wählen Sie ein Preset aus.")
            return
        output_dir = self.config["output_dir"]
        use_local_ytdlp = self.config["use_local_ytdlp"]
        flags = self.config.get("ytdlp_flags", {})
        is_audio = preset.get("is_audio", False)
        extra_args = []
        if preset.get("username"):
            extra_args.extend(["-u", preset["username"]])
        if preset.get("password"):
            extra_args.extend(["-p", preset["password"]])
        if preset.get("referer"):
            extra_args.append(f"--referer={preset['referer']}")
        if preset.get("hls_ffmpeg"):
            extra_args.extend(["--downloader", "ffmpeg", "--hls-use-mpegts"])
        if preset.get("sublang"):
            extra_args.extend(["--sub-lang", preset["sublang"]])
        if preset.get("embed_subs"):
            extra_args.append("--embed-subs")
        if preset.get("subformat"):
            extra_args.extend(["--convert-subs", preset["subformat"]])
        if flags.get("ignore_config"):
            extra_args.append("--ignore-config")
        if flags.get("remux_mkv") and not is_audio:
            extra_args.extend(["--remux-video", "mkv"])
        if flags.get("embed_metadata"):
            extra_args.append("--embed-metadata")
        full_args = (" ".join(extra_args) + " " + preset["args"]).strip() if extra_args else preset["args"]
        self.save_config()
        self.log_output.clear()
        self.log_output.append(f"Starte Download von: {url}")
        output_filename = None
        if preset.get("has_series_template", False):
            output_filename = self.get_output_filename(preset)
            self.log_output.append(f"Verwende Ausgabedateiname: {output_filename}")
        self.log_output.append("Download-Parameter:")
        self.log_output.append(f"URL: {url}")
        self.log_output.append(f"Ausgabeverzeichnis: {output_dir}")
        self.log_output.append(f"Argumente: {preset['args']}")
        self.log_output.append(f"Lokales yt-dlp: {use_local_ytdlp}")
        self.log_output.append(f"yt-dlp-Flags: {flags}")
        self.log_output.append(f"Audio-Preset: {is_audio}")
        self.log_output.append(f"Benutzername: {preset.get('username','')}")
        self.log_output.append(f"Referer: {preset.get('referer','')}")
        self.log_output.append(f"HLS-ffmpeg: {preset.get('hls_ffmpeg',False)}")
        self.log_output.append(f"Untertitelsprache: {preset.get('sublang','')}")
        self.log_output.append(f"Embed-Subs: {preset.get('embed_subs',False)}")
        self.log_output.append(f"Untertitel-Format: {preset.get('subformat','')}")
        self.log_output.append(f"Ausgabedateiname: {output_filename}")
        self.download_thread = DownloadThread(
            url=url,
            output_dir=output_dir,
            cmd_args=full_args,
            use_local_ytdlp=use_local_ytdlp,
            output_filename=output_filename
        )
        self.download_thread.update_signal.connect(self.update_log)
        self.download_thread.finished_signal.connect(self.download_finished)
        self.download_btn.setEnabled(False)
        self.download_thread.start()
    
    def update_log(self, text):
        self.log_output.append(text)
        # Scroll zum Ende
        scrollbar = self.log_output.verticalScrollBar()
        scrollbar.setValue(scrollbar.maximum())
    
    def download_finished(self, success, message):
        self.download_btn.setEnabled(True)
        
        if success:
            self.log_output.append(message)
            QMessageBox.information(self, "Erfolg", message)
        else:
            self.log_output.append(f"Fehler: {message}")
            QMessageBox.warning(self, "Fehler", message)
            
    def closeEvent(self, event):
        self.save_config()
        event.accept()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_()) 