import os
import logging
from typing import List, Dict, Optional, Any

from PyQt6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QLabel, QProgressBar,
    QComboBox, QMessageBox,
    QFileDialog, QTableWidget, QTableWidgetItem,
    QHeaderView, QMenu, QLineEdit, QFrame,
)
from PyQt6.QtCore import Qt, pyqtSignal, QUrl, QMimeData
from PyQt6.QtGui import (
    QDragEnterEvent, QDragMoveEvent, QDragLeaveEvent, QDropEvent,
    QDrag, QPixmap, QPainter, QColor,
)

from ipod_device import IPodDevice
from device_monitor import DeviceMonitor
from worker import WorkerThread

logger = logging.getLogger(__name__)


class NumericTableItem(QTableWidgetItem):
    def __lt__(self, other):
        self_data = self.data(Qt.ItemDataRole.UserRole) or {}
        other_data = other.data(Qt.ItemDataRole.UserRole) or {}
        self_num = (self_data or {}).get("track_number", 0) or 0
        other_num = (other_data or {}).get("track_number", 0) or 0
        return self_num < other_num


class DraggableIPodTable(QTableWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setDragEnabled(True)

    def startDrag(self, supportedActions):
        paths = []
        for item in self.selectedItems():
            row = item.row()
            data_item = self.item(row, 0)
            if data_item is None:
                continue
            track_data = data_item.data(Qt.ItemDataRole.UserRole)
            if track_data and track_data.get("file_path"):
                paths.append(track_data["file_path"])

        if not paths:
            return

        paths = list(set(paths))
        mime = QMimeData()
        mime.setUrls([QUrl.fromLocalFile(p) for p in paths])

        drag = QDrag(self)
        drag.setMimeData(mime)

        count = len(paths)
        label = f"{count} track{'s' if count != 1 else ''}"
        pixmap = QPixmap(160, 28)
        pixmap.fill(Qt.GlobalColor.transparent)
        painter = QPainter(pixmap)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
        bg = self.palette().color(self.palette().ColorRole.Highlight)
        painter.setBrush(bg)
        painter.setPen(Qt.PenStyle.NoPen)
        painter.drawRoundedRect(0, 0, 160, 28, 6, 6)
        painter.setPen(self.palette().color(self.palette().ColorRole.HighlightedText))
        painter.drawText(pixmap.rect(), Qt.AlignmentFlag.AlignCenter, label)
        painter.end()
        drag.setPixmap(pixmap)
        drag.setHotSpot(pixmap.rect().center())

        drag.exec(Qt.DropAction.CopyAction)


class iPodTab(QWidget):

    device_mounted = pyqtSignal(str)
    device_unmounted = pyqtSignal()
    tracks_dropped_for_transfer = pyqtSignal(list)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.ipod_devices: List[Dict] = []
        self.current_mount_point: Optional[str] = None
        self.worker_thread: Optional[WorkerThread] = None
        self._drag_hover_active = False
        self.setAcceptDrops(True)
        self._setup_ui()

    def _setup_ui(self):
        layout = QVBoxLayout(self)
        layout.setContentsMargins(12, 12, 12, 12)
        layout.setSpacing(8)

        palette = self.palette()
        hl = palette.color(palette.ColorRole.Highlight)
        hl_name = hl.name()
        hl_light = QColor(hl)
        hl_light.setAlpha(60)
        hl_light_name = hl_light.name()
        text_color = palette.color(palette.ColorRole.WindowText).name()
        dim_color = palette.color(palette.ColorRole.PlaceholderText).name()
        mid_color = palette.color(palette.ColorRole.Midlight).name() if palette.color(palette.ColorRole.Midlight).isValid() else palette.color(palette.ColorRole.Window).darker(120).name()
        base_color = palette.color(palette.ColorRole.Base).name()
        window_color = palette.color(palette.ColorRole.Window).name()

        section_style = f"""
            color: {dim_color};
            font-size: 11px;
            font-weight: bold;
            letter-spacing: 1px;
            padding: 4px 0;
        """

        btn_style = f"""
            QPushButton {{
                padding: 5px 12px;
                border: 1px solid {mid_color};
                border-radius: 4px;
                background: {base_color};
                color: {text_color};
                font-size: 12px;
            }}
            QPushButton:hover {{
                background: {hl_light_name};
                border-color: {hl_name};
            }}
            QPushButton:disabled {{
                color: {dim_color};
                border-color: {mid_color};
                background: {window_color};
            }}
        """

        # ── Device Toolbar ──────────────────────────────────
        device_layout = QHBoxLayout()
        device_layout.setSpacing(6)

        device_label = QLabel("Device:")
        device_label.setStyleSheet(f"color: {text_color}; font-size: 12px; font-weight: bold;")
        device_layout.addWidget(device_label)

        self.device_combo = QComboBox()
        self.device_combo.setPlaceholderText("No devices found")
        self.device_combo.setMinimumWidth(200)
        self.device_combo.currentIndexChanged.connect(self._on_device_combo_changed)
        device_layout.addWidget(self.device_combo)

        self.refresh_button = QPushButton("\U0001F504 Refresh")
        self.refresh_button.setStyleSheet(btn_style)
        self.refresh_button.clicked.connect(self._on_refresh_devices_clicked)
        device_layout.addWidget(self.refresh_button)

        device_layout.addStretch()

        self.mount_button = QPushButton("\U0001F50C  Mount")
        self.mount_button.setStyleSheet(btn_style)
        self.mount_button.setEnabled(False)
        self.mount_button.clicked.connect(self._on_mount_clicked)
        device_layout.addWidget(self.mount_button)

        self.eject_button = QPushButton("\u23CF  Eject")
        self.eject_button.setStyleSheet(btn_style)
        self.eject_button.setEnabled(False)
        self.eject_button.clicked.connect(self._on_eject_clicked)
        device_layout.addWidget(self.eject_button)

        layout.addLayout(device_layout)

        # ── Separator ───────────────────────────────────────
        sep1 = QFrame()
        sep1.setFrameShape(QFrame.Shape.HLine)
        sep1.setStyleSheet(f"color: {mid_color}; margin: 2px 0;")
        sep1.setFixedHeight(1)
        layout.addWidget(sep1)

        # ── Device Info Bar ─────────────────────────────────
        self.device_info_label = QLabel("No device selected")
        self.device_info_label.setStyleSheet(f"color: {dim_color}; font-size: 12px; padding: 2px 0;")
        layout.addWidget(self.device_info_label)

        self.device_status_label = QLabel("Status: Not connected")
        self.device_status_label.setStyleSheet(f"color: {dim_color}; font-size: 11px; padding: 0 0 4px 0;")
        layout.addWidget(self.device_status_label)

        # ── Search toolbar ──────────────────────────────────
        search_layout = QHBoxLayout()
        search_layout.setSpacing(8)

        self.track_count_label = QLabel("Tracks: 0")
        self.track_count_label.setStyleSheet(f"color: {text_color}; font-size: 12px; font-weight: bold;")
        search_layout.addWidget(self.track_count_label)

        search_layout.addStretch()

        self.ipod_search_input = QLineEdit()
        self.ipod_search_input.setPlaceholderText("\U0001F50D  Search artists, albums, tracks...")
        self.ipod_search_input.setFixedWidth(280)
        self.ipod_search_input.setStyleSheet(f"""
            QLineEdit {{
                padding: 5px 10px;
                border: 1px solid {mid_color};
                border-radius: 4px;
                background: {base_color};
                color: {text_color};
                font-size: 12px;
            }}
            QLineEdit:focus {{
                border-color: {hl_name};
            }}
        """)
        self.ipod_search_input.textChanged.connect(self._on_search_text_changed)
        search_layout.addWidget(self.ipod_search_input)

        layout.addLayout(search_layout)

        # ── Track Table ─────────────────────────────────────
        self.track_table = DraggableIPodTable()
        self.track_table.setColumnCount(7)
        self.track_table.setHorizontalHeaderLabels(
            ["#", "Artist", "Title", "Album", "Duration", "Genre", "Size"]
        )
        self.track_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
        self.track_table.setSelectionMode(QTableWidget.SelectionMode.ExtendedSelection)
        self.track_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
        self.track_table.setAlternatingRowColors(True)
        self.track_table.verticalHeader().setVisible(False)
        self.track_table.setSortingEnabled(True)
        self.track_table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
        self.track_table.customContextMenuRequested.connect(self._on_table_context_menu)
        self.track_table.cellDoubleClicked.connect(self._on_table_double_clicked)

        self.track_table.setStyleSheet(f"""
            QTableWidget {{
                background: {base_color};
                alternate-background-color: {window_color};
                border: 1px solid {mid_color};
                border-radius: 4px;
                gridline-color: {mid_color};
                font-size: 12px;
                color: {text_color};
                selection-background-color: {hl_light_name};
                selection-color: {text_color};
            }}
            QTableWidget::item {{
                padding: 4px 8px;
            }}
            QTableWidget::item:selected {{
                background: {hl_light_name};
                color: {text_color};
            }}
            QHeaderView::section {{
                background: {window_color};
                color: {dim_color};
                font-size: 11px;
                font-weight: bold;
                padding: 6px 8px;
                border: none;
                border-bottom: 1px solid {mid_color};
                border-right: 1px solid {mid_color};
            }}
            QHeaderView::section:hover {{
                color: {text_color};
                background: {hl_light_name};
            }}
            QHeaderView::down-arrow {{
                image: none;
            }}
            QHeaderView::up-arrow {{
                image: none;
            }}
        """)

        header = self.track_table.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
        header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
        header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
        header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch)
        header.setSectionResizeMode(4, QHeaderView.ResizeMode.ResizeToContents)
        header.setSectionResizeMode(5, QHeaderView.ResizeMode.ResizeToContents)
        header.setSectionResizeMode(6, QHeaderView.ResizeMode.ResizeToContents)

        self.track_table.horizontalHeader().sortIndicatorChanged.connect(self._on_sort_changed)
        layout.addWidget(self.track_table, stretch=1)

        # ── Drop Zone ───────────────────────────────────────
        self._drop_label = QLabel("\u2B07  Drop tracks here to transfer to iPod")
        self._drop_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self._drop_label.setFixedHeight(44)
        self._drop_label.setStyleSheet(
            f"background: {hl_light_name}; border: 2px dashed {hl_name}; "
            f"border-radius: 8px; font-weight: bold; color: {hl_name}; font-size: 13px;"
        )
        self._drop_label.setVisible(False)
        layout.addWidget(self._drop_label)

        # ── Action Toolbar ──────────────────────────────────
        action_layout = QHBoxLayout()
        action_layout.setSpacing(6)

        self.remove_button = QPushButton("\U0001F5D1  Remove from iPod")
        self.remove_button.setStyleSheet(btn_style)
        self.remove_button.clicked.connect(self._on_remove_tracks_clicked)
        action_layout.addWidget(self.remove_button)

        self.export_button = QPushButton("\u2B07  Download to Computer...")
        self.export_button.setEnabled(False)
        self.export_button.setStyleSheet(btn_style)
        self.export_button.clicked.connect(self._on_export_ipod_clicked)
        action_layout.addWidget(self.export_button)

        action_layout.addStretch()

        self.orphan_button = QPushButton("\U0001F4C2  Scan Orphans")
        self.orphan_button.setStyleSheet(btn_style)
        self.orphan_button.clicked.connect(self._on_scan_orphans_clicked)
        action_layout.addWidget(self.orphan_button)

        self.dedup_button = QPushButton("\U0001F501  Remove Duplicates")
        self.dedup_button.setStyleSheet(btn_style)
        self.dedup_button.clicked.connect(self._on_remove_duplicates_clicked)
        action_layout.addWidget(self.dedup_button)

        layout.addLayout(action_layout)

        # ── Progress Layout ─────────────────────────────────
        progress_layout = QVBoxLayout()
        progress_layout.setSpacing(4)

        self.export_progress = QProgressBar()
        self.export_progress.setRange(0, 100)
        self.export_progress.setVisible(False)
        self.export_progress.setFixedHeight(18)
        self.export_progress.setStyleSheet(f"""
            QProgressBar {{
                border: 1px solid {mid_color};
                border-radius: 4px;
                background: {window_color};
                text-align: center;
                font-size: 11px;
                color: {text_color};
            }}
            QProgressBar::chunk {{
                background: {hl_name};
                border-radius: 3px;
            }}
        """)
        progress_layout.addWidget(self.export_progress)

        self.export_status = QLabel("")
        self.export_status.setVisible(False)
        self.export_status.setStyleSheet(f"color: {dim_color}; font-size: 11px;")
        progress_layout.addWidget(self.export_status)

        self.delete_progress = QProgressBar()
        self.delete_progress.setRange(0, 100)
        self.delete_progress.setVisible(False)
        self.delete_progress.setFixedHeight(18)
        self.delete_progress.setStyleSheet(f"""
            QProgressBar {{
                border: 1px solid {mid_color};
                border-radius: 4px;
                background: {window_color};
                text-align: center;
                font-size: 11px;
                color: {text_color};
            }}
            QProgressBar::chunk {{
                background: {hl_name};
                border-radius: 3px;
            }}
        """)
        progress_layout.addWidget(self.delete_progress)

        self.delete_status = QLabel("")
        self.delete_status.setVisible(False)
        self.delete_status.setStyleSheet(f"color: {dim_color}; font-size: 11px;")
        progress_layout.addWidget(self.delete_status)

        layout.addLayout(progress_layout)

    # ── Drag / Drop ───────────────────────────────────────────
    def dragEnterEvent(self, event: QDragEnterEvent):
        if event.mimeData().hasUrls():
            self._drag_hover_active = True
            self._drop_label.setVisible(True)
            event.acceptProposedAction()
        else:
            super().dragEnterEvent(event)

    def dragMoveEvent(self, event: QDragMoveEvent):
        if self._drag_hover_active and event.mimeData().hasUrls():
            event.acceptProposedAction()
        else:
            super().dragMoveEvent(event)

    def dragLeaveEvent(self, event: QDragLeaveEvent):
        self._drag_hover_active = False
        self._drop_label.setVisible(False)
        super().dragLeaveEvent(event)

    def dropEvent(self, event: QDropEvent):
        self._drag_hover_active = False
        self._drop_label.setVisible(False)
        if event.mimeData().hasUrls():
            paths = [url.toLocalFile() for url in event.mimeData().urls()]
            paths = [p for p in paths if os.path.exists(p)]
            if paths:
                if not self.current_mount_point:
                    QMessageBox.warning(
                        self, "iPod Not Mounted",
                        "Mount the iPod first before transferring tracks."
                    )
                    event.ignore()
                    return
                self.tracks_dropped_for_transfer.emit(paths)
                event.acceptProposedAction()
            else:
                event.ignore()
        else:
            super().dropEvent(event)

    # ── Device Discovery ──────────────────────────────────────
    def _on_refresh_devices_clicked(self):
        if self._is_worker_running():
            return

        self.device_info_label.setText("Detecting devices...")

        self.worker_thread = WorkerThread(task_type="detect_devices")
        self.worker_thread.progress_signal.connect(self._on_device_detection_progress)
        self.worker_thread.finished_signal.connect(self._on_device_detection_finished)
        self.worker_thread.start()

    def _on_device_detection_progress(self, progress, status):
        self.device_info_label.setText(status)

    def _on_device_detection_finished(self, success, message, result):
        self._cleanup_worker()

        if success:
            self.ipod_devices = result
            self.device_combo.blockSignals(True)
            self.device_combo.clear()
            for device in self.ipod_devices:
                mounted = device.get("mounted", False)
                mount_text = " [mounted]" if mounted else " [not mounted]"
                self.device_combo.addItem(device.get("name", "Unknown Device") + mount_text)
                idx = self.device_combo.count() - 1
                self.device_combo.setItemData(idx, device, Qt.ItemDataRole.UserRole)

            if self.ipod_devices:
                self.device_combo.setCurrentIndex(0)
                self.device_combo.blockSignals(False)
                device = self.ipod_devices[0]
                detected_mount = device.get("mount_point")
                already_mounted = device.get("mounted", False)
                self._start_mount_and_load(device, detected_mount, already_mounted)
            else:
                self.device_combo.blockSignals(False)
                self._set_device_not_found()
        else:
            self._set_device_not_found()

    def _start_mount_and_load(self, device: dict, mount_point=None, already_mounted=False):
        self.device_status_label.setText("Status: Mounting and loading tracks...")
        self.mount_button.setEnabled(False)

        self.worker_thread = WorkerThread(
            task_type="mount_and_load",
            device=device,
            mount_point=mount_point,
            already_mounted=already_mounted,
        )
        self.worker_thread.progress_signal.connect(self._on_mount_load_progress)
        self.worker_thread.finished_signal.connect(self._on_mount_load_finished)
        self.worker_thread.start()

    def _on_mount_load_progress(self, progress, status):
        self.device_info_label.setText(status)

    def _on_mount_load_finished(self, success, message, result):
        if success:
            device = result["device"]
            mount_point = result["mount_point"]
            device_info = result["device_info"]
            tracks = result["tracks"]

            self.current_mount_point = mount_point
            device["info"] = device_info

            idx = self.device_combo.currentIndex()
            if idx >= 0:
                name = device.get("name", "Unknown Device")
                self.device_combo.setItemText(idx, name + " [mounted]")
                self.device_combo.setItemData(idx, device, Qt.ItemDataRole.UserRole)

            free_space = device_info.get("free_space", 0)
            total_space = device_info.get("total_space", 0)

            self.device_info_label.setText(
                f"Device: {device['name']}  \u00B7  "
                f"Mount: {mount_point}  \u00B7  "
                f"Free: {free_space / 1024**2:.1f} MB / {total_space / 1024**2:.1f} MB"
            )
            self.device_status_label.setText("Status: Mounted and ready")
            self.device_status_label.setStyleSheet(
                f"color: {self.palette().color(self.palette().ColorRole.Highlight).name()}; "
                f"font-size: 11px; font-weight: bold; padding: 0 0 4px 0;"
            )
            self.mount_button.setEnabled(False)
            self.eject_button.setEnabled(True)

            self.device_mounted.emit(mount_point)

            self._populate_table(tracks)

            self.track_count_label.setText(f"Tracks: {len(tracks)}")
            self.export_button.setEnabled(len(tracks) > 0)
        else:
            self.device_status_label.setText(f"Status: {message}")
            self.device_status_label.setStyleSheet(
                f"color: {self.palette().color(self.palette().ColorRole.PlaceholderText).name()}; "
                f"font-size: 11px; padding: 0 0 4px 0;"
            )
            self.mount_button.setEnabled(True)
            if self.ipod_devices:
                self._set_device_unmounted(self.ipod_devices[0])
            else:
                self._set_device_not_found()

        self._cleanup_worker()

    def _populate_table(self, tracks: List[Dict]):
        self.track_table.setSortingEnabled(False)
        self.track_table.setRowCount(0)

        for track in tracks:
            row = self.track_table.rowCount()
            self.track_table.insertRow(row)

            track_num = track.get("track_number", 0) or 0
            duration_ms = track.get("duration_ms", 0) or 0
            file_size = track.get("file_size", 0) or 0
            play_count = track.get("play_count", 0) or 0

            num_item = NumericTableItem(str(track_num) if track_num else "")
            num_item.setData(Qt.ItemDataRole.UserRole, track)

            cells = [
                num_item,
                QTableWidgetItem(track.get("artist", "Unknown")),
                QTableWidgetItem(track.get("title", "Unknown")),
                QTableWidgetItem(track.get("album", "") or ""),
                QTableWidgetItem(self._format_duration(duration_ms)),
                QTableWidgetItem(track.get("genre", "") or ""),
                QTableWidgetItem(self._format_size(file_size)),
            ]

            for col, item in enumerate(cells):
                if col == 0:
                    continue
                self.track_table.setItem(row, col, item)

            self.track_table.setItem(row, 0, num_item)

        self.track_table.setSortingEnabled(True)
        self.ipod_search_input.clear()

    def _format_duration(self, duration_ms: int) -> str:
        if duration_ms <= 0:
            return "--:--"
        total_s = int(duration_ms / 1000)
        m, s = divmod(total_s, 60)
        return f"{m}:{s:02d}"

    def _format_size(self, size_bytes: int) -> str:
        if size_bytes <= 0:
            return ""
        if size_bytes < 1024 * 1024:
            return f"{size_bytes / 1024:.0f} KB"
        return f"{size_bytes / (1024 * 1024):.1f} MB"

    # ── Mount / Eject ─────────────────────────────────────────
    def _set_device_unmounted(self, device: dict):
        self.current_mount_point = None
        self.device_info_label.setText(
            f"Device: {device['name']}\nClick 'Mount' to connect"
        )
        self.device_status_label.setText("Status: Connected but not mounted")
        self.device_status_label.setStyleSheet(
            f"color: {self.palette().color(self.palette().ColorRole.PlaceholderText).name()}; "
            f"font-size: 11px; padding: 0 0 4px 0;"
        )
        self.mount_button.setEnabled(True)
        self.eject_button.setEnabled(False)
        self.track_count_label.setText("Tracks: 0")
        self.track_table.setRowCount(0)
        self.export_button.setEnabled(False)
        self.export_progress.setVisible(False)
        self.export_status.setVisible(False)

        self.device_unmounted.emit()

    def _set_device_not_found(self):
        self.current_mount_point = None
        self.device_info_label.setText("No iPod devices found")
        self.device_status_label.setText("Status: Not connected")
        self.device_status_label.setStyleSheet(
            f"color: {self.palette().color(self.palette().ColorRole.PlaceholderText).name()}; "
            f"font-size: 11px; padding: 0 0 4px 0;"
        )
        self.device_combo.clear()
        self.mount_button.setEnabled(False)
        self.eject_button.setEnabled(False)
        self.track_count_label.setText("Tracks: 0")
        self.track_table.setRowCount(0)
        self.export_button.setEnabled(False)
        self.export_progress.setVisible(False)
        self.export_status.setVisible(False)

        self.device_unmounted.emit()

    def _on_mount_clicked(self):
        idx = self.device_combo.currentIndex()
        if idx < 0:
            return
        device = self.device_combo.itemData(idx, Qt.ItemDataRole.UserRole)
        if not device:
            return
        detected_mount = device.get("mount_point")
        self._start_mount_and_load(device, detected_mount, already_mounted=False)

    def _on_eject_clicked(self):
        reply = QMessageBox.question(
            self, "Eject iPod",
            "Eject iPod? Make sure no transfers are in progress.",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        )
        if reply != QMessageBox.StandardButton.Yes:
            return

        self.device_status_label.setText("Status: Ejecting...")
        self.eject_button.setEnabled(False)

        try:
            ipod = IPodDevice(mount_point=self.current_mount_point)
            success = ipod.unmount_device()
            if success or True:
                self.current_mount_point = None
                if self.ipod_devices:
                    self.ipod_devices[0]["mounted"] = False
                    self.ipod_devices[0]["mount_point"] = None

                idx = self.device_combo.currentIndex()
                if idx >= 0:
                    name = self.ipod_devices[0].get("name", "Unknown Device")
                    self.device_combo.setItemText(idx, name + " [not mounted]")
                    self.device_combo.setItemData(idx, self.ipod_devices[0], Qt.ItemDataRole.UserRole)

                self.device_info_label.setText("iPod ejected. You can now disconnect it.")
                self.device_status_label.setText("Status: Ejected -- safe to disconnect")
                self.device_status_label.setStyleSheet(
                    f"color: {self.palette().color(self.palette().ColorRole.PlaceholderText).name()}; "
                    f"font-size: 11px; padding: 0 0 4px 0;"
                )
                self.mount_button.setEnabled(False)
                self.eject_button.setEnabled(False)
                self.track_count_label.setText("Tracks: 0")
                self.track_table.setRowCount(0)
                self.device_unmounted.emit()
            else:
                self.device_status_label.setText("Status: Eject failed")
                self.device_status_label.setStyleSheet(
                    f"color: {self.palette().color(self.palette().ColorRole.PlaceholderText).name()}; "
                    f"font-size: 11px; padding: 0 0 4px 0;"
                )
                self.eject_button.setEnabled(True)
                QMessageBox.warning(self, "Eject Error", "Failed to eject iPod safely.")
        except Exception as e:
            self.device_status_label.setText("Status: Eject error")
            self.device_status_label.setStyleSheet(
                f"color: {self.palette().color(self.palette().ColorRole.PlaceholderText).name()}; "
                f"font-size: 11px; padding: 0 0 4px 0;"
            )
            self.eject_button.setEnabled(True)
            QMessageBox.warning(self, "Eject Error", f"Error ejecting iPod: {e}")

    # ── Search / Filter ───────────────────────────────────────
    def _on_search_text_changed(self, text: str):
        query = text.lower()
        for row in range(self.track_table.rowCount()):
            if not query:
                self.track_table.setRowHidden(row, False)
                continue
            match = False
            for col in (1, 2, 3):
                item = self.track_table.item(row, col)
                if item and query in item.text().lower():
                    match = True
                    break
            self.track_table.setRowHidden(row, not match)

        visible = 0
        for row in range(self.track_table.rowCount()):
            if not self.track_table.isRowHidden(row):
                visible += 1
        total = self.track_table.rowCount()
        if query:
            self.track_count_label.setText(f"Tracks: {visible} / {total}")
        else:
            self.track_count_label.setText(f"Tracks: {total}")

    def _on_sort_changed(self, _column: int, _order: int):
        pass

    # ── Context Menu ──────────────────────────────────────────
    def _on_table_context_menu(self, pos):
        item = self.track_table.itemAt(pos)
        if item is None:
            return
        row = item.row()
        data_item = self.track_table.item(row, 0)
        if data_item is None:
            return
        track_data = data_item.data(Qt.ItemDataRole.UserRole)

        menu = QMenu(self)
        menu.setStyleSheet(f"""
            QMenu {{
                background: {self.palette().color(self.palette().ColorRole.Window).name()};
                border: 1px solid {self.palette().color(self.palette().ColorRole.Midlight).name()};
                border-radius: 4px;
                padding: 4px;
                font-size: 12px;
                color: {self.palette().color(self.palette().ColorRole.WindowText).name()};
            }}
            QMenu::item {{
                padding: 6px 24px;
                border-radius: 3px;
            }}
            QMenu::item:selected {{
                background: {self.palette().color(self.palette().ColorRole.Highlight).name()};
                color: {self.palette().color(self.palette().ColorRole.HighlightedText).name()};
            }}
        """)

        play_action = menu.addAction("\u25B6  Play")
        export_action = menu.addAction("\u2B07  Download to Computer...")
        menu.addSeparator()
        remove_action = menu.addAction("\U0001F5D1  Remove from iPod")

        action = menu.exec(self.track_table.viewport().mapToGlobal(pos))
        if action == play_action:
            if track_data and track_data.get("file_path") and os.path.exists(track_data["file_path"]):
                self._play_track(track_data)
        elif action == export_action:
            self._on_export_ipod_clicked()
        elif action == remove_action:
            self._on_remove_tracks_clicked()

    def _on_table_double_clicked(self, row: int):
        data_item = self.track_table.item(row, 0)
        if data_item is None:
            return
        track_data = data_item.data(Qt.ItemDataRole.UserRole)
        if track_data and track_data.get("file_path") and os.path.exists(track_data["file_path"]):
            self._play_track(track_data)

    def _play_track(self, track_data: dict):
        from PyQt6.QtMultimedia import QMediaPlayer
        path = track_data["file_path"]
        parent = self.parent()
        while parent is not None:
            if hasattr(parent, "player") and hasattr(parent, "player_header"):
                player: QMediaPlayer = parent.player
                player.setSource(QUrl.fromLocalFile(path))
                player.play()
                parent.player_header.set_playing(True)
                parent.player_header.set_track_info(
                    f"{track_data.get('artist', 'Unknown')}  \u2014  {track_data.get('title', 'Unknown')}"
                )
                break
            parent = parent.parent()

    # ── Load tracks ──────────────────────────────────────────
    def _load_ipod_tracks(self):
        if not self.current_mount_point:
            return

        try:
            from ipod_nano7_db import Nano7Database
            db = Nano7Database(self.current_mount_point)
            tracks = db.get_all_tracks()

            self._populate_table(tracks)

            self.track_count_label.setText(f"Tracks: {len(tracks)}")
            self.export_button.setEnabled(len(tracks) > 0)
        except Exception as e:
            logger.error(f"Failed to load iPod tracks: {e}")
            self.track_count_label.setText("Tracks: unknown")

    # ── Remove Tracks ─────────────────────────────────────────
    def _on_remove_tracks_clicked(self):
        selected_rows = set()
        for item in self.track_table.selectedItems():
            selected_rows.add(item.row())

        if not selected_rows:
            QMessageBox.information(self, "Info", "Select tracks to remove")
            return

        count = len(selected_rows)
        reply = QMessageBox.question(
            self, "Confirm Delete",
            f"Remove {count} track(s) from iPod?\nThis will permanently delete the files from the device.",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        )
        if reply != QMessageBox.StandardButton.Yes:
            return

        if self._is_worker_running():
            QMessageBox.information(self, "Info", "Please wait for the current task to finish")
            return

        pids = []
        for row in selected_rows:
            data_item = self.track_table.item(row, 0)
            if data_item is None:
                continue
            track_data = data_item.data(Qt.ItemDataRole.UserRole)
            if track_data and "pid" in track_data:
                pids.append(track_data["pid"])

        if not pids:
            QMessageBox.warning(self, "Error", "No valid tracks selected")
            return

        self._set_action_buttons_enabled(False)
        self.delete_progress.setVisible(True)
        self.delete_progress.setValue(0)
        self.delete_status.setVisible(True)
        self.delete_status.setText(f"Removing {len(pids)} track(s)...")

        self.worker_thread = WorkerThread(
            task_type="delete_tracks",
            pids=pids,
            mount_point=self.current_mount_point,
        )
        self.worker_thread.progress_signal.connect(self._on_delete_progress)
        self.worker_thread.finished_signal.connect(self._on_delete_finished)
        self.worker_thread.start()

    def _set_action_buttons_enabled(self, enabled: bool):
        self.remove_button.setEnabled(enabled)
        self.export_button.setEnabled(enabled)
        self.orphan_button.setEnabled(enabled)
        self.dedup_button.setEnabled(enabled)

    def _on_delete_progress(self, progress, status):
        self.delete_progress.setValue(progress)
        self.delete_status.setText(status)

    def _on_delete_finished(self, success, message, result):
        self._set_action_buttons_enabled(True)
        if success:
            self.delete_progress.setValue(100)
        self._load_ipod_tracks()
        QMessageBox.information(self, "Done" if success else "Error", message)
        self._cleanup_worker()

    # ── Export ─────────────────────────────────────────────────
    def _on_export_ipod_clicked(self):
        if not self.current_mount_point:
            QMessageBox.warning(self, "Error", "No iPod device mounted.")
            return

        selected_rows = set()
        for item in self.track_table.selectedItems():
            selected_rows.add(item.row())

        if not selected_rows:
            QMessageBox.information(self, "Info", "Select tracks to download")
            return

        dest_dir = QFileDialog.getExistingDirectory(self, "Select Download Destination")
        if not dest_dir:
            return

        tracks = []
        for row in selected_rows:
            data_item = self.track_table.item(row, 0)
            if data_item is None:
                continue
            data = data_item.data(Qt.ItemDataRole.UserRole)
            if data and data.get("file_path") and os.path.exists(data["file_path"]):
                tracks.append(data)

        if not tracks:
            QMessageBox.warning(self, "Error", "No valid track files found on device")
            return

        if self._is_worker_running():
            QMessageBox.information(self, "Info", "Please wait for the current task to finish")
            return

        self.export_button.setEnabled(False)
        self.export_progress.setVisible(True)
        self.export_progress.setValue(0)
        self.export_status.setVisible(True)
        self.export_status.setText(f"Exporting {len(tracks)} track(s)...")

        self.worker_thread = WorkerThread(
            task_type="export_ipod",
            tracks=tracks,
            dest_dir=dest_dir,
            mount_point=self.current_mount_point,
        )
        self.worker_thread.progress_signal.connect(self._on_export_ipod_progress)
        self.worker_thread.finished_signal.connect(self._on_export_ipod_finished)
        self.worker_thread.start()

    def _on_export_ipod_progress(self, progress, status):
        self.export_progress.setValue(progress)
        self.export_status.setText(status)

    def _on_export_ipod_finished(self, success, message, result):
        self.export_button.setEnabled(True)
        self.export_progress.setValue(100 if success else 0)

        if success:
            self.export_status.setText(message)
            QMessageBox.information(self, "Export Complete", message)
        else:
            self.export_status.setText(f"Error: {message}")
            QMessageBox.warning(self, "Export Error", message)

        self._cleanup_worker()

    # ── Orphans ────────────────────────────────────────────────
    def _on_scan_orphans_clicked(self):
        if not self.current_mount_point:
            QMessageBox.warning(self, "Error", "No iPod device mounted.")
            return

        try:
            from ipod_nano7_db import Nano7Database
            db = Nano7Database(self.current_mount_point)
            orphans = db.get_orphaned_files()

            if not orphans:
                QMessageBox.information(self, "Scan Complete", "No orphaned files found.")
                return

            total_size = sum(o["size"] for o in orphans)
            total_mb = total_size / (1024 * 1024)

            details = f"Found {len(orphans)} orphaned file(s) occupying {total_mb:.1f} MB:\n\n"
            for o in orphans[:20]:
                size_kb = o["size"] / 1024
                details += f"  {o['artist']} -- {o['title']} ({size_kb:.0f} KB)\n"
            if len(orphans) > 20:
                details += f"  ... and {len(orphans) - 20} more\n"

            reply = QMessageBox.question(
                self, "Orphaned Files Found",
                f"{details}\nDelete these files to free up space on iPod?",
                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
            )
            if reply == QMessageBox.StandardButton.Yes:
                deleted = db.delete_orphaned_files()
                QMessageBox.information(self, "Cleanup Complete", f"Deleted {deleted} orphaned file(s).")
                self._load_ipod_tracks()
        except Exception as e:
            logger.error(f"Failed to scan for orphaned files: {e}")
            QMessageBox.warning(self, "Error", f"Failed to scan: {e}")

    # ── Duplicates ─────────────────────────────────────────────
    def _on_remove_duplicates_clicked(self):
        if not self.current_mount_point:
            QMessageBox.warning(self, "Error", "No iPod device mounted.")
            return

        if self._is_worker_running():
            QMessageBox.information(self, "Info", "Please wait for the current task to finish")
            return

        self._set_action_buttons_enabled(False)
        self.delete_progress.setVisible(True)
        self.delete_progress.setValue(0)
        self.delete_status.setVisible(True)
        self.delete_status.setText("Scanning for duplicates...")

        self.worker_thread = WorkerThread(
            task_type="remove_duplicates",
            mount_point=self.current_mount_point,
        )
        self.worker_thread.progress_signal.connect(self._on_delete_progress)
        self.worker_thread.finished_signal.connect(self._on_delete_duplicates_finished)
        self.worker_thread.start()

    def _on_delete_duplicates_finished(self, success, message, result):
        self._set_action_buttons_enabled(True)
        self._load_ipod_tracks()
        if success:
            self.delete_progress.setValue(100)
        QMessageBox.information(self, "Duplicates" if success else "Error", message)
        self._cleanup_worker()

    # ── Monitoring ─────────────────────────────────────────────
    def start_monitoring(self, monitor: DeviceMonitor):
        monitor.state_changed.connect(self._on_auto_state_changed)

    def stop_monitoring(self, monitor: DeviceMonitor):
        try:
            monitor.state_changed.disconnect(self._on_auto_state_changed)
        except TypeError:
            pass

    def _on_auto_state_changed(self, devices):
        """Automatic device detection callback from the monitor timer.

        Updates the device combo silently and only mounts a device that
        is not already active.  Idempotent Play Counts processing in
        the worker prevents double‑counting even if the mount handler
        runs multiple times due to USB glitches.
        """
        if self._is_worker_running():
            return

        self._refresh_device_combo(devices)

        if not devices:
            return

        mp = devices[0].get("mount_point", "")

        if mp and mp == self.current_mount_point:
            return

        self._start_mount_and_load(
            devices[0],
            mount_point=mp,
            already_mounted=devices[0].get("mounted", False),
        )

    def _refresh_device_combo(self, devices):
        """Update the device combo box without triggering re‑mount."""
        self.device_combo.blockSignals(True)
        self.device_combo.clear()
        for device in devices:
            mounted = device.get("mounted", False)
            suffix = " [mounted]" if mounted else " [not mounted]"
            self.device_combo.addItem(device.get("name", "Unknown Device") + suffix)
            idx = self.device_combo.count() - 1
            self.device_combo.setItemData(idx, device, Qt.ItemDataRole.UserRole)
        if devices:
            self.device_combo.setCurrentIndex(0)
        self.device_combo.blockSignals(False)

    def _on_device_combo_changed(self, index):
        device = self.device_combo.itemData(index, Qt.ItemDataRole.UserRole)
        if not device:
            return
        mp = device.get("mount_point")
        if mp and mp == self.current_mount_point:
            return
        if self._is_worker_running():
            return
        self._start_mount_and_load(
            device,
            mount_point=mp,
            already_mounted=device.get("mounted", False),
        )

    def _cleanup_worker(self):
        if self.worker_thread:
            self.worker_thread.quit()
            self.worker_thread.wait()
            self.worker_thread = None

    def _is_worker_running(self):
        if self.worker_thread:
            try:
                return self.worker_thread.isRunning()
            except RuntimeError:
                self.worker_thread = None
        return False
