"""
playlist_panel.py — Danseliste med Ny/Gem/Hent knapper, autogem og event-overblik.
"""

from PyQt6.QtWidgets import (
    QWidget, QVBoxLayout, QListWidget, QListWidgetItem,
    QLabel, QHBoxLayout, QPushButton, QMenu, QAbstractItemView,
    QMessageBox, QStyledItemDelegate,
)
from PyQt6.QtCore import Qt, pyqtSignal, QTimer, QByteArray, QRect
from PyQt6.QtGui import QColor, QDragEnterEvent, QDropEvent, QPainter


ACTIVE_PLAYLIST_NAME = "__aktiv__"


class AvailabilityDelegate(QStyledItemDelegate):
    """Tegner en farvet dot til højre for hvert playlist-item."""
    def paint(self, painter, option, index):
        super().paint(painter, option, index)
        color = index.data(Qt.ItemDataRole.UserRole + 1)
        if not color:
            return
        painter.save()
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
        painter.setBrush(QColor(color))
        painter.setPen(Qt.PenStyle.NoPen)
        r = option.rect
        cx = r.right() - 10
        cy = r.center().y()
        painter.drawEllipse(cx - 5, cy - 5, 10, 10)
        painter.restore()


class PlaylistPanel(QWidget):
    song_selected    = pyqtSignal(int)
    status_changed   = pyqtSignal(int, str)
    song_dropped     = pyqtSignal(dict)
    playlist_changed = pyqtSignal()
    event_started    = pyqtSignal()
    next_song_ready  = pyqtSignal(dict)   # udsendes når næste sang ændres — main_window indlæser den     # udsendes af Start event — main_window indlæser første sang     # udsendes ved enhver ændring → trigger autogem
    sync_requested   = pyqtSignal()       # bed main_window om at køre sync (efter sletning)

    STATUS_ICON  = {"pending": "   ", "playing": " ▶ ", "played": " ✓ ", "skipped": " — ", "next": " ▷ "}
    STATUS_COLOR = {"pending": "#5a6070", "playing": "#e8a020", "played": "#2ecc71", "skipped": "#e74c3c", "next": "#3b8fd4"}

    def __init__(self, parent=None):
        super().__init__(parent)
        self._songs: list[dict] = []
        self._statuses: list[str] = []
        self._current_idx = -1
        self._song_ended = False
        self._active_playlist_id: str | None = None
        self._named_playlist_id: str | None = None   # den indlæste/gemte navngivne liste
        self._build_ui()
        self.setAcceptDrops(True)
        # Autogem-timer — venter 800ms efter sidst ændring
        self._autosave_timer = QTimer(self)
        self._autosave_timer.setSingleShot(True)
        self._autosave_timer.setInterval(800)
        self._autosave_timer.timeout.connect(self._autosave)
        # Event-state gem — hurtig, kritisk for genopstart efter strømsvigt
        self._event_state_timer = QTimer(self)
        self._event_state_timer.setSingleShot(True)
        self._event_state_timer.setInterval(300)
        self._event_state_timer.timeout.connect(self._save_event_state)

    def _build_ui(self):
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        # ── Header med titel ──────────────────────────────────────────────────
        header = QHBoxLayout()
        header.setContentsMargins(10, 6, 10, 6)
        self._title_label = QLabel("DANSELISTE")
        self._title_label.setObjectName("section_title")
        header.addWidget(self._title_label)
        layout.addLayout(header)

        # ── Ny / Gem / Hent knapper ───────────────────────────────────────────
        toolbar = QHBoxLayout()
        toolbar.setContentsMargins(8, 2, 8, 4)
        toolbar.setSpacing(4)

        btn_new = QPushButton("✚  Ny")
        btn_new.setFixedHeight(26)
        btn_new.setToolTip("Opret en ny tom danseliste")
        btn_new.clicked.connect(self._new_playlist)
        toolbar.addWidget(btn_new)

        btn_save = QPushButton("💾  Gem som...")
        btn_save.setFixedHeight(26)
        btn_save.setToolTip("Gem aktuel liste med et navn")
        btn_save.clicked.connect(self._save_as)
        toolbar.addWidget(btn_save)

        self._btn_save_current = QPushButton("💾  Gem")
        self._btn_save_current.setFixedHeight(26)
        self._btn_save_current.setToolTip("Gem ændringer til den indlæste liste")
        self._btn_save_current.clicked.connect(self._save_current)
        self._btn_save_current.setEnabled(False)
        toolbar.addWidget(self._btn_save_current)

        btn_load = QPushButton("📂  Hent...")
        btn_load.setFixedHeight(26)
        btn_load.setToolTip("Hent en tidligere gemt danseliste")
        btn_load.clicked.connect(self._load_dialog)
        toolbar.addWidget(btn_load)

        self._btn_offline_check = QPushButton("⬤  Offline tjek")
        self._btn_offline_check.setFixedHeight(26)
        self._btn_offline_check.setToolTip("Kontrollér om alle sange er tilgængelige på denne maskine")
        self._btn_offline_check.clicked.connect(self._check_offline)
        toolbar.addWidget(self._btn_offline_check)

        toolbar.addStretch()

        self._lbl_autosave = QLabel("")
        self._lbl_autosave.setObjectName("result_count")
        toolbar.addWidget(self._lbl_autosave)

        layout.addLayout(toolbar)

        # ── Event-kontrol ─────────────────────────────────────────────────────
        ctrl = QHBoxLayout()
        ctrl.setContentsMargins(8, 2, 8, 4)
        ctrl.setSpacing(6)

        self._btn_start = QPushButton("▶  START EVENT")
        self._btn_start.setFixedHeight(28)
        self._btn_start.setToolTip("Nulstil alle statusser og gør klar til event")
        self._btn_start.clicked.connect(self._start_event)
        ctrl.addWidget(self._btn_start)
        ctrl.addStretch()

        self._lbl_progress = QLabel("0 / 0")
        self._lbl_progress.setObjectName("result_count")
        ctrl.addWidget(self._lbl_progress)

        btn_info = QPushButton("ℹ")
        btn_info.setFixedSize(24, 28)
        btn_info.setToolTip("Danseliste-info: samlet tid, pause-tid m.m.")
        btn_info.clicked.connect(self._show_playlist_info)
        ctrl.addWidget(btn_info)

        layout.addLayout(ctrl)

        # Pause-tid per dans (skjult men kan vises via info)
        try:
            from ui.settings_dialog import load_settings
            s = load_settings()
            self._pause_seconds    = s.get("between_seconds",  60)
            self._workshop_seconds = s.get("workshop_minutes", 10) * 60
        except Exception:
            self._pause_seconds    = 60
            self._workshop_seconds = 600

        # ── Liste ─────────────────────────────────────────────────────────────
        self._list = QListWidget()
        self._list.setItemDelegate(AvailabilityDelegate(self._list))
        self._list.setObjectName("playlist_list")
        self._list.setDragDropMode(QAbstractItemView.DragDropMode.DragDrop)
        self._list.setDefaultDropAction(Qt.DropAction.MoveAction)
        self._list.setAcceptDrops(True)
        self._list.itemDoubleClicked.connect(self._on_double_click)
        self._list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
        self._list.customContextMenuRequested.connect(self._show_context_menu)
        self._list.model().rowsMoved.connect(self._on_rows_moved)
        layout.addWidget(self._list)

    # ── Drag & drop ───────────────────────────────────────────────────────────

    def dragEnterEvent(self, event: QDragEnterEvent):
        if event.mimeData().hasFormat("application/x-linedance-song"):
            event.acceptProposedAction()
        else:
            event.ignore()

    def dropEvent(self, event: QDropEvent):
        mime = event.mimeData()
        if mime.hasFormat("application/x-linedance-song"):
            import json
            song = json.loads(mime.data("application/x-linedance-song").data().decode())
            self._append_song(song)
            self.song_dropped.emit(song)
            event.acceptProposedAction()

    def _append_song(self, song: dict):
        self._songs.append(song)
        self._statuses.append("pending")
        self._refresh()
        self._trigger_autosave()

    # ── Data API ──────────────────────────────────────────────────────────────

    def load_songs(self, songs: list[dict], reset_statuses: bool = True, name: str = ""):
        self._songs = list(songs)
        if reset_statuses:
            self._statuses = ["pending"] * len(songs)
        self._current_idx = -1
        self._song_ended = False
        if name:
            self._title_label.setText(f"DANSELISTE — {name.upper()}")
        self._check_availability(self._songs)
        self._refresh()
        self._trigger_autosave()

    def set_current(self, idx: int, song_ended: bool = False):
        self._current_idx = idx
        self._song_ended = song_ended
        if 0 <= idx < len(self._statuses) and not song_ended:
            self._statuses[idx] = "playing"
        self._refresh()
        self._scroll_to(idx)

    def mark_played(self, idx: int):
        if 0 <= idx < len(self._statuses):
            self._statuses[idx] = "played"
        self._refresh()
        self._trigger_autosave()
        self._trigger_event_state_save()

    def set_next_ready(self, idx: int):
        """Sæt næste sang klar — uden at overskrive skipped/played statusser."""
        # Nulstil forrige current hvis den stadig er playing
        old = self._current_idx
        if old != idx and 0 <= old < len(self._statuses):
            if self._statuses[old] == "playing":
                self._statuses[old] = "pending"
        self._current_idx = idx
        self._song_ended = False
        # Ændr KUN status hvis den er pending — rør ikke skipped/played
        if 0 <= idx < len(self._statuses):
            if self._statuses[idx] not in ("skipped", "played"):
                self._statuses[idx] = "pending"
        self._refresh()
        self._scroll_to(idx)

    def get_song(self, idx: int) -> dict | None:
        return self._songs[idx] if 0 <= idx < len(self._songs) else None

    def get_songs(self) -> list[dict]:
        return list(self._songs)

    def get_statuses(self) -> list[str]:
        return list(self._statuses)

    def count(self) -> int:
        return len(self._songs)

    def set_playlist_name(self, name: str):
        self._title_label.setText(f"DANSELISTE — {name.upper()}")

    # ── Drag-flytning ─────────────────────────────────────────────────────────

    def _on_rows_moved(self, parent, start, end, dest, dest_row):
        """Opdater _songs og _statuses når en sang flyttes via drag."""
        # Husk hvilken sang der er aktiv
        current_song_id = None
        if 0 <= self._current_idx < len(self._songs):
            current_song_id = self._songs[self._current_idx].get("id")

        new_songs = []
        new_statuses = []
        for i in range(self._list.count()):
            old_idx = self._list.item(i).data(Qt.ItemDataRole.UserRole)
            if old_idx is not None and 0 <= old_idx < len(self._songs):
                new_songs.append(self._songs[old_idx])
                new_statuses.append(self._statuses[old_idx])
        self._songs = new_songs
        self._statuses = new_statuses

        # Gendan current_idx til den sang der stadig spiller
        if current_song_id:
            for i, s in enumerate(self._songs):
                if s.get("id") == current_song_id:
                    self._current_idx = i
                    break
        else:
            self._current_idx = -1

        self._song_ended = False
        self._refresh()
        self._trigger_autosave()
        # Emit IKKE next_song_ready — afspilning fortsætter uforstyrret

    # ── Event-state ───────────────────────────────────────────────────────────

    def _save_event_state(self):
        """Gem current_idx og statuses — overlever strømsvigt."""
        try:
            from local.local_db import save_event_state
            save_event_state(self._current_idx, self._statuses)
        except Exception as e:
            pass

    def _trigger_event_state_save(self):
        self._event_state_timer.start()

    def restore_event_state(self) -> bool:
        """Gendan gemt event-fremgang. Returnerer True hvis gendannet."""
        try:
            from local.local_db import load_event_state
            result = load_event_state()
            if not result:
                return False
            idx, statuses = result
            if len(statuses) != len(self._songs):
                return False   # listen er ændret siden sidst
            self._statuses = statuses
            self._current_idx = idx
            self._song_ended = False
            self._refresh()
            return True
        except Exception as e:
            pass
            return False

    def get_named_playlist_id(self) -> str | None:
        return self._named_playlist_id

    def next_playable_idx(self) -> int | None:
        """Find første sang fra toppen der ikke er afspillet, sprunget over eller i gang."""
        for i in range(len(self._songs)):
            if self._statuses[i] not in ("skipped", "played", "playing"):
                if i == self._current_idx and not self._song_ended:
                    continue
                return i
        return None

    # ── Autogem ───────────────────────────────────────────────────────────────

    def _trigger_autosave(self):
        """Start/nulstil debounce-timer — gemmer 800ms efter sidst ændring."""
        self._autosave_timer.start()
        self._lbl_autosave.setText("● ikke gemt")

    def _autosave(self):
        """Gem til '__aktiv__' OG til den navngivne liste hvis der er én."""
        try:
            from local.local_db import get_db, create_playlist, add_song_to_playlist
            with get_db() as conn:
                conn.execute(
                    "DELETE FROM playlists WHERE name=?", (ACTIVE_PLAYLIST_NAME,)
                )
            pl_id = create_playlist(ACTIVE_PLAYLIST_NAME)
            self._active_playlist_id = pl_id
            for i, song in enumerate(self._songs, start=1):
                if song.get("id"):
                    add_song_to_playlist(pl_id, song["id"], position=i)

            # Gem også til den navngivne liste
            if self._named_playlist_id:
                with get_db() as conn:
                    conn.execute(
                        "DELETE FROM playlist_songs WHERE playlist_id=?",
                        (self._named_playlist_id,)
                    )
                    for i, song in enumerate(self._songs, start=1):
                        if song.get("id"):
                            status = self._statuses[i-1] if i-1 < len(self._statuses) else "pending"
                            import uuid as _uuid
                            conn.execute(
                                "INSERT INTO playlist_songs "
                                "(id, playlist_id, song_id, file_id, position, status, is_workshop, dance_override) "
                                "VALUES (?,?,?,?,?,?,?,?)",
                                (str(_uuid.uuid4()), self._named_playlist_id,
                                 song["id"], song.get("file_id"),
                                 i, status,
                                 1 if song.get("is_workshop") else 0,
                                 song.get("active_dance") or "")
                            )

            self._lbl_autosave.setText("✓ gemt")
            self.playlist_changed.emit()
        except Exception as e:
            self._lbl_autosave.setText("⚠ gemfejl")

    def _save_named_playlist_id(self, pl_id: str | None):
        """Gem hvilken navngiven liste der er aktiv — til brug ved næste opstart."""
        from PyQt6.QtCore import QSettings
        s = QSettings("LineDance", "Player")
        if pl_id:
            s.setValue("session/named_playlist_id", pl_id)
        else:
            s.remove("session/named_playlist_id")

    def restore_active_playlist(self):
        """Gendan senest aktive navngivne liste med event-fremgang ved opstart."""
        try:
            from PyQt6.QtCore import QSettings
            s = QSettings("LineDance", "Player")
            pl_id = s.value("session/named_playlist_id", None, type=str)
            if not pl_id:
                return False

            import sqlite3
            from local.local_db import DB_PATH
            conn = sqlite3.connect(str(DB_PATH))
            conn.row_factory = sqlite3.Row

            # Verificer at listen stadig eksisterer
            pl = conn.execute(
                "SELECT id, name FROM playlists WHERE id=?", (pl_id,)
            ).fetchone()
            if not pl:
                conn.close()
                return False

            # Hent sange med status, workshop og dans-override
            # JOIN songs — sangen er altid i songs tabellen (oprettet ved pull med file_missing=1)
            # file_missing betyder bare at filen ikke er på denne maskine
            songs_raw = conn.execute("""
                SELECT s.id, s.title, s.artist, s.album,
                       s.bpm, s.duration_sec,
                       ps.file_id,
                       f.local_path, f.file_format,
                       COALESCE(f.file_missing, 1) as file_missing,
                       ps.position, ps.status, ps.is_workshop, ps.dance_override
                FROM playlist_songs ps
                JOIN songs s ON s.id = ps.song_id
                LEFT JOIN files f ON f.id = ps.file_id
                WHERE ps.playlist_id=? ORDER BY ps.position
            """, (pl_id,)).fetchall()

            songs = []
            statuses = []
            for row in songs_raw:
                dances = conn.execute("""
                    SELECT d.name FROM song_dances sd
                    JOIN dances d ON d.id = sd.dance_id
                    WHERE sd.song_id=? ORDER BY sd.dance_order
                """, (row["id"],)).fetchall()
                dance_names = [d["name"] for d in dances]
                override = row["dance_override"] or ""
                active_dance = override if override else (dance_names[0] if dance_names else "")

                local_path   = row["local_path"] or ""
                file_missing = bool(row["file_missing"])

                # Forsøg at finde en anden fil lokalt hvis den specifikke mangler
                if file_missing or not local_path:
                    match = conn.execute(
                        "SELECT f.local_path FROM files f "
                        "WHERE f.song_id=? AND f.file_missing=0 LIMIT 1",
                        (row["id"],)
                    ).fetchone()
                    if match:
                        local_path   = match["local_path"]
                        file_missing = False

                songs.append({
                    "id":           row["id"],
                    "title":        row["title"],
                    "artist":       row["artist"],
                    "album":        row["album"] or "",
                    "bpm":          row["bpm"] or 0,
                    "duration_sec": row["duration_sec"] or 0,
                    "file_id":      row["file_id"] if "file_id" in row.keys() else None,
                    "local_path":   local_path,
                    "file_format":  row["file_format"] or "",
                    "file_missing": file_missing,
                    "dances":       dance_names,
                    "active_dance": active_dance,
                    "alt_dance":    row["alt_dance_override"] if "alt_dance_override" in row.keys() else "",
                    "is_workshop":  bool(row["is_workshop"]),
                })
                statuses.append(row["status"] or "pending")
            conn.close()

            if songs:
                self._songs         = songs
                # Rens "playing" — må kun være én ad gangen
                self._statuses      = [
                    "pending" if s == "playing" else s
                    for s in statuses
                ]
                self._named_playlist_id = pl_id
                self._current_idx   = -1
                self._song_ended    = False
                self._btn_save_current.setEnabled(True)
                self._btn_save_current.setToolTip(f"Gem ændringer til '{pl['name']}'")
                self._title_label.setText(f"DANSELISTE — {pl['name'].upper()}")
                self._lbl_autosave.setText("✓ gendannet")
                self._check_availability(self._songs)

                # Find næste uafspillede
                ni = self.next_playable_idx()
                if ni is not None:
                    self._current_idx = ni

                self._refresh()
                return True
        except Exception:
            pass
        return False

    # ── Ny / Gem som / Hent ───────────────────────────────────────────────────

    def _new_playlist(self):
        if self._songs:
            reply = QMessageBox.question(
                self, "Ny danseliste",
                "Ryd den aktuelle liste og start forfra?",
                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            )
            if reply != QMessageBox.StandardButton.Yes:
                return
        self._songs = []
        self._statuses = []
        self._current_idx = -1
        self._song_ended = False
        self._named_playlist_id = None
        self._btn_save_current.setEnabled(False)
        self._btn_save_current.setToolTip("Gem ændringer til den indlæste liste")
        self._save_named_playlist_id(None)
        self._title_label.setText("DANSELISTE — NY")
        self._refresh()
        self._trigger_autosave()

    def _save_as(self):
        if not self._songs:
            QMessageBox.information(self, "Gem", "Danselisten er tom.")
            return
        from ui.playlist_browser import PlaylistBrowserDialog
        current_name = self._title_label.text().replace("DANSELISTE — ", "").replace("DANSELISTE", "").strip()
        dialog = PlaylistBrowserDialog(
            mode="save",
            current_songs=self._songs,
            current_name=current_name,
            parent=self.window()
        )
        def on_saved(pl_id, name):
            self._named_playlist_id = pl_id
            self._title_label.setText(f"DANSELISTE — {name.upper()}")
            self._lbl_autosave.setText(f"✓ gemt som \"{name}\"")
            self._btn_save_current.setEnabled(True)
            self._btn_save_current.setToolTip(f"Gem ændringer til '{name}'")
            self._save_named_playlist_id(pl_id)
        dialog.playlist_selected.connect(on_saved)
        dialog.sync_requested.connect(self._request_sync)
        dialog.exec()

    def _save_current(self):
        """Gem ændringer tilbage til den aktuelt indlæste navngivne liste."""
        if not self._named_playlist_id:
            return
        if not self._songs:
            QMessageBox.information(self, "Gem", "Danselisten er tom.")
            return
        try:
            from local.local_db import get_db
            with get_db() as conn:
                conn.execute(
                    "DELETE FROM playlist_songs WHERE playlist_id=?",
                    (self._named_playlist_id,)
                )
                for i, song in enumerate(self._songs, start=1):
                    if song.get("id"):
                        status = self._statuses[i-1] if i-1 < len(self._statuses) else "pending"
                        import uuid as _uuid
                        conn.execute(
                            "INSERT INTO playlist_songs "
                            "(id, playlist_id, song_id, file_id, position, status, is_workshop, dance_override) "
                            "VALUES (?,?,?,?,?,?,?,?)",
                            (str(_uuid.uuid4()), self._named_playlist_id,
                             song["id"], song.get("file_id"),
                             i, status,
                             1 if song.get("is_workshop") else 0,
                             song.get("active_dance") or "")
                        )
            self._lbl_autosave.setText("✓ gemt")

            # Push til server hvis linket med edit-rettighed
            if getattr(self, "_can_edit_server", False):
                from local.local_db import get_db as _gdb
                with _gdb() as c:
                    meta = c.execute(
                        "SELECT api_project_id FROM playlists WHERE id=?",
                        (self._named_playlist_id,)
                    ).fetchone()
                if meta and meta["api_project_id"]:
                    self._push_linked_playlist(
                        self._named_playlist_id, meta["api_project_id"]
                    )
                    self._lbl_autosave.setText("✓ gemt og synkroniseret")

        except Exception as e:
            QMessageBox.warning(self, "Fejl", f"Kunne ikke gemme: {e}")

    def _request_sync(self):
        """Bobl sync-anmodning op til main_window."""
        self.sync_requested.emit()

    def _load_dialog(self):
        from ui.playlist_browser import PlaylistBrowserDialog
        dialog = PlaylistBrowserDialog(mode="load", parent=self.window())
        dialog.playlist_selected.connect(self._load_playlist_by_id)
        dialog.sync_requested.connect(self._request_sync)
        dialog.exec()

    def _load_playlist_by_id(self, pl_id: str, pl_name: str):
        try:
            from local.local_db import get_db

            # Tjek om listen er linket til serveren — pull først
            with get_db() as conn:
                pl_meta = conn.execute(
                    "SELECT api_project_id, is_linked, server_permission "
                    "FROM playlists WHERE id=?", (pl_id,)
                ).fetchone()

            if pl_meta and pl_meta["is_linked"] and pl_meta["api_project_id"]:
                self._pull_linked_playlist(pl_id, pl_meta["api_project_id"])
                # Opdater gem-knap baseret på rettighed
                perm = pl_meta["server_permission"] or "view"
                self._named_playlist_id = pl_id
                self._can_edit_server = (perm == "edit")
            else:
                self._can_edit_server = False
            with get_db() as conn:
                # JOIN songs — sangen er altid i songs tabellen (oprettet ved pull med file_missing=1)
                songs_raw = conn.execute("""
                    SELECT s.id, s.title, s.artist, s.album,
                           s.bpm, s.duration_sec,
                           ps.file_id,
                           f.local_path, f.file_format,
                           COALESCE(f.file_missing, 1) as file_missing,
                           ps.position, ps.status, ps.is_workshop, ps.dance_override
                    FROM playlist_songs ps
                    JOIN songs s ON s.id = ps.song_id
                    LEFT JOIN files f ON f.id = ps.file_id
                    WHERE ps.playlist_id=? ORDER BY ps.position
                """, (pl_id,)).fetchall()
                songs = []
                statuses = []
                repaired = 0
                for row in songs_raw:
                    dances = conn.execute("""
                        SELECT d.name FROM song_dances sd
                        JOIN dances d ON d.id = sd.dance_id
                        WHERE sd.song_id=? ORDER BY sd.dance_order
                    """, (row["id"],)).fetchall()
                    dance_names = [d["name"] for d in dances]
                    override = row["dance_override"] or ""
                    active_dance = override if override else (dance_names[0] if dance_names else "")

                    local_path   = row["local_path"] or ""
                    file_missing = bool(row["file_missing"])

                    # Forsøg at finde en anden fil lokalt hvis den specifikke mangler
                    if file_missing or not local_path:
                        match = conn.execute(
                            "SELECT f.local_path FROM files f "
                            "WHERE f.song_id=? AND f.file_missing=0 LIMIT 1",
                            (row["id"],)
                        ).fetchone()
                        if match:
                            local_path   = match["local_path"]
                            file_missing = False
                            repaired += 1

                    songs.append({
                        "id":           row["id"],
                        "title":        row["title"],
                        "artist":       row["artist"],
                        "album":        row["album"] or "",
                        "bpm":          row["bpm"] or 0,
                        "duration_sec": row["duration_sec"] or 0,
                        "file_id":      row["file_id"] if "file_id" in row.keys() else None,
                        "local_path":   local_path,
                        "file_format":  row["file_format"] or "",
                        "file_missing": file_missing,
                        "dances":       dance_names,
                        "active_dance": active_dance,
                        "is_workshop":  bool(row["is_workshop"]),
                    })
                    statuses.append(row["status"] or "pending")

            self._songs = songs
            self._statuses = statuses
            self._current_idx = -1
            self._song_ended = False
            self._named_playlist_id = pl_id

            # Vis link-indikator i titlen
            is_linked = pl_meta and pl_meta["is_linked"]
            perm      = pl_meta["server_permission"] if is_linked else "edit"
            link_icon = " 🔗" if is_linked else ""
            self._title_label.setText(f"DANSELISTE — {pl_name.upper()}{link_icon}")

            status_txt = f"✓ indlæst — {repaired} sange fundet lokalt" if repaired else "✓ indlæst"
            if is_linked:
                status_txt += f"  ({perm})"
            self._lbl_autosave.setText(status_txt)

            # Gem-knap: deaktiver hvis view-only linket liste
            can_save = not is_linked or perm == "edit"
            self._btn_save_current.setEnabled(can_save)
            self._btn_save_current.setToolTip(
                f"Gem ændringer til '{pl_name}'" if can_save
                else "Du har kun læse-adgang til denne delte liste"
            )
            self._save_named_playlist_id(pl_id)
            self._refresh()
            self._trigger_autosave()
        except Exception as e:
            QMessageBox.warning(self, "Fejl", f"Kunne ikke indlæse listen: {e}")

    # ── Start event ───────────────────────────────────────────────────────────

    def _show_playlist_info(self):
        """Åbn/luk det flydende danseliste-info vindue."""
        try:
            if hasattr(self, "_info_window") and self._info_window \
                    and self._info_window.isVisible():
                self._info_window.close()
                self._info_window = None
                return
        except RuntimeError:
            self._info_window = None

        # Opdater defaults fra indstillinger ved åbning
        try:
            from ui.settings_dialog import load_settings
            s = load_settings()
            self._pause_seconds    = s.get("between_seconds", 60)
            self._workshop_seconds = s.get("workshop_minutes", 10) * 60
        except Exception:
            pass

        from ui.playlist_info_dialog import PlaylistInfoWindow
        from PyQt6.QtWidgets import QApplication
        from PyQt6.QtCore import QSettings
        main = self.window()
        self._info_window = PlaylistInfoWindow(self, parent=main)
        QApplication.instance().aboutToQuit.connect(self._info_window.close)

        # Gendan gemt position eller placer ved siden af hovedvindue
        s = QSettings("LineDance", "Player")
        pos = s.value("window/info_pos")
        if pos:
            self._info_window.move(pos)
        elif main:
            geo = main.geometry()
            self._info_window.move(geo.right() + 10, geo.top())

        # Sikr at vinduet er på skærmen
        screen = QApplication.primaryScreen().availableGeometry()
        w_geo = self._info_window.frameGeometry()
        x = max(0, min(w_geo.x(), screen.right()  - w_geo.width()))
        y = max(0, min(w_geo.y(), screen.bottom() - w_geo.height()))
        self._info_window.move(x, y)
        self._info_window.show()

    def _change_dance(self, idx: int, song: dict):
        """Lad brugeren vælge/skrive hvilken dans der vises for dette nummer."""
        from ui.dance_picker_dialog import DancePickerDialog
        dances = song.get("dances", [])
        current = song.get("active_dance", "")
        if not current:
            current = dances[0] if dances else ""
        current_choreo = song.get("active_choreo", "")

        # Afgør om valget er permanent eller midlertidigt
        # Permanent: ingen dans tagget, eller valgt dans er ikke i de taggede
        # Midlertidig: sangen har flere danse og brugeren vælger en af dem

        dlg = DancePickerDialog(
            current_dance=current,
            song_title=song.get("title", ""),
            existing_dances=dances,
            parent=self.window()
        )
        if dlg.exec():
            chosen = dlg.get_dance()
            # Dans-valg i playlisten er altid midlertidigt — kun dance_override
            song["active_dance"] = chosen  # tom streng = ingen dans
            self._refresh()
            self._sync_dance_to_db(idx, song)

    def _change_alt_dance(self, idx: int, song: dict):
        """Lad brugeren vælge alternativ dans til denne sang i playlisten."""
        from ui.alt_dance_picker_dialog import AltDancePickerDialog
        dlg = AltDancePickerDialog(song, parent=self.window())
        if dlg.exec():
            if dlg.was_cleared():
                chosen = ""
            else:
                chosen = dlg.get_dance()
            rating = dlg.get_rating()
            song["alt_dance"] = chosen
            self._refresh()
            # Gem alt_dance_override på playlist_songs
            self._sync_alt_dance_to_db(idx, song, chosen)
            # Gem rating hvis givet
            if chosen and rating is not None:
                self._save_alt_dance_rating(song, chosen, rating)

    def _sync_alt_dance_to_db(self, idx: int, song: dict, alt_dance: str):
        """Gem alt_dance_override til playlist_songs — både aktiv og navngiven liste."""
        pl_ids = []
        if self._active_playlist_id:
            pl_ids.append(self._active_playlist_id)
        if self._named_playlist_id and self._named_playlist_id not in pl_ids:
            pl_ids.append(self._named_playlist_id)
        if not pl_ids:
            return
        try:
            import logging
            from local.local_db import get_db
            with get_db() as conn:
                for pl_id in pl_ids:
                    conn.execute(
                        "UPDATE playlist_songs SET alt_dance_override=? "
                        "WHERE playlist_id=? AND position=?",
                        (alt_dance, pl_id, idx + 1)
                    )
                    logging.getLogger(__name__).info(
                        f"alt_dance_override='{alt_dance}' gemt på pos {idx+1} i {pl_id}"
                    )
        except Exception as e:
            import logging
            logging.getLogger(__name__).warning(f"alt_dance_to_db fejl: {e}", exc_info=True)

    def _save_alt_dance_rating(self, song: dict, dance_name: str, rating: int):
        """Gem brugerens rating på en alternativ-dans."""
        import uuid
        song_id = song.get("id", "")
        try:
            from local.local_db import get_db
            with get_db() as conn:
                # Find dance_id
                dance_row = conn.execute(
                    "SELECT id FROM dances WHERE name=? COLLATE NOCASE LIMIT 1",
                    (dance_name,)
                ).fetchone()
                if not dance_row:
                    return
                dance_id = dance_row["id"]
                # Opdater eller indsæt rating
                existing = conn.execute(
                    "SELECT id FROM song_alt_dances WHERE song_id=? AND dance_id=?",
                    (song_id, dance_id)
                ).fetchone()
                if existing:
                    conn.execute(
                        "UPDATE song_alt_dances SET user_rating=? WHERE song_id=? AND dance_id=?",
                        (rating, song_id, dance_id)
                    )
                else:
                    conn.execute(
                        "INSERT INTO song_alt_dances (id, song_id, dance_id, user_rating) VALUES (?,?,?,?)",
                        (str(uuid.uuid4()), song_id, dance_id, rating)
                    )
        except Exception as e:
            import logging
            logging.getLogger(__name__).warning(f"save_alt_dance_rating fejl: {e}")

    def _sync_dance_to_db(self, idx: int, song: dict):
        """Gem dance_override til playlist_songs (midlertidigt valg)."""
        import logging
        _log = logging.getLogger(__name__)
        if not self._named_playlist_id:
            _log.warning("_sync_dance_to_db: ingen named_playlist_id")
            return
        try:
            from local.local_db import get_db
            dance_val = song.get("active_dance") or ""
            with get_db() as conn:
                rows_affected = conn.execute(
                    "UPDATE playlist_songs SET dance_override=? "
                    "WHERE playlist_id=? AND position=?",
                    (dance_val, self._named_playlist_id, idx + 1)
                ).rowcount
                _log.info(f"dance_override='{dance_val}' gemt på position {idx+1}, {rows_affected} rækker")
        except Exception as e:
            import logging
            logging.getLogger(__name__).warning(f"_sync_dance_to_db fejl: {e}")

    def _save_dance_permanently(self, idx: int, song: dict, dance_name: str, choreo: str = ""):
        """
        Gem dans permanent på sangen:
        1. song_dances tabellen
        2. ID3-tag i filen (hvis tilgængelig)
        3. Opdater sang-dict så listen vises korrekt
        """
        import uuid
        song_id   = song.get("id", "")
        local_path = song.get("local_path", "")

        try:
            from local.local_db import get_db
            with get_db() as conn:
                # Find eller opret dans
                dance_row = conn.execute(
                    "SELECT id FROM dances WHERE name=? COLLATE NOCASE LIMIT 1",
                    (dance_name,)
                ).fetchone()
                if dance_row:
                    dance_id = dance_row["id"]
                    if choreo:
                        conn.execute(
                            "UPDATE dances SET choreographer=? WHERE id=? AND choreographer=''",
                            (choreo, dance_id)
                        )
                else:
                    cur = conn.execute(
                        "INSERT INTO dances (name, choreographer) VALUES (?,?)",
                        (dance_name, choreo or "")
                    )
                    dance_id = cur.lastrowid

                # Tilføj til song_dances
                existing = conn.execute(
                    "SELECT id FROM song_dances WHERE song_id=? AND dance_id=?",
                    (song_id, dance_id)
                ).fetchone()
                if not existing:
                    # Find næste dance_order
                    max_order = conn.execute(
                        "SELECT MAX(dance_order) FROM song_dances WHERE song_id=?",
                        (song_id,)
                    ).fetchone()[0] or 0
                    conn.execute(
                        "INSERT INTO song_dances (id, song_id, dance_id, dance_order) VALUES (?,?,?,?)",
                        (str(uuid.uuid4()), song_id, dance_id, max_order + 1)
                    )

            # Opdater sang-dict
            dances = song.get("dances", [])
            if dance_name not in dances:
                dances.append(dance_name)
                song["dances"] = dances
            song["active_dance"] = dance_name

            # Gem i ID3-tag hvis filen er tilgængelig
            if local_path:
                try:
                    from local.tag_reader import write_dance_to_file
                    write_dance_to_file(local_path, dances)
                except Exception:
                    pass

            # Opdater også dance_override på listen
            self._sync_dance_to_db(idx, song)

            import logging
            logging.getLogger(__name__).info(
                f"Dans gemt permanent: '{dance_name}' → '{song.get('title','?')}'"
            )

        except Exception as e:
            import logging
            logging.getLogger(__name__).warning(f"Kunne ikke gemme dans permanent: {e}")

    def _sync_ws_to_db(self, idx: int, song: dict):
        """Gem is_workshop til playlist_songs — både navngiven og aktiv liste."""
        pl_ids = []
        if self._named_playlist_id:
            pl_ids.append(self._named_playlist_id)
        if self._active_playlist_id:
            pl_ids.append(self._active_playlist_id)
        if not pl_ids:
            return
        try:
            from local.local_db import get_db
            with get_db() as conn:
                for pl_id in pl_ids:
                    conn.execute(
                        "UPDATE playlist_songs SET is_workshop=? "
                        "WHERE playlist_id=? AND position=?",
                        (1 if song.get("is_workshop") else 0, pl_id, idx + 1)
                    )
        except Exception:
            pass

    def _pull_linked_playlist(self, pl_id: str, server_id: str):
        """Hent seneste version af en linket liste fra serveren — i baggrundstråd."""
        import threading
        import uuid

        def _do_pull():
            try:
                from ui.settings_dialog import load_settings
                from local.local_db import DB_PATH
                import sqlite3, urllib.request, json

                s = load_settings()
                server_url = s.get("server_url", "").rstrip("/")
                mw = self.window()
                token = getattr(mw, "_api_token", None)
                if not token or not server_url:
                    return

                req = urllib.request.Request(
                    f"{server_url}/sharing/playlists/{server_id}",
                    headers={"Authorization": f"Bearer {token}"}
                )
                with urllib.request.urlopen(req, timeout=8) as resp:
                    pl_data = json.loads(resp.read())

                conn = sqlite3.connect(str(DB_PATH), timeout=10)
                conn.row_factory = sqlite3.Row
                conn.execute("PRAGMA journal_mode=WAL")
                conn.execute("DELETE FROM playlist_songs WHERE playlist_id=?", (pl_id,))

                position = 1
                for song_data in pl_data.get("songs", []):
                    title  = song_data.get("title", "")
                    artist = song_data.get("artist", "")
                    if not title:
                        continue
                    # Find sang via titel+artist
                    local = conn.execute(
                        "SELECT s.id FROM songs s "
                        "JOIN files f ON f.song_id = s.id AND f.file_missing=0 "
                        "WHERE s.title=? AND s.artist=? LIMIT 1",
                        (title, artist)
                    ).fetchone()
                    if not local:
                        # Sang mangler lokalt — opret som missing
                        local = conn.execute(
                            "SELECT id FROM songs WHERE title=? AND artist=? LIMIT 1",
                            (title, artist)
                        ).fetchone()
                    if not local:
                        new_id = str(uuid.uuid4())
                        conn.execute(
                            "INSERT INTO songs (id, title, artist) VALUES (?,?,?)",
                            (new_id, title, artist)
                        )
                        song_id = new_id
                    else:
                        song_id = local["id"]

                    # Find fil
                    file_row = conn.execute(
                        "SELECT id FROM files WHERE song_id=? AND file_missing=0 LIMIT 1",
                        (song_id,)
                    ).fetchone()
                    file_id = file_row["id"] if file_row else None

                    conn.execute(
                        "INSERT INTO playlist_songs "
                        "(id, playlist_id, song_id, file_id, position, status, is_workshop, dance_override) "
                        "VALUES (?,?,?,?,?,?,?,?)",
                        (str(uuid.uuid4()), pl_id, song_id, file_id,
                         position, song_data.get("status", "pending"),
                         1 if song_data.get("is_workshop") else 0,
                         song_data.get("dance_override") or "")
                    )
                    position += 1

                conn.commit()
                conn.close()
            except Exception:
                pass  # Offline — brug lokalt cachet version

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

    def _push_linked_playlist(self, pl_id: str, server_id: str):
        """Push ændringer til server for en linket liste."""
        try:
            from ui.settings_dialog import load_settings
            from local.local_db import DB_PATH
            s = load_settings()
            server_url = s.get("server_url", "").rstrip("/")
            mw = self.window()
            token = getattr(mw, "_api_token", None)
            if not token or not server_url:
                return

            import sqlite3, json, urllib.request
            conn = sqlite3.connect(str(DB_PATH))
            conn.row_factory = sqlite3.Row
            songs = []
            for ps in conn.execute(
                "SELECT s.title, s.artist, ps.position, ps.status, "
                "ps.is_workshop, ps.dance_override "
                "FROM playlist_songs ps JOIN songs s ON s.id=ps.song_id "
                "WHERE ps.playlist_id=? ORDER BY ps.position", (pl_id,)
            ).fetchall():
                songs.append({
                    "title":         ps["title"],
                    "artist":        ps["artist"],
                    "position":      ps["position"],
                    "status":        ps["status"] or "pending",
                    "is_workshop":   bool(ps["is_workshop"]),
                    "dance_override": ps["dance_override"] or "",
                })
            conn.close()

            data = json.dumps({"songs": songs}).encode()
            req = urllib.request.Request(
                f"{server_url}/sharing/playlists/{server_id}/songs",
                data=data,
                headers={
                    "Content-Type": "application/json",
                    "Authorization": f"Bearer {token}",
                },
                method="PUT"
            )
            urllib.request.urlopen(req, timeout=8)
        except Exception as e:
            pass

    def _on_pause_changed(self, seconds: int):
        self._pause_seconds = seconds

    def _start_event(self):
        if not self._songs:
            return
        reply = QMessageBox.question(
            self, "Start event",
            "Dette nulstiller alle statusser i danselisten.\nFortsæt?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
        )
        if reply == QMessageBox.StandardButton.Yes:
            self._statuses = ["pending"] * len(self._songs)
            self._current_idx = -1
            self._song_ended = False
            try:
                from local.local_db import clear_event_state
                clear_event_state()
            except Exception:
                pass
            self._refresh()
            self._scroll_to(0)
            # Sæt første sang klar
            ni = self.next_playable_idx()
            if ni is not None:
                self._current_idx = ni
                self._refresh()
                self.next_song_ready.emit(self._songs[ni])
            self.event_started.emit()

    # ── Tilgængelighed ────────────────────────────────────────────────────────

    def _check_availability(self, songs: list[dict]) -> list[dict]:
        """Tjek om sange er tilgængelige lokalt — forsøger auto-match ved gule."""
        from pathlib import Path
        from local.local_db import get_db

        try:
            with get_db() as conn:
                for song in songs:
                    path = song.get("local_path", "")
                    if path and Path(path).exists():
                        song["availability"] = "green"
                        continue

                    # Forsøg auto-match via titel+artist
                    title  = song.get("title", "")
                    artist = song.get("artist", "")
                    try:
                        match = conn.execute("""
                            SELECT id, local_path FROM songs
                            WHERE title=? AND artist=? AND file_missing=0
                            AND local_path IS NOT NULL AND local_path != ''
                            LIMIT 1
                        """, (title, artist)).fetchone()
                        if match and Path(match["local_path"]).exists():
                            song["local_path"]   = match["local_path"]
                            song["id"]           = match["id"]
                            song["file_missing"] = False
                            song["availability"] = "green"
                            continue
                    except Exception:
                        pass

                    song["availability"] = "yellow" if not path else "red"
        except Exception:
            # DB fejl — sæt alle til yellow
            for song in songs:
                song.setdefault("availability", "yellow")
        return songs

    def check_offline_availability(self):
        """Gennemgå alle sange og forsøg auto-match for gule/røde."""
        from pathlib import Path
        from local.local_db import get_db

        with get_db() as conn:
            for song in self._songs:
                path = song.get("local_path", "")
                # Grøn — filen eksisterer lokalt
                if path and Path(path).exists():
                    song["availability"] = "green"
                    song["file_missing"] = False
                    # Opdater files tabellen
                    conn.execute(
                        "UPDATE files SET file_missing=0 WHERE local_path=?",
                        (path,)
                    )
                    continue

                # Forsøg auto-match via titel+artist i files tabellen
                title  = song.get("title", "")
                artist = song.get("artist", "")
                match = conn.execute("""
                    SELECT f.id as file_id, f.local_path, s.id as song_id
                    FROM files f
                    JOIN songs s ON s.id = f.song_id
                    WHERE s.title=? AND s.artist=? AND f.file_missing=0
                    AND f.local_path IS NOT NULL AND f.local_path != ''
                    LIMIT 1
                """, (title, artist)).fetchone()

                if match and Path(match["local_path"]).exists():
                    song["local_path"]   = match["local_path"]
                    song["file_id"]      = match["file_id"]
                    song["availability"] = "green"
                    song["file_missing"] = False
                    # Opdater playlist_songs til at pege på den fundne fil
                    if self._named_playlist_id:
                        conn.execute(
                            "UPDATE playlist_songs SET file_id=? "
                            "WHERE playlist_id=? AND song_id=?",
                            (match["file_id"], self._named_playlist_id, song["id"])
                        )
                else:
                    song["availability"] = "red"

        self._refresh()
        green_count = sum(1 for s in self._songs if s.get("availability") == "green")
        return green_count, len(self._songs)

    # ── Højreklik ─────────────────────────────────────────────────────────────

    def _show_context_menu(self, pos):
        item = self._list.itemAt(pos)
        if not item:
            return
        idx = item.data(Qt.ItemDataRole.UserRole)
        if idx is None:
            return
        song = self._songs[idx] if 0 <= idx < len(self._songs) else None
        avail = song.get("availability", "yellow") if song else "yellow"
        all_green = all(s.get("availability", "yellow") == "green" for s in self._songs)

        menu = QMenu(self)
        act_play   = menu.addAction("▶  Afspil denne")
        menu.addSeparator()
        act_skip   = menu.addAction("—  Spring over")
        act_unplay = menu.addAction("↺  Sæt til ikke afspillet")
        act_played = menu.addAction("✓  Sæt til afspillet")
        menu.addSeparator()
        act_dance  = menu.addAction("💃  Vælg dans...")
        act_alt_dance = menu.addAction("💃  Vælg alternativ dans...")
        is_ws = song.get("is_workshop", False) if song else False
        act_ws = menu.addAction("🎓  Fjern workshop" if is_ws else "🎓  Markér som workshop")
        menu.addSeparator()
        act_dance_info = menu.addAction("ℹ  Dans-info...")
        menu.addSeparator()
        # Offline tilgængelighed
        act_check = menu.addAction("🔍  Kontrollér offline afvikling")
        act_check.setEnabled(not all_green)
        act_manual = menu.addAction("📂  Manuel match af fil...")
        act_manual.setEnabled(avail != "green")
        menu.addSeparator()
        act_remove = menu.addAction("✕  Fjern fra liste")

        action = menu.exec(self._list.mapToGlobal(pos))
        if action == act_play:
            self.song_selected.emit(idx)
        elif action == act_skip:
            self._statuses[idx] = "skipped"
            self.status_changed.emit(idx, "skipped")
            self._refresh(); self._trigger_autosave(); self._trigger_event_state_save()
        elif action == act_unplay:
            self._statuses[idx] = "pending"
            self.status_changed.emit(idx, "pending")
            self._refresh(); self._trigger_autosave(); self._trigger_event_state_save()
        elif action == act_played:
            self._statuses[idx] = "played"
            self.status_changed.emit(idx, "played")
            self._refresh(); self._trigger_autosave(); self._trigger_event_state_save()
        elif action == act_dance and song:
            self._change_dance(idx, song)
        elif action == act_alt_dance and song:
            self._change_alt_dance(idx, song)
        elif action == act_ws and song:
            song["is_workshop"] = not song.get("is_workshop", False)
            self._sync_ws_to_db(idx, song)
            self._refresh()
            self.playlist_changed.emit()
        elif action == act_dance_info:
            if song:
                from ui.dance_info_dialog import DanceInfoDialog
                dlg = DanceInfoDialog(song, parent=self.window())
                dlg.exec()
        elif action == act_check:
            self._check_offline()
        elif action == act_manual and song:
            self._manual_match(idx, song)
        elif action == act_remove:
            self._songs.pop(idx)
            self._statuses.pop(idx)
            if self._current_idx >= idx:
                self._current_idx = max(-1, self._current_idx - 1)
            self._refresh(); self._trigger_autosave()

    def _check_offline(self):
        """Kør offline tjek og vis resultat."""
        green, total = self.check_offline_availability()
        red = sum(1 for s in self._songs if s.get("availability") == "red")
        if red == 0:
            self._set_status_msg(f"✓ Alle {total} sange er tilgængelige lokalt", 4000)
        else:
            self._set_status_msg(
                f"⚠ {green}/{total} tilgængelige — {red} ikke fundet (rød markering)", 6000
            )

    def _set_status_msg(self, msg: str, ms: int = 3000):
        """Vis besked i playlist-titlen kortvarigt."""
        old = self._title_label.text()
        self._title_label.setText(msg)
        QTimer.singleShot(ms, lambda: self._title_label.setText(old))

    def _manual_match(self, idx: int, song: dict):
        """Søg i biblioteket og lad brugeren vælge den rigtige sang."""
        from PyQt6.QtWidgets import (
            QDialog, QVBoxLayout, QHBoxLayout, QLineEdit,
            QListWidget, QListWidgetItem, QPushButton, QLabel
        )
        from local.local_db import get_db
        from pathlib import Path

        dlg = QDialog(self.window())
        dlg.setWindowTitle(f"Manuel match: {song.get('title','?')}")
        dlg.setMinimumSize(500, 400)
        layout = QVBoxLayout(dlg)
        layout.setContentsMargins(12, 12, 12, 12)
        layout.setSpacing(8)

        lbl = QLabel(f"Søg i biblioteket efter en sang til: <b>{song.get('title','')} – {song.get('artist','')}</b>")
        lbl.setWordWrap(True)
        layout.addWidget(lbl)

        search = QLineEdit()
        search.setPlaceholderText("Søg på titel eller artist...")
        search.setText(song.get("title", ""))
        layout.addWidget(search)

        lst = QListWidget()
        layout.addWidget(lst)

        def _load_results(text):
            lst.clear()
            try:
                with get_db() as conn:
                    rows = conn.execute("""
                        SELECT id, title, artist, local_path, file_format
                        FROM songs
                        WHERE file_missing=0
                        AND (title LIKE ? OR artist LIKE ?)
                        ORDER BY title LIMIT 100
                    """, (f"%{text}%", f"%{text}%")).fetchall()
                for row in rows:
                    label = f"{row['title']}  ·  {row['artist']}  [{row['file_format']}]"
                    item = QListWidgetItem(label)
                    item.setData(Qt.ItemDataRole.UserRole, dict(row))
                    lst.addItem(item)
            except Exception:
                pass

        search.textChanged.connect(_load_results)
        _load_results(search.text())

        btn_row = QHBoxLayout()
        btn_ok = QPushButton("✓  Brug valgt sang")
        btn_ok.setEnabled(False)
        btn_cancel = QPushButton("Annuller")
        btn_row.addWidget(btn_ok)
        btn_row.addWidget(btn_cancel)
        layout.addLayout(btn_row)

        lst.itemSelectionChanged.connect(lambda: btn_ok.setEnabled(bool(lst.currentItem())))
        btn_cancel.clicked.connect(dlg.reject)

        def _accept():
            item = lst.currentItem()
            if not item:
                return
            chosen = item.data(Qt.ItemDataRole.UserRole)
            path = chosen.get("local_path", "")
            if not path or not Path(path).exists():
                return
            song["local_path"]   = path
            song["availability"] = "green"
            song["file_missing"] = False
            # Opdater DB
            try:
                with get_db() as conn:
                    conn.execute(
                        "UPDATE songs SET local_path=?, file_missing=0 WHERE id=?",
                        (path, song.get("id", ""))
                    )
            except Exception:
                pass
            self._refresh()
            dlg.accept()

        btn_ok.clicked.connect(_accept)
        lst.itemDoubleClicked.connect(lambda _: _accept())
        dlg.exec()

    # ── Render ────────────────────────────────────────────────────────────────

    def _refresh(self):
        self._list.clear()
        played = sum(1 for s in self._statuses if s == "played")
        self._lbl_progress.setText(f"{played} / {len(self._songs)} afspillet")

        # Find næste uafspillede til blå markering — aldrig samme som current
        next_idx = None
        if self._current_idx >= 0 and not self._song_ended:
            # Sang spiller — vis næste som blå
            next_idx = self.next_playable_idx()
        elif self._current_idx == -1 or self._song_ended:
            # Ingen sang spiller — vis første som blå
            next_idx = self.next_playable_idx()

        for i, song in enumerate(self._songs):
            is_current = (i == self._current_idx and not self._song_ended)
            is_next    = (i == next_idx and not is_current)
            if is_current:
                status = "playing"
            elif is_next:
                status = "next"
            else:
                status = self._statuses[i]
            icon = self.STATUS_ICON.get(status, "   ")

            # Dans er primær tekst, sang er sekundær
            active = song.get("active_dance", "")
            if not active:
                dances = song.get("dances", [])
                active = dances[0] if dances else "— ingen dans —"
            alt    = song.get("alt_dance", "")
            ws_tag = " 🎓" if song.get("is_workshop") else ""

            # Tilgængeligheds-dot til højre — kun hvis tjekket (ikke yellow)
            avail = song.get("availability", "yellow")
            avail_color = {"green": "#27ae60", "red": "#e74c3c"}.get(avail, None)
            avail_tip   = {"green": "Tilgængelig lokalt", "red": "Ikke fundet lokalt"}.get(avail, "")

            dance_line = f"{active}{ws_tag}"
            if alt:
                dance_line += f"  /  {alt}"

            text = (f"{i+1:>2}.  {dance_line}\n"
                    f"        {song.get('title','—')}  ·  {song.get('artist','')}")
            item = QListWidgetItem(f"{icon} {text}")
            item.setData(Qt.ItemDataRole.UserRole, i)
            item.setData(Qt.ItemDataRole.UserRole + 1, avail_color)
            if avail_tip:
                item.setToolTip(avail_tip)

            if status == "playing":
                item.setForeground(QColor(self.STATUS_COLOR["playing"]))
                f = item.font(); f.setBold(True); item.setFont(f)
            elif status == "next":
                item.setForeground(QColor(self.STATUS_COLOR["next"]))
                f = item.font(); f.setBold(True); item.setFont(f)
            elif status == "played":
                item.setForeground(QColor("#2ecc71"))
            elif status == "skipped":
                item.setForeground(QColor("#e74c3c"))
            else:
                item.setForeground(QColor("#9aa0b0"))
            self._list.addItem(item)

    def _scroll_to(self, idx: int):
        if 0 <= idx < self._list.count():
            self._list.scrollToItem(
                self._list.item(idx), QListWidget.ScrollHint.PositionAtCenter)

    def _on_double_click(self, item: QListWidgetItem):
        idx = item.data(Qt.ItemDataRole.UserRole)
        if idx is not None:
            self.song_selected.emit(idx)