"""
main_window.py — Linedance afspiller hovedvindue.
"""
import logging
logger = logging.getLogger(__name__)

from PyQt6.QtWidgets import (
    QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QSlider, QLabel, QFrame, QSplitter,
    QSizePolicy, QMenuBar, QMenu, QStatusBar, QFileDialog,
    QMessageBox,
)
from PyQt6.QtCore import Qt, QTimer, QThread
from PyQt6.QtGui import QAction
from pathlib import Path

from ui.vu_meter         import VUMeter
from ui.playlist_panel   import PlaylistPanel
from ui.library_panel    import LibraryPanel
from ui.themes           import apply_theme
from ui.scan_worker      import ScanWorker
from ui.login_dialog     import LoginDialog, API_URL
from ui.playlist_manager import PlaylistManagerDialog
from ui.settings_dialog  import SettingsDialog, load_settings
from player.player       import Player


class ProgressBar(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._fraction = 0.0
        self._demo_fraction = 0.0       # hvor musikken stopper (blå)
        self._demo_fade_fraction = 0.0  # hvor fade slutter (grå)
        self.setFixedHeight(10)
        self.setCursor(Qt.CursorShape.PointingHandCursor)

    def set_fraction(self, f: float):
        self._fraction = max(0.0, min(1.0, f))
        self.update()

    def set_demo_marker(self, demo_f: float, fade_f: float = 0.0):
        self._demo_fraction      = max(0.0, min(1.0, demo_f))
        self._demo_fade_fraction = max(0.0, min(1.0, fade_f))
        self.update()

    def paintEvent(self, event):
        from PyQt6.QtGui import QPainter, QColor
        p = QPainter(self)
        w, h = self.width(), self.height()
        p.fillRect(0, 0, w, h, QColor("#2c3038"))
        fill_w = int(w * self._fraction)
        if fill_w > 0:
            p.fillRect(0, 0, fill_w, h, QColor("#e8a020"))
        # Fade-slut markør (grå) — vises bag demo-markøren
        if self._demo_fade_fraction > 0:
            fx = int(w * self._demo_fade_fraction)
            p.fillRect(fx - 1, 0, 2, h, QColor("#6a7080"))
        # Demo-stop markør (blå)
        if self._demo_fraction > 0:
            mx = int(w * self._demo_fraction)
            p.fillRect(mx - 1, 0, 2, h, QColor("#3b8fd4"))
        p.end()

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            fraction = event.position().x() / self.width()
            mw = self.window()
            if hasattr(mw, "_on_seek"):
                mw._on_seek(fraction)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("LineDance Player")
        self.setMinimumSize(1000, 680)
        self.resize(1600, 820)

        self._dark_theme = True
        self._player = Player(self)
        # Preview-afspiller til bibliotek (høretelefoner)
        from player.player import PreviewPlayer
        self._preview_player = PreviewPlayer(self)
        self._current_idx = -1
        self._song_ended = False
        self._demo_active = False
        self._watcher      = None
        self._scan_workers = []   # Hold referencer til aktive scan-tråde
        self._scan_worker = None
        self._api_url: str | None = None
        self._api_token: str | None = None
        self._api_username: str | None = None

        # Indlæs indstillinger
        self._settings = load_settings()
        self._dark_theme = self._settings.get("dark_theme", True)
        self._demo_seconds = self._settings.get("demo_seconds", 10)
        self._demo_fade_seconds = self._settings.get("demo_fade_seconds", 5)

        self._connect_player_signals()
        self._library_loaded.connect(self._apply_library)
        self._db_ready.connect(self._on_db_ready)
        self._login_success_signal.connect(self._on_login_success)
        self._login_fail_signal.connect(self._on_login_fail)
        self._status_signal.connect(self._set_status)
        self._build_menu()
        self._build_ui()
        self._build_statusbar()
        apply_theme(self._app_ref(), dark=self._dark_theme)
        self._theme_btn.setText("☀  LYS TEMA" if self._dark_theme else "● MØRKT TEMA")

        # Gendan gemt vinduestørrelse og splitter-position
        self._restore_window_state()

        # Start DB og scanning ved opstart
        QTimer.singleShot(200, self._init_local_db)

        # Auto-login hvis aktiveret i indstillinger
        if self._settings.get("auto_login") and self._settings.get("password"):
            QTimer.singleShot(800, self._auto_login)

    def _app_ref(self):
        from PyQt6.QtWidgets import QApplication
        return QApplication.instance()

    def _connect_player_signals(self):
        self._player.position_changed.connect(self._on_position)
        self._player.time_changed.connect(self._on_time)
        self._player.levels_changed.connect(self._on_levels)
        self._player.song_ended.connect(self._on_song_ended)
        self._player.state_changed.connect(self._on_state_changed)

    # ── Menu ──────────────────────────────────────────────────────────────────

    def _build_menu(self):
        menubar = self.menuBar()

        # ── Filer ─────────────────────────────────────────────────────────────
        file_menu = menubar.addMenu("Filer")

        self._act_go_online = QAction("● Gå online", self)
        self._act_go_online.setShortcut("Ctrl+L")
        self._act_go_online.triggered.connect(self._go_online)
        file_menu.addAction(self._act_go_online)

        self._act_sync = QAction("↕  Synkroniser nu", self)
        self._act_sync.setShortcut("Ctrl+Shift+S")
        self._act_sync.triggered.connect(self._manual_sync)
        file_menu.addAction(self._act_sync)

        file_menu.addSeparator()

        act_settings = QAction("Indstillinger...", self)
        act_settings.setShortcut("Ctrl+,")
        act_settings.triggered.connect(self._open_settings)
        file_menu.addAction(act_settings)

        file_menu.addSeparator()

        act_quit = QAction("Afslut", self)
        act_quit.setShortcut("Ctrl+Q")
        act_quit.triggered.connect(self.close)
        file_menu.addAction(act_quit)

        # ── Danse ─────────────────────────────────────────────────────────────
        dance_menu = menubar.addMenu("Danse")

        act_new_dance = QAction("Opret dans...", self)
        act_new_dance.setShortcut("Ctrl+D")
        act_new_dance.setToolTip("Opret en dans i databasen uden at knytte den til musik")
        act_new_dance.triggered.connect(self._create_dance_dialog)
        dance_menu.addAction(act_new_dance)

        # ── Ingen Danseliste- eller Visning-menu ──────────────────────────────
        # Ny/Gem/Hent ligger direkte i danseliste-panelet
        # Tema-skift ligger i topbar-knappen
        # Mapper og scan ligger i ⚙ Mapper dialogen

        # Gem reference til scan-action (bruges stadig internt)
        self._act_scan = QAction("Scan", self)
        self._act_scan.triggered.connect(self.start_scan)

    # ── Statuslinje ───────────────────────────────────────────────────────────

    def _build_statusbar(self):
        self._statusbar = QStatusBar()
        self.setStatusBar(self._statusbar)
        self._statusbar.showMessage("Klar")

        # Versionsnummer permanent til højre
        try:
            from main import APP_VERSION
        except Exception:
            APP_VERSION = "0.8.1"
        version_lbl = QLabel(f"v{APP_VERSION}")
        version_lbl.setObjectName("result_count")
        version_lbl.setContentsMargins(0, 0, 8, 0)
        self._statusbar.addPermanentWidget(version_lbl)

    def _set_status(self, text: str, timeout_ms: int = 0):
        """Vis besked i statuslinjen. timeout_ms=0 = permanent."""
        self._statusbar.showMessage(text, timeout_ms)

    # ── UI byggeri ────────────────────────────────────────────────────────────

    def _build_ui(self):
        root = QWidget()
        root.setObjectName("root")
        self.setCentralWidget(root)
        main_layout = QVBoxLayout(root)
        main_layout.setContentsMargins(10, 6, 10, 10)
        main_layout.setSpacing(4)

        main_layout.addWidget(self._build_topbar())
        main_layout.addWidget(self._build_now_playing())
        main_layout.addWidget(self._build_progress())
        main_layout.addWidget(self._build_transport())
        main_layout.addWidget(self._build_panels(), stretch=1)

    def _build_topbar(self) -> QFrame:
        bar = QFrame()
        bar.setObjectName("topbar")
        layout = QHBoxLayout(bar)
        layout.setContentsMargins(12, 6, 12, 6)

        logo = QLabel("LINE<span style='color:#9aa0b0;font-weight:400'>DANCE</span> PLAYER")
        logo.setObjectName("logo")
        logo.setTextFormat(Qt.TextFormat.RichText)
        layout.addWidget(logo)
        layout.addStretch()

        self._conn_label = QLabel("● OFFLINE")
        self._conn_label.setObjectName("conn_label")
        layout.addWidget(self._conn_label)

        self._theme_btn = QPushButton("☀  LYS TEMA")
        self._theme_btn.setFixedHeight(26)
        self._theme_btn.clicked.connect(self._toggle_theme)
        layout.addWidget(self._theme_btn)

        return bar

    def _build_now_playing(self) -> QFrame:
        frame = QFrame()
        frame.setObjectName("now_playing_frame")
        layout = QHBoxLayout(frame)
        layout.setContentsMargins(12, 10, 12, 10)

        track_frame = QFrame()
        track_frame.setObjectName("track_display")
        track_layout = QVBoxLayout(track_frame)
        track_layout.setContentsMargins(10, 8, 10, 8)
        track_layout.setSpacing(3)

        self._lbl_title = QLabel("—")
        self._lbl_title.setObjectName("track_title")
        track_layout.addWidget(self._lbl_title)

        self._lbl_meta = QLabel("—")
        self._lbl_meta.setObjectName("track_meta")
        track_layout.addWidget(self._lbl_meta)

        self._lbl_dances = QLabel("")
        self._lbl_dances.setObjectName("track_meta")
        self._lbl_dances.setWordWrap(True)
        track_layout.addWidget(self._lbl_dances)

        layout.addWidget(track_frame, stretch=1)

        self._vu = VUMeter()
        layout.addWidget(self._vu)

        return frame

    def _build_progress(self) -> QFrame:
        frame = QFrame()
        frame.setObjectName("progress_frame")
        layout = QHBoxLayout(frame)
        layout.setContentsMargins(12, 6, 12, 6)
        layout.setSpacing(8)

        self._lbl_cur = QLabel("0:00")
        self._lbl_cur.setObjectName("track_meta")
        self._lbl_cur.setFixedWidth(36)
        layout.addWidget(self._lbl_cur)

        self._progress = ProgressBar(self)
        self._progress.setSizePolicy(
            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
        )
        layout.addWidget(self._progress, stretch=1)

        self._lbl_tot = QLabel("0:00")
        self._lbl_tot.setObjectName("track_meta")
        self._lbl_tot.setFixedWidth(36)
        self._lbl_tot.setAlignment(Qt.AlignmentFlag.AlignRight)
        layout.addWidget(self._lbl_tot)

        return frame

    def _build_transport(self) -> QFrame:
        frame = QFrame()
        frame.setObjectName("transport_frame")
        layout = QHBoxLayout(frame)
        layout.setContentsMargins(14, 10, 14, 10)
        layout.setSpacing(8)

        def btn(text, name=None, size=52, checkable=False):
            b = QPushButton(text)
            if name:
                b.setObjectName(name)
            b.setFixedSize(size, size)
            if checkable:
                b.setCheckable(True)
            return b

        self._btn_play   = btn("▶", "btn_play", size=72)
        self._btn_stop   = btn("■", "btn_stop", size=72)
        self._btn_demo   = btn(f"▶\n{self._demo_seconds} SEK", "btn_demo", size=64, checkable=True)

        self._btn_play.clicked.connect(self._toggle_play)
        self._btn_stop.clicked.connect(self._stop)
        self._btn_demo.clicked.connect(self._toggle_demo)

        layout.addWidget(self._btn_play)
        layout.addWidget(self._btn_stop)

        layout.addSpacing(24)

        sep1 = QFrame()
        sep1.setFrameShape(QFrame.Shape.VLine)
        sep1.setFixedWidth(1)
        layout.addWidget(sep1)

        layout.addSpacing(24)

        layout.addWidget(self._btn_demo)
        layout.addStretch()

        lbl_vol = QLabel("VOL")
        lbl_vol.setObjectName("vol_label")
        layout.addWidget(lbl_vol)

        self._vol_slider = QSlider(Qt.Orientation.Horizontal)
        self._vol_slider.setRange(0, 100)
        self._vol_slider.setValue(self._settings.get("volume", 78))
        self._vol_slider.setFixedWidth(160)
        self._vol_slider.setFixedHeight(36)
        self._vol_slider.setObjectName("vol_slider")
        self._vol_slider.valueChanged.connect(self._on_volume)
        layout.addWidget(self._vol_slider)

        self._lbl_vol = QLabel(str(self._settings.get("volume", 78)))
        self._lbl_vol.setObjectName("vol_val")
        layout.addWidget(self._lbl_vol)

        return frame

    def _build_panels(self) -> QSplitter:
        self._splitter = QSplitter(Qt.Orientation.Horizontal)

        self._playlist_panel = PlaylistPanel()
        self._playlist_panel.song_selected.connect(self._load_song_by_idx)
        self._playlist_panel.song_dropped.connect(self._on_song_dropped)
        self._playlist_panel.event_started.connect(self._on_event_started)
        self._playlist_panel.next_song_ready.connect(self._on_next_song_ready)
        self._playlist_panel.playlist_changed.connect(self._on_playlist_changed)
        self._playlist_panel.sync_requested.connect(self._manual_sync)

        # Debounce-timer til auto-sync — starter sync 5 sek efter sidst ændring
        self._sync_debounce = QTimer(self)
        self._sync_debounce.setSingleShot(True)
        self._sync_debounce.setInterval(5000)
        self._sync_debounce.timeout.connect(self._auto_sync)

        # Periodisk sync — kører hvert 10. minut
        self._sync_periodic = QTimer(self)
        self._sync_periodic.setInterval(10 * 60 * 1000)
        self._sync_periodic.timeout.connect(self._manual_sync)
        self._sync_periodic.start()

        self._library_panel = LibraryPanel()
        self._library_panel.set_preview_player(self._preview_player)

        # Sæt audio devices fra indstillinger
        main_device    = self._settings.get("audio_device_main", "")
        preview_device = self._settings.get("audio_device_preview", "")
        if main_device:
            self._player.set_audio_device(main_device)
        if preview_device:
            self._preview_player.set_audio_device(preview_device)
        self._library_panel.song_selected.connect(self._on_library_song_selected)
        self._library_panel.add_to_playlist.connect(self._add_song_to_playlist)
        self._library_panel.scan_requested.connect(self.start_scan)
        self._library_panel.sync_requested.connect(self._manual_sync)
        self._library_panel.edit_tags_requested.connect(self._open_tag_editor)
        self._library_panel.send_mail_requested.connect(self._send_mail)

        self._splitter.addWidget(self._playlist_panel)
        self._splitter.addWidget(self._library_panel)
        self._splitter.setSizes([700, 900])

        return self._splitter

    def _restore_window_state(self):
        from PyQt6.QtCore import QSettings, QByteArray
        settings = QSettings("LineDance", "Player")
        geom = settings.value("window/geometry")
        if geom:
            self.restoreGeometry(geom)
        splitter_state = settings.value("window/splitter")
        if splitter_state and hasattr(self, "_splitter"):
            self._splitter.restoreState(splitter_state)

    def _save_window_state(self):
        from PyQt6.QtCore import QSettings
        settings = QSettings("LineDance", "Player")
        settings.setValue("window/geometry", self.saveGeometry())
        if hasattr(self, "_splitter"):
            settings.setValue("window/splitter", self._splitter.saveState())

    # ── Lokal DB + scanning ───────────────────────────────────────────────────

    def _init_local_db(self):
        # Debounce-timer til reload (skal oprettes i GUI-tråden)
        self._reload_timer = QTimer(self)
        self._reload_timer.setSingleShot(True)
        self._reload_timer.setInterval(2000)
        self._reload_timer.timeout.connect(self._reload_library)

        # Kør init_db i baggrundstråd — blokerer ikke GUI
        import threading
        threading.Thread(target=self._init_db_background, daemon=True).start()

    def _init_db_background(self):
        """Kører i baggrundstråd — initialiserer DB og loader bibliotek."""
        try:
            from local.local_db import init_db
            init_db()
            self._db_ready.emit()
        except Exception as e:
            pass

    def _start_watcher(self):
        """Start fil-watcher i baggrundstråd — blokerer aldrig GUI."""
        import threading

        def _start():
            try:
                from local.file_watcher import get_watcher

                def on_file_change(event_type, path, song_id):
                    # Brug signal — den eneste 100% thread-safe metode på Windows
                    self._file_changed_signal.emit()

                watcher = get_watcher(on_change=on_file_change)
                watcher.start()
                self._watcher = watcher  # sæt først når den er klar
            except Exception:
                pass

        threading.Thread(target=_start, daemon=True).start()

    def start_scan(self):
        """Start fuld scanning af alle biblioteker — watcher kører i egne baggrundstråde."""
        if not self._watcher:
            self._set_status("Ingen biblioteker at scanne — tilføj en mappe først")
            return
        self._set_status("Scanner biblioteker i baggrunden...")
        self._watcher._full_scan_all()
        # Genindlæs bibliotekslisten efter et øjeblik
        QTimer.singleShot(3000, self._reload_library)

    def _on_scan_status(self, text: str):
        self._set_status(text)
        self._library_panel.update_scan_status(text)

    def _on_scan_done(self, count: int):
        self._library_panel.set_scanning(False)
        self._act_scan.setEnabled(True)
        msg = f"Scanning færdig — {count} filer gennemgået"
        self._set_status(msg, timeout_ms=5000)
        # Genindlæs biblioteket
        QTimer.singleShot(200, self._reload_library)

    # Signal til at opdatere biblioteket fra baggrundstråd
    _library_loaded       = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal(list)
    _db_ready             = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal()
    _file_changed_signal  = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal()
    _login_success_signal = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal(str)
    _login_fail_signal    = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal(str)
    _status_signal        = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal(str, int)

    def _reload_library(self):
        """Hent sange fra DB i baggrundstråd — thread-safe via signal."""
        import threading
        threading.Thread(target=self._fetch_library, daemon=True).start()

    def _fetch_library(self):
        """Kører i baggrundstråd — henter sange og sender til GUI via signal."""
        try:
            import sqlite3
            from local.local_db import DB_PATH
            conn = sqlite3.connect(str(DB_PATH))
            conn.row_factory = sqlite3.Row
            rows = conn.execute("""
                SELECT s.id, s.title, s.artist, s.album, s.bpm,
                       s.duration_sec,
                       f.id as file_id, f.local_path, f.file_format, f.file_missing,
                       GROUP_CONCAT(d.name, ',')                        AS dance_names,
                       GROUP_CONCAT(COALESCE(dl.name,''), ',')          AS dance_levels,
                       GROUP_CONCAT(COALESCE(d.choreographer,''), ',')  AS dance_choreographers,
                       GROUP_CONCAT(DISTINCT ad.name)                   AS alt_dance_names
                FROM songs s
                JOIN files f               ON f.song_id = s.id AND f.file_missing = 0
                LEFT JOIN song_dances sd   ON sd.song_id = s.id
                LEFT JOIN dances d         ON d.id = sd.dance_id
                LEFT JOIN dance_levels dl  ON dl.id = d.level_id
                LEFT JOIN song_alt_dances sad ON sad.song_id = s.id
                LEFT JOIN dances ad        ON ad.id = sad.dance_id
                GROUP BY s.id
                ORDER BY s.artist, s.title
            """).fetchall()
            conn.close()

            songs = []
            for row in rows:
                dances      = row["dance_names"].split(",")        if row["dance_names"]        else []
                levels      = row["dance_levels"].split(",")       if row["dance_levels"]       else []
                choreos     = row["dance_choreographers"].split(",") if row["dance_choreographers"] else []
                alt_dances  = row["alt_dance_names"].split(",")    if row["alt_dance_names"]    else []
                songs.append({
                    "id":                    row["id"],
                    "title":                 row["title"],
                    "artist":                row["artist"],
                    "album":                 row["album"],
                    "bpm":                   row["bpm"],
                    "duration_sec":          row["duration_sec"],
                    "file_id":               row["file_id"],
                    "local_path":            row["local_path"],
                    "file_format":           row["file_format"],
                    "file_missing":          bool(row["file_missing"]),
                    "dances":                dances,
                    "dance_levels":          levels,
                    "dance_choreographers":  choreos,
                    "alt_dances":            alt_dances,
                })
            self._library_loaded.emit(songs)
        except Exception:
            pass

    def _on_db_ready(self):
        """DB er initialiseret — indlæs bibliotek og start post-init."""
        self._reload_library()
        self._post_init()

    def _apply_library(self, songs: list):
        self._library_panel.load_songs(songs)
        count = len(songs)
        self._set_status(
            f"Bibliotek: {count} sang{'e' if count != 1 else ''}", 3000
        )

    def _post_init(self):
        """Kør efter DB er initialiseret — gendan state og start scan."""
        try:
            restored = self._playlist_panel.restore_active_playlist()
            if restored:
                # Hent den sang der er klar (current_idx sat af restore)
                idx  = self._playlist_panel._current_idx
                song = self._playlist_panel.get_song(idx)

                if self._playlist_panel.restore_event_state():
                    # Event var i gang — genoptag
                    idx  = self._playlist_panel._current_idx
                    song = self._playlist_panel.get_song(idx)
                    if song:
                        self._current_idx = idx
                        self._song_ended  = False
                        self._load_song(song)
                        self._playlist_panel.set_current(idx)
                        self._set_status(
                            f"Event genoptaget ved: {song.get('title','')} — tryk ▶",
                            6000,
                        )
                elif song:
                    # Normal opstart — load første sang klar
                    self._current_idx = idx
                    self._song_ended  = False
                    self._load_song(song)
                    self._playlist_panel.set_current(idx)
                    self._set_status(
                        f"Klar: {song.get('title','')} — tryk ▶ for at starte",
                        4000,
                    )
        except Exception:
            pass

        QTimer.singleShot(5000, self.start_background_scan)
        # Start AcoustID fingerprinting efter 10 sekunder hvis aktiveret
        acoustid_on = self._settings.get("acoustid_enabled", False)
        logger.info(f"AcoustID indstilling: {acoustid_on}")
        if acoustid_on:
            QTimer.singleShot(10000, self._start_acoustid)

    def _start_acoustid(self):
        """Start AcoustID fingerprinting i baggrunden."""
        from local.acoustid_worker import AcoustIDWorker, find_fpcalc
        from local.local_db import DB_PATH

        if not find_fpcalc():
            logger.info("AcoustID: fpcalc ikke fundet — springer over")
            return

        if not hasattr(self, "_acoustid_worker"):
            self._acoustid_worker = AcoustIDWorker(str(DB_PATH))

        if self._acoustid_worker.is_running():
            return

        def on_progress(done, total, title):
            if total > 0 and done % 10 == 0:
                self._set_status(
                    f"AcoustID: {done}/{total} — {title}", 3000
                )

        self._acoustid_worker.start(
            api_key=self._settings.get("acoustid_api_key", ""),
            on_progress=on_progress,
        )
        self._set_status("AcoustID fingerprinting startet i baggrunden", 4000)

    def start_background_scan(self):
        """Start scanning af alle aktive biblioteker i baggrunden."""
        try:
            import sqlite3
            from local.local_db import DB_PATH
            from ui.scan_worker import ScanWorker

            conn = sqlite3.connect(str(DB_PATH))
            conn.row_factory = sqlite3.Row
            libs = conn.execute(
                "SELECT id, path FROM libraries WHERE is_active=1"
            ).fetchall()
            conn.close()

            pending = [lib for lib in libs if Path(lib["path"]).exists()]
            if not pending:
                return

            self._set_status("Scanner biblioteker i baggrunden...", 4000)
            self._scan_workers = []
            finished_count = [0]

            def on_one_finished(count, p):
                finished_count[0] += 1
                self._set_status(f"Scanning færdig — {count} filer", 4000)
                # Ryd færdige workers ud
                self._scan_workers = [w for w in self._scan_workers
                                      if w.isRunning()]

            for lib in pending:
                worker = ScanWorker(lib["id"], lib["path"], str(DB_PATH),
                                    overwrite_bpm=False)
                worker.finished.connect(on_one_finished)
                worker.start()
                worker.setPriority(QThread.Priority.LowestPriority)
                self._scan_workers.append(worker)

        except Exception:
            pass

    def add_library_path(self, path: str):
        try:
            if not self._watcher:
                self._set_status("Watcher ikke klar endnu — prøv igen om et øjeblik", 3000)
                return
            self._watcher.add_library(path)
            self._set_status(f"Tilføjet: {path} — scanner i baggrunden...")
            # Genindlæs bibliotekslisten efter kort pause
            QTimer.singleShot(800, self._reload_library)
        except Exception as e:
            self._set_status(f"Fejl ved tilføjelse: {e}")

    def _create_dance_dialog(self):
        """Opret en dans i databasen — fritliggende, uden tilknytning til musik."""
        from PyQt6.QtWidgets import (
            QDialog, QVBoxLayout, QFormLayout, QLineEdit,
            QComboBox, QDialogButtonBox, QMessageBox
        )
        try:
            from local.local_db import get_dance_levels, get_or_create_dance
        except Exception as e:
            QMessageBox.warning(self, "Fejl", str(e))
            return

        levels = [dict(r) for r in get_dance_levels()]

        dlg = QDialog(self)
        dlg.setWindowTitle("Opret dans")
        dlg.setFixedWidth(380)
        layout = QVBoxLayout(dlg)
        layout.setSpacing(8)
        layout.setContentsMargins(14, 14, 14, 14)

        form = QFormLayout()
        form.setSpacing(8)

        name_edit = QLineEdit()
        name_edit.setPlaceholderText("f.eks. Cowboy Cha Cha")
        form.addRow("Dans-navn:", name_edit)

        level_cb = QComboBox()
        level_cb.addItem("— intet niveau —", None)
        for lvl in levels:
            level_cb.addItem(lvl["name"], lvl["id"])
        form.addRow("Niveau:", level_cb)

        choreo_edit = QLineEdit()
        choreo_edit.setPlaceholderText("Koreografens navn (valgfri)")
        form.addRow("Koreograf:", choreo_edit)

        layout.addLayout(form)

        btns = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok |
            QDialogButtonBox.StandardButton.Cancel
        )
        btns.accepted.connect(dlg.accept)
        btns.rejected.connect(dlg.reject)
        layout.addWidget(btns)

        name_edit.setFocus()
        if dlg.exec():
            name   = name_edit.text().strip()
            choreo = choreo_edit.text().strip()
            level  = level_cb.currentData()
            if name:
                try:
                    get_or_create_dance(name, level, choreographer=choreo)
                    self._set_status(f'Dans "{name}" oprettet', 3000)
                except Exception as e:
                    QMessageBox.warning(self, "Fejl", f"Kunne ikke oprette dans:\n{e}")

    def _open_settings(self):
        dialog = SettingsDialog(parent=self)
        if dialog.exec():
            self._settings = dialog.get_values()
            self._demo_seconds = self._settings.get("demo_seconds", 10)
            self._demo_fade_seconds = self._settings.get("demo_fade_seconds", 5)
            # Opdater tema hvis ændret
            new_dark = self._settings.get("dark_theme", True)
            if new_dark != self._dark_theme:
                self._dark_theme = new_dark
                apply_theme(self._app_ref(), dark=self._dark_theme)
                self._theme_btn.setText(
                    "☀  LYS TEMA" if self._dark_theme else "● MØRKT TEMA"
                )
                self._vu.set_dark(self._dark_theme)
            # Opdater demo-knap tekst
            self._btn_demo.setText(f"▶\n{self._demo_seconds} SEK")
            # Opdater demo-markør hvis en sang er indlæst
            if hasattr(self, "_current_song") and self._current_song:
                dur = self._current_song.get("duration_sec", 0)
                if dur > 0:
                    self._progress.set_demo_marker(min(self._demo_seconds / dur, 1.0), min((self._demo_seconds + self._demo_fade_seconds) / dur, 1.0))
            self._set_status("Indstillinger gemt", 2000)
            # Anvend lydenheder med det samme
            main_device    = self._settings.get("audio_device_main", "")
            preview_device = self._settings.get("audio_device_preview", "")
            if main_device:
                self._player.set_audio_device(main_device)
            if preview_device:
                self._preview_player.set_audio_device(preview_device)

    def _auto_login(self):
        """Forsøg automatisk login med gemte oplysninger."""
        username   = self._settings.get("username", "")
        password   = self._settings.get("password", "")
        server_url = self._settings.get("server_url", "http://localhost:8000").rstrip("/")
        if not username or not password:
            return

        def _run():
            try:
                import urllib.request, urllib.parse, json
                data = urllib.parse.urlencode({"username": username, "password": password}).encode()
                req = urllib.request.Request(
                    f"{server_url}/auth/login", data=data,
                    headers={"Content-Type": "application/x-www-form-urlencoded"},
                    method="POST",
                )
                with urllib.request.urlopen(req, timeout=8) as resp:
                    body = json.loads(resp.read())
                    self._api_token    = body.get("access_token")
                    self._api_url      = server_url
                    self._api_username = username
                    # Kald GUI-opdatering via signal — thread-safe
                    self._login_success_signal.emit(username)
            except Exception as e:
                self._login_fail_signal.emit(str(e))

        import threading
        threading.Thread(target=_run, daemon=True).start()

    def _on_playlist_changed(self):
        """Danseliste ændret — start debounce-timer til auto-sync og opdater live-status."""
        if hasattr(self, "_sync_debounce"):
            self._sync_debounce.start()
        # Opdater storskærm med det samme
        self._sync_event_status_to_playlist()

    def _auto_sync(self):
        """Kør sync hvis vi er online — kaldes af debounce-timer."""
        if not self._api_token:
            return
        if not hasattr(self, "_sync_manager") or not self._sync_manager:
            return
        self._sync_manager.push(
            on_done=lambda r: self._status_signal.emit(
                f"↑ Synkroniseret — {r.get('songs_synced', 0)} sange", 3000
            ),
            on_error=lambda e: self._status_signal.emit(
                f"⚠ Sync fejl: {e}", 8000
            ),
        )

    def _on_next_song_ready(self, song: dict):
        """Næste sang er klar — load den i afspilleren og markér orange."""
        idx = self._playlist_panel._current_idx
        self._current_idx = idx
        self._song_ended  = False
        self._playlist_panel._song_ended = False
        self._load_song(song)
        self._playlist_panel.set_current(idx)

    def _on_login_success(self, username: str):
        """Kaldes i GUI-tråden når login lykkes."""
        self._set_online_state(True)
        self._set_status(f"Logget ind som {username}", 4000)

    def _on_login_fail(self, error: str):
        """Kaldes i GUI-tråden når login fejler."""
        self._set_status(f"Login fejlede: {error}", 5000)

    def _go_online(self):
        """Log ind/ud med gemte credentials."""
        if self._api_token:
            self._go_offline()
            return
        username   = self._settings.get("username", "")
        password   = self._settings.get("password", "")
        server_url = self._settings.get("server_url", "http://localhost:8000").rstrip("/")
        if not username or not password:
            self._set_status("Udfyld brugernavn og kodeord i Indstillinger → Online", 5000)
            return

        def _run():
            try:
                import urllib.request, urllib.parse, json
                data = urllib.parse.urlencode({"username": username, "password": password}).encode()
                req = urllib.request.Request(
                    f"{server_url}/auth/login", data=data,
                    headers={"Content-Type": "application/x-www-form-urlencoded"},
                    method="POST",
                )
                with urllib.request.urlopen(req, timeout=8) as resp:
                    body = json.loads(resp.read())
                    self._api_token    = body.get("access_token")
                    self._api_url      = server_url
                    self._api_username = username
                    self._login_success_signal.emit(username)
            except Exception as e:
                self._login_fail_signal.emit(str(e))

        import threading
        threading.Thread(target=_run, daemon=True).start()

    def _sync_dance_data(self):
        """Synkroniser dans-niveauer og navne fra API."""
        if not self._api_token:
            return
        try:
            import urllib.request, json
            headers = {"Authorization": f"Bearer {self._api_token}"}

            # Hent niveauer
            req = urllib.request.Request(f"{API_URL}/dances/levels", headers=headers)
            with urllib.request.urlopen(req, timeout=8) as resp:
                levels = json.loads(resp.read())
            from local.local_db import sync_dance_levels_from_api
            sync_dance_levels_from_api(levels)

            # Hent populære dans-navne
            req = urllib.request.Request(f"{API_URL}/dances/names?limit=500", headers=headers)
            with urllib.request.urlopen(req, timeout=8) as resp:
                names = json.loads(resp.read())
            from local.local_db import sync_dance_names_from_api
            sync_dance_names_from_api(names)

            self._set_status(f"Synkroniseret {len(levels)} niveauer og {len(names)} dans-navne", 4000)
        except Exception as e:
            pass

    def _go_offline(self):
        self._api_url = self._api_token = self._api_username = None
        self._set_online_state(False)
        self._set_status("Offline — arbejder lokalt", 3000)

    def _set_online_state(self, online: bool):
        if online:
            name = self._api_username or "?"
            self._conn_label.setText(f"● ONLINE  ({name})")
            self._conn_label.setStyleSheet("color: #2ecc71;")
            self._act_go_online.setText("● Gå offline")
            self._init_sync()
        else:
            self._conn_label.setText("● OFFLINE")
            self._conn_label.setStyleSheet("color: #5a6070;")
            self._act_go_online.setText("● Gå online")
            self._sync_manager = None

    def _init_sync(self):
        """Opret SyncManager og kør initial push+pull."""
        try:
            from local.local_db import DB_PATH
            from local.sync_manager import SyncManager
            server_url = self._settings.get("server_url", "http://localhost:8000")
            self._sync_manager = SyncManager(
                db_path=str(DB_PATH),
                server_url=server_url,
                token=self._api_token,
            )
            self._sync_manager.push_and_pull(
                on_done=lambda r: self._status_signal.emit(
                    f"✓ Synkroniseret — {r['push']['songs_synced']} sange", 5000
                ),
                on_error=lambda e: self._status_signal.emit(
                    f"⚠ Sync fejl: {e}", 5000
                ),
            )
        except Exception as e:
            self._set_status(f"⚠ Sync fejl: {e}", 5000)

    def _manual_sync(self):
        import logging
        log = logging.getLogger(__name__)
        log.info(f"Manuel sync — token: {'ja' if self._api_token else 'NEJ'}, manager: {'ja' if hasattr(self, '_sync_manager') and self._sync_manager else 'NEJ'}")
        if not self._api_token:
            self._set_status("Log ind for at synkronisere", 3000)
            return
        if not hasattr(self, "_sync_manager") or not self._sync_manager:
            log.info("Ingen sync_manager — kalder _init_sync")
            self._init_sync()
            return
        self._set_status("Synkroniserer...", 2000)
        self._sync_manager.push_and_pull(
            on_done=lambda r: self._status_signal.emit(
                f"✓ Synkroniseret — {r['push']['songs_synced']} sange", 4000
            ),
            on_error=lambda e: self._status_signal.emit(
                f"⚠ Sync fejl: {e}", 5000
            ),
        )

    def _new_playlist(self):
        self._stop()
        self._playlist_panel.load_songs([])
        self._playlist_panel.set_playlist_name("Ny liste")
        self._set_status("Ny danseliste oprettet", 2000)

    def _open_playlist_manager(self):
        dialog = PlaylistManagerDialog(
            current_songs=self._playlist_panel.get_songs(),
            parent=self,
        )
        dialog.playlist_loaded.connect(self._on_playlist_loaded)
        dialog.exec()

    def _on_playlist_loaded(self, name: str, songs: list[dict], pl_id: int):
        self._stop()
        self._playlist_panel.load_songs(songs)
        self._playlist_panel.set_playlist_name(name)
        if pl_id:
            self._playlist_panel._named_playlist_id = pl_id
            self._playlist_panel._save_named_playlist_id(pl_id)
            self._playlist_panel._btn_save_current.setEnabled(True)
            self._playlist_panel._btn_save_current.setToolTip(f"Gem ændringer til '{name}'")
        self._set_status(f"Indlæst: {name} ({len(songs)} sange)", 3000)

    def _open_tag_editor(self, song: dict):
        from ui.tag_editor import TagEditorDialog
        dialog = TagEditorDialog(song, parent=self)
        if dialog.exec():
            # Genindlæs biblioteket så ændringer vises
            QTimer.singleShot(200, self._reload_library)
            # Push ændringer til server med det samme
            QTimer.singleShot(500, self._manual_sync)

    def _send_mail(self, song: dict):
        import subprocess, sys, shutil, urllib.parse
        from pathlib import Path

        path  = song.get("local_path", "")
        title = song.get("title", "")
        artist = song.get("artist", "")

        if not path or not Path(path).exists():
            self._set_status("Filen blev ikke fundet — kan ikke sende mail", 4000)
            return

        # ── Auto-detekter mailklient ───────────────────────────────────────────

        def try_thunderbird() -> bool:
            """Thunderbird: thunderbird -compose attachment='file:///sti'"""
            candidates = []
            if sys.platform == "win32":
                import winreg
                for base in (winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER):
                    try:
                        key = winreg.OpenKey(base,
                            r"SOFTWARE\Mozilla\Mozilla Thunderbird")
                        inst, _ = winreg.QueryValueEx(key, "Install Directory")
                        candidates.append(str(Path(inst) / "thunderbird.exe"))
                    except Exception:
                        pass
                candidates += [
                    r"C:\Program Files\Mozilla Thunderbird\thunderbird.exe",
                    r"C:\Program Files (x86)\Mozilla Thunderbird\thunderbird.exe",
                ]
            elif sys.platform == "darwin":
                candidates = [
                    "/Applications/Thunderbird.app/Contents/MacOS/thunderbird",
                ]
            else:
                candidates = [shutil.which("thunderbird") or "",
                              "/usr/bin/thunderbird",
                              "/usr/local/bin/thunderbird",
                              "/snap/bin/thunderbird"]

            tb = next((c for c in candidates if c and Path(c).exists()), None)
            if not tb:
                return False

            file_uri = Path(path).as_uri()
            subject  = f"Linedance sang: {title} — {artist}"
            compose  = (
                f"subject='{subject}',"
                f"attachment='{file_uri}'"
            )
            subprocess.Popen([tb, "-compose", compose])
            return True

        def try_outlook() -> bool:
            """Outlook: outlook.exe /a 'filsti'  (kun Windows)"""
            if sys.platform != "win32":
                return False
            candidates = [
                shutil.which("outlook") or "",
                r"C:\Program Files\Microsoft Office\root\Office16\OUTLOOK.EXE",
                r"C:\Program Files (x86)\Microsoft Office\root\Office16\OUTLOOK.EXE",
                r"C:\Program Files\Microsoft Office\Office16\OUTLOOK.EXE",
            ]
            ol = next((c for c in candidates if c and Path(c).exists()), None)
            if not ol:
                return False
            subprocess.Popen([ol, "/a", path])
            return True

        def fallback_mailto():
            """Ingen vedhæftning — åbn standard-mailprogram via mailto:"""
            subject = urllib.parse.quote(f"Linedance sang: {title} — {artist}")
            body    = urllib.parse.quote(
                f"Sang: {title}\nArtist: {artist}\nFil: {path}\n\n"
                f"(Vedhæft filen manuelt fra ovenstående sti)"
            )
            mailto = f"mailto:?subject={subject}&body={body}"
            if sys.platform == "win32":
                import os; os.startfile(mailto)
            elif sys.platform == "darwin":
                subprocess.Popen(["open", mailto])
            else:
                subprocess.Popen(["xdg-open", mailto])

        # ── Prøv i rækkefølge ─────────────────────────────────────────────────
        if try_thunderbird():
            self._set_status(f"Thunderbird åbnet med {Path(path).name} vedh.", 4000)
        elif try_outlook():
            self._set_status(f"Outlook åbnet med {Path(path).name} vedh.", 4000)
        else:
            fallback_mailto()
            self._set_status(
                f"Ingen kendt mailklient fundet — åbnet mailto: (uden vedhæftning)", 5000
            )

    def _on_event_started(self):
        """Start event — indlæs første sang i afspilleren klar til afspilning."""
        first = self._playlist_panel.get_song(0)
        if not first:
            return
        self._stop()
        self._current_idx = 0
        self._song_ended = False
        self._load_song(first)
        self._set_status("Event klar — tryk ▶ for at starte", 5000)

    def _on_song_dropped(self, song: dict):
        self._set_status(f"Tilføjet: {song.get('title','')}", 2000)

    def _menu_add_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "Vælg musikmappe")
        if folder:
            self.add_library_path(folder)

    # ── Afspilning ────────────────────────────────────────────────────────────

    def _load_song(self, song: dict):
        self._current_song = song
        self._song_ended = False
        self._demo_active = False
        self._btn_demo.setChecked(False)

        dur = song.get("duration_sec", 0)
        self._player.load(song.get("local_path", ""), dur)

        # Stop preview hvis den kører
        if hasattr(self, "_preview_player") and self._preview_player.is_playing():
            self._library_panel._stop_preview()

        self._lbl_title.setText(song.get("title", "—"))
        bpm = song.get("bpm", 0)
        fmt_dur = f"{dur//60}:{dur%60:02d}"
        self._lbl_meta.setText(f"{song.get('artist','')}  ·  {bpm} BPM  ·  {fmt_dur}")

        dances = song.get("dances", [])
        self._lbl_dances.setText(
            "  ·  ".join(f"[{d}]" for d in dances) if dances else "ingen danse tagget"
        )

        if dur > 0:
            self._progress.set_demo_marker(min(self._demo_seconds / dur, 1.0), min((self._demo_seconds + self._demo_fade_seconds) / dur, 1.0))

        self._set_status(f"Indlæst: {song.get('title','—')}", 3000)

    def _load_song_by_idx(self, idx: int):
        song = self._playlist_panel.get_song(idx)
        if not song:
            return
        # Nulstil gammel markering
        old_idx = self._playlist_panel._current_idx
        if old_idx is not None and old_idx != idx:
            if 0 <= old_idx < len(self._playlist_panel._statuses):
                if self._playlist_panel._statuses[old_idx] == "playing":
                    self._playlist_panel._statuses[old_idx] = "pending"
        self._current_idx = idx
        self._load_song(song)
        self._playlist_panel.set_current(idx)

    def _toggle_play(self):
        if self._demo_active:
            self._player.stop()
            self._demo_active = False
            self._btn_demo.setChecked(False)
            self._btn_play.setText("▶")
            return
        self._waiting_for_auto = False  # annuller evt. auto-timer
        if self._player.is_playing():
            self._player.pause()
        else:
            self._song_ended = False
            self._player.play()
            self._btn_play.setText("⏸")
            self._sync_event_status_to_playlist()

    def _stop(self):
        # Annuller evt. igangværende demo_then_play
        self._demo_then_play_pending = False
        # Annuller evt. igangværende countdown
        if getattr(self, "_waiting_for_auto", False):
            self._waiting_for_auto = False
            if hasattr(self, "_countdown_timer"):
                self._countdown_timer.stop()
            self._set_status("Auto-afspilning annulleret", 3000)
            return
        self._player.stop()
        self._song_ended = False
        self._demo_active = False
        self._btn_demo.setChecked(False)
        self._btn_play.setText("▶")
        self._vu.reset()

    def _toggle_demo(self):
        if self._demo_active:
            self._player.stop()
            self._demo_active = False
            self._btn_demo.setChecked(False)
            self._btn_play.setText("▶")
        else:
            self._demo_active = True
            self._btn_demo.setChecked(True)
            self._player.play_demo(
                stop_at_sec=self._demo_seconds,
                fade_sec=self._demo_fade_seconds,
            )
            self._btn_play.setText("⏸")

    def _prev_song(self):
        if self._current_idx > 0:
            self._stop()
            self._load_song_by_idx(self._current_idx - 1)

    def _next_song(self):
        if self._current_idx < self._playlist_panel.count() - 1:
            self._stop()
            self._playlist_panel.mark_played(self._current_idx)
            self._load_song_by_idx(self._current_idx + 1)

    def _play_next(self):
        self._song_ended = False
        self._player.play()
        self._btn_play.setText("⏸")

    def _on_library_song_selected(self, song: dict):
        self._load_song(song)
        # VLC er asynkron — vent kort på at media er klar
        QTimer.singleShot(150, self._play_after_load)

    def _play_after_load(self):
        self._player.play()
        self._btn_play.setText("⏸")

    def _add_song_to_playlist(self, song: dict):
        songs = [self._playlist_panel.get_song(i)
                 for i in range(self._playlist_panel.count())]
        songs = [s for s in songs if s]
        songs.append(song)
        self._playlist_panel.load_songs(songs)
        self._set_status(f"Tilføjet til danseliste: {song.get('title','')}", 2000)

    # ── Player signals ────────────────────────────────────────────────────────

    def _on_position(self, fraction: float):
        self._progress.set_fraction(fraction)

    def _on_time(self, cur: int, tot: int):
        self._lbl_cur.setText(f"{cur//60}:{cur%60:02d}")
        self._lbl_tot.setText(f"{tot//60}:{tot%60:02d}")

    def _on_levels(self, left: float, right: float):
        self._vu.set_levels(left, right)

    def _on_song_ended(self):
        self._song_ended = True
        self._demo_active = False
        self._btn_demo.setChecked(False)
        self._btn_play.setText("▶")
        self._vu.reset()

        # Hvis demo_then_play venter på at starte sang — lad timeren klare det
        if getattr(self, "_demo_then_play_pending", False):
            return

        # Synkroniser current_idx til playlist_panel
        self._playlist_panel._current_idx = self._current_idx

        # Markér den afspillede sang
        self._playlist_panel.mark_played(self._current_idx)

        # Synkroniser event-status til den gemte navngivne liste
        self._sync_event_status_to_playlist()

        # Find næste uafspillede
        ni = self._playlist_panel.next_playable_idx()
        next_song = self._playlist_panel.get_song(ni) if ni is not None else None
        if next_song:
            self._current_idx = ni
            self._playlist_panel.set_next_ready(ni)
            self._load_song(next_song)

            mode  = self._settings.get("after_song_mode",  "manual")
            delay = self._settings.get("after_song_delay", 2)

            if mode == "manual":
                self._waiting_for_auto = False
                self._set_status(f"Klar: {next_song.get('title','')} — tryk ▶ for at starte")

            elif mode in ("auto_demo", "auto_play", "demo_then_play"):
                self._waiting_for_auto = True
                self._countdown_secs   = delay
                self._countdown_mode   = mode
                self._countdown_title  = next_song.get("title", "")
                label = "Auto-demo" if mode in ("auto_demo", "demo_then_play") else "Auto-play"
                self._set_status(f"{label} om {delay}s — {self._countdown_title}")

                if not hasattr(self, "_countdown_timer"):
                    self._countdown_timer = QTimer(self)
                    self._countdown_timer.setInterval(1000)
                    self._countdown_timer.timeout.connect(self._countdown_tick)
                self._countdown_timer.start()
        else:
            self._current_idx = -1
            self._playlist_panel._current_idx = -1
            self._playlist_panel._song_ended = False
            self._playlist_panel._refresh()
            self._sync_event_status_to_playlist()
            self._lbl_title.setText("— Danseliste afsluttet —")
            self._lbl_meta.setText("")
            self._lbl_dances.setText("")
            self._set_status("Danselisten er afsluttet")

    def _countdown_tick(self):
        """Tæller ned og starter auto-afspilning når den når 0."""
        if not getattr(self, "_waiting_for_auto", False):
            self._countdown_timer.stop()
            return
        self._countdown_secs -= 1
        label = "Auto-play" if self._countdown_mode == "auto_play" else "Auto-demo"
        if self._countdown_secs > 0:
            self._set_status(f"{label} om {self._countdown_secs}s — {self._countdown_title}")
        else:
            self._countdown_timer.stop()
            if self._countdown_mode == "auto_play":
                self._auto_play_next()
            else:
                self._auto_demo_next()  # auto_demo og demo_then_play starter begge med demo

    def _auto_demo_next(self):
        """Afspil demo af den næste klargjorte sang automatisk."""
        if not getattr(self, "_waiting_for_auto", False):
            return
        self._waiting_for_auto = False
        self._playlist_panel.set_current(self._current_idx)
        self._player.play_demo(self._demo_seconds, self._demo_fade_seconds)
        self._demo_active = True
        self._btn_demo.setChecked(True)
        self._btn_play.setText("⏸")
        self._song_ended = False

        # Hvis demo_then_play: start sangen automatisk når demo er færdig
        if getattr(self, "_countdown_mode", "") == "demo_then_play":
            delay = self._settings.get("after_song_delay", 2)
            total = self._demo_seconds + self._demo_fade_seconds + delay
            self._demo_then_play_pending = True
            QTimer.singleShot(total * 1000, self._auto_play_after_demo)

    def _auto_play_after_demo(self):
        """Start sangen efter demo er færdig (bruges af demo_then_play)."""
        self._demo_then_play_pending = False
        self._song_ended = False
        self._player.stop()
        self._demo_active = False
        self._btn_demo.setChecked(False)
        self._playlist_panel.set_current(self._current_idx)
        self._player.play()
        self._btn_play.setText("⏸")

    def _auto_play_next(self):
        """Start næste sang automatisk."""
        if not getattr(self, "_waiting_for_auto", False):
            return  # Brugeren har allerede trykket ▶ manuelt
        self._waiting_for_auto = False
        self._playlist_panel.set_current(self._current_idx)
        self._player.play()
        self._btn_play.setText("⏸")
        self._song_ended = False

    def _sync_event_status_to_playlist(self):
        """Gem event-fremgang lokalt og mini-sync til server."""
        try:
            pl_id = self._playlist_panel.get_named_playlist_id()
            if not pl_id:
                return
            songs    = self._playlist_panel.get_songs()
            statuses = self._playlist_panel.get_statuses()
            from local.local_db import get_db
            with get_db() as conn:
                for position, status in enumerate(statuses, start=1):
                    conn.execute(
                        "UPDATE playlist_songs SET status=? "
                        "WHERE playlist_id=? AND position=?",
                        (status, pl_id, position)
                    )
                # Hent server_id for denne playliste
                row = conn.execute(
                    "SELECT api_project_id FROM playlists WHERE id=?", (pl_id,)
                ).fetchone()
                server_id = row["api_project_id"] if row else None

            # Mini-sync til server hvis online
            if server_id and self._api_token:
                self._mini_sync_to_server(server_id, songs, statuses)
        except Exception:
            pass

    def _mini_sync_to_server(self, server_id: str, songs: list, statuses: list):
        """Send kun playliste-status til server — kører i baggrundstråd."""
        import threading, urllib.request, json
        url   = f"{self._api_url}/live/{server_id}/status"
        token = self._api_token

        payload = json.dumps({
            "songs": [
                {
                    "title":       s.get("title", ""),
                    "artist":      s.get("artist", ""),
                    "status":      statuses[i] if i < len(statuses) else "pending",
                    "position":    i + 1,
                    "dance":       s.get("active_dance", "") or
                                   (s.get("dances", [""])[0] if s.get("dances") else ""),
                    "duration":    s.get("duration_sec", 0),
                    "is_workshop": s.get("is_workshop", False),
                }
                for i, s in enumerate(songs)
            ]
        }).encode()

        def _push():
            try:
                req = urllib.request.Request(
                    url, data=payload, method="POST",
                    headers={
                        "Content-Type":  "application/json",
                        "Authorization": f"Bearer {token}",
                    }
                )
                urllib.request.urlopen(req, timeout=4)
            except Exception:
                pass

        threading.Thread(target=_push, daemon=True).start()

    def _on_state_changed(self, state: str):
        if state == "playing":
            self._btn_play.setText("⏸")
        elif state in ("paused", "stopped"):
            self._btn_play.setText("▶")
            if state == "stopped" and not self._song_ended:
                self._vu.reset()
        elif state == "demo_ended":
            self._demo_active = False
            self._btn_demo.setChecked(False)
            self._btn_play.setText("▶")
            self._vu.reset()

    def _on_seek(self, fraction: float):
        self._player.set_position(fraction)

    def _on_volume(self, value: int):
        self._lbl_vol.setText(str(value))
        self._player.set_volume(value)
        from ui.settings_dialog import save_settings
        self._settings["volume"] = value
        save_settings(self._settings)

    # ── Tema ──────────────────────────────────────────────────────────────────

    def _toggle_theme(self):
        self._dark_theme = not self._dark_theme
        apply_theme(self._app_ref(), dark=self._dark_theme)
        self._theme_btn.setText(
            "● MØRKT TEMA" if not self._dark_theme else "☀  LYS TEMA"
        )
        self._vu.set_dark(self._dark_theme)

    # ── Luk ───────────────────────────────────────────────────────────────────

    def closeEvent(self, event):
        self._save_window_state()
        self._player.stop()
        if self._scan_worker and self._scan_worker.isRunning():
            self._scan_worker.quit()
            self._scan_worker.wait(2000)
        # Stop scan workers
        if hasattr(self, "_scan_workers"):
            for w in self._scan_workers:
                if w.isRunning():
                    w.cancel()
        # Stop watchdog subprocess
        if hasattr(self, "_watchdog_proc") and self._watchdog_proc:
            try:
                self._watchdog_proc.terminate()
            except Exception:
                pass
        event.accept()