"""Custom PyQt6 widgets for EuBI-Bridge GUI."""

from typing import Callable, Optional, List, Tuple
from PyQt6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, QSlider, QLabel, QSpinBox,
    QPushButton, QColorDialog, QComboBox, QCheckBox, QLineEdit,
    QListWidget, QListWidgetItem, QAbstractItemView, QStyledItemDelegate,
    QStyle, QHeaderView, QTableWidget, QTableWidgetItem, QDialog,
    QFileDialog, QTreeWidget, QTreeWidgetItem, QProgressBar, QScrollArea,
    QSizePolicy
)
from PyQt6.QtCore import Qt, pyqtSignal, QSize, QThread
from PyQt6.QtGui import QIcon, QColor, QPixmap, QFont
import logging

logger = logging.getLogger(__name__)


class LabeledSlider(QWidget):
    """Slider with label and spinbox for precise input."""
    
    valueChanged = pyqtSignal(int)
    
    def __init__(
        self,
        label: str,
        min_val: int = 0,
        max_val: int = 100,
        initial: int = 0,
        parent=None
    ):
        super().__init__(parent)
        self.min_val = min_val
        self.max_val = max_val
        
        layout = QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        
        # Label
        self.label = QLabel(label)
        self.label.setMinimumWidth(80)
        layout.addWidget(self.label)
        
        # Slider
        self.slider = QSlider(Qt.Orientation.Horizontal)
        self.slider.setMinimum(min_val)
        self.slider.setMaximum(max_val)
        self.slider.setValue(initial)
        self.slider.setTickPosition(QSlider.TickPosition.TicksBelow)
        layout.addWidget(self.slider)
        
        # Spinbox
        self.spinbox = QSpinBox()
        self.spinbox.setMinimum(min_val)
        self.spinbox.setMaximum(max_val)
        self.spinbox.setValue(initial)
        self.spinbox.setMaximumWidth(80)
        layout.addWidget(self.spinbox)
        
        # Connect signals
        self.slider.valueChanged.connect(self._on_slider_change)
        self.spinbox.valueChanged.connect(self._on_spinbox_change)
        self.setLayout(layout)
    
    def _on_slider_change(self, value):
        self.spinbox.blockSignals(True)
        self.spinbox.setValue(value)
        self.spinbox.blockSignals(False)
        self.valueChanged.emit(value)
    
    def _on_spinbox_change(self, value):
        self.slider.blockSignals(True)
        self.slider.setValue(value)
        self.slider.blockSignals(False)
        self.valueChanged.emit(value)
    
    def value(self) -> int:
        return self.slider.value()
    
    def setValue(self, value: int):
        self.slider.blockSignals(True)
        self.spinbox.blockSignals(True)
        self.slider.setValue(value)
        self.spinbox.setValue(value)
        self.slider.blockSignals(False)
        self.spinbox.blockSignals(False)


class ColorPicker(QWidget):
    """Color picker widget with hex display."""
    
    colorChanged = pyqtSignal(str)  # Emits hex color
    
    def __init__(self, label: str = "Color", initial_color: str = "#FFFFFF", parent=None):
        super().__init__(parent)
        self.current_color = initial_color
        
        layout = QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        
        # Label
        if label:
            lbl = QLabel(label)
            lbl.setMinimumWidth(80)
            layout.addWidget(lbl)
        
        # Color button (shows current color)
        self.color_button = QPushButton()
        self.color_button.setMaximumWidth(50)
        self.color_button.setMinimumHeight(30)
        self._update_button_color()
        self.color_button.clicked.connect(self._show_color_dialog)
        layout.addWidget(self.color_button)
        
        # Hex input
        self.hex_input = QLineEdit()
        self.hex_input.setText(initial_color)
        self.hex_input.setMaximumWidth(100)
        self.hex_input.editingFinished.connect(self._on_hex_edited)
        layout.addWidget(self.hex_input)
        
        layout.addStretch()
        self.setLayout(layout)
    
    def _update_button_color(self):
        qcolor = QColor(self.current_color)
        pixmap = QPixmap(30, 30)
        pixmap.fill(qcolor)
        self.color_button.setIcon(QIcon(pixmap))
    
    def _show_color_dialog(self):
        color = QColorDialog.getColor(QColor(self.current_color), self)
        if color.isValid():
            self.set_color(color.name())
    
    def _on_hex_edited(self):
        text = self.hex_input.text()
        if QColor(text).isValid():
            self.set_color(text)
    
    def set_color(self, hex_color: str):
        if QColor(hex_color).isValid():
            self.current_color = hex_color
            self._update_button_color()
            self.hex_input.setText(hex_color)
            self.colorChanged.emit(hex_color)
    
    def color(self) -> str:
        return self.current_color


class RangeSlider(QWidget):
    """Dual-slider for min/max range selection."""
    
    rangeChanged = pyqtSignal(float, float)
    
    def __init__(
        self,
        label: str,
        min_val: float = 0.0,
        max_val: float = 100.0,
        initial_min: Optional[float] = None,
        initial_max: Optional[float] = None,
        parent=None
    ):
        super().__init__(parent)
        
        if initial_min is None:
            initial_min = min_val
        if initial_max is None:
            initial_max = max_val
        
        layout = QVBoxLayout(self)
        
        # Label
        lbl = QLabel(label)
        layout.addWidget(lbl)
        
        # Range display
        range_layout = QHBoxLayout()
        range_layout.addWidget(QLabel("Min:"))
        self.min_input = QLineEdit(f"{initial_min:.2f}")
        self.min_input.setMaximumWidth(80)
        range_layout.addWidget(self.min_input)
        
        range_layout.addSpacing(20)
        range_layout.addWidget(QLabel("Max:"))
        self.max_input = QLineEdit(f"{initial_max:.2f}")
        self.max_input.setMaximumWidth(80)
        range_layout.addWidget(self.max_input)
        range_layout.addStretch()
        layout.addLayout(range_layout)
        
        self.min_input.editingFinished.connect(self._on_values_changed)
        self.max_input.editingFinished.connect(self._on_values_changed)
        
        self.setLayout(layout)
    
    def _on_values_changed(self):
        try:
            min_val = float(self.min_input.text())
            max_val = float(self.max_input.text())
            self.rangeChanged.emit(min_val, max_val)
        except ValueError:
            pass
    
    def set_range(self, min_val: float, max_val: float):
        self.min_input.setText(f"{min_val:.2f}")
        self.max_input.setText(f"{max_val:.2f}")
    
    def range(self) -> Tuple[float, float]:
        try:
            return float(self.min_input.text()), float(self.max_input.text())
        except ValueError:
            return 0.0, 100.0


class EfficientFileList(QWidget):
    """
    Efficient file list for large directories using lazy loading and pagination.
    Implements fast directory scanning without blocking UI.
    """
    
    fileSelected = pyqtSignal(str)  # Emits selected file path
    
    def __init__(self, parent=None):
        super().__init__(parent)
        
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)  # No outer margins
        layout.setSpacing(5)  # Minimal spacing
        
        # Search/filter
        search_layout = QHBoxLayout()
        search_layout.addWidget(QLabel("Filter:"))
        self.search_input = QLineEdit()
        self.search_input.setPlaceholderText("Type to filter...")
        self.search_input.textChanged.connect(self._on_filter_changed)
        search_layout.addWidget(self.search_input)
        layout.addLayout(search_layout)
        
        # File list (pagination moved to parent)
        self.file_list = QListWidget()
        self.file_list.itemDoubleClicked.connect(self._on_item_selected)
        # Enable uniform item sizes for consistent height calculation
        self.file_list.setUniformItemSizes(True)
        # Set minimum height for ~8 items, but allow expansion
        self.file_list.setMinimumHeight(8 * 35 + 5)  # 8 items at 35px each + 5px for border
        layout.addWidget(self.file_list, 1)  # stretch factor 1 to expand with parent
        
        self.setLayout(layout)
        
        self._items_per_page = 30
        self._all_items = []
        self._filtered_items = []
        self._current_page = 1
        self.page_spinbox = None  # Will be set by parent via set_pagination_spinbox()
        self.total_pages_label = None  # Will be set by parent via set_pagination_spinbox()

        self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
        self.file_list.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
    
    def set_pagination_spinbox(self, spinbox: QSpinBox, label: QLabel):
        """Set pagination controls that are managed by parent."""
        self.page_spinbox = spinbox
        self.total_pages_label = label
        self.page_spinbox.valueChanged.connect(self._on_page_changed)
    
    def set_items(self, items: List[str]):
        """Set list of items (e.g., file paths)."""
        self._all_items = sorted(items)
        self._filtered_items = self._all_items.copy()
        self._current_page = 1
        self._update_display()
    
    def _on_filter_changed(self, text: str):
        filter_text = text.lower()
        if filter_text:
            self._filtered_items = [
                item for item in self._all_items
                if filter_text in item.lower()
            ]
        else:
            self._filtered_items = self._all_items.copy()
        
        self._current_page = 1
        self._update_display()
    
    def _on_page_changed(self, page: int):
        self._current_page = page
        self._update_display()
    
    def _update_display(self):
        self.file_list.clear()
        
        if not self._filtered_items:
            return
        
        # Calculate pagination
        total_pages = (len(self._filtered_items) + self._items_per_page - 1) // self._items_per_page
        self._current_page = max(1, min(self._current_page, total_pages))
        
        if self.page_spinbox:
            self.page_spinbox.blockSignals(True)
            self.page_spinbox.setMaximum(max(1, total_pages))
            self.page_spinbox.setValue(self._current_page)
            self.page_spinbox.blockSignals(False)
        
        if self.total_pages_label:
            self.total_pages_label.setText(f"/ {total_pages}")
        
        # Get items for current page
        start = (self._current_page - 1) * self._items_per_page
        end = start + self._items_per_page
        page_items = self._filtered_items[start:end]
        
        # Display items
        for item in page_items:
            self.file_list.addItem(item)
    
    def _on_item_selected(self, item: QListWidgetItem):
        self.fileSelected.emit(item.text())
    
    def selected_item(self) -> Optional[str]:
        current = self.file_list.currentItem()
        return current.text() if current else None


class PanableImageLabel(QLabel):
    """QLabel that emits signals for mouse wheel and drag events."""
    
    # Signal emitted when user scrolls mouse wheel
    zIndexChangeRequested = pyqtSignal(int)
    
    # Signal emitted when user drags with mouse
    panRequested = pyqtSignal(int, int)  # delta_x, delta_y
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self._drag_start_pos = None
        self._is_panning = False
    
    def wheelEvent(self, event):
        """Handle mouse wheel events for Z-slice navigation."""
        delta = event.angleDelta().y()
        if delta != 0:
            direction = 1 if delta > 0 else -1
            self.zIndexChangeRequested.emit(direction)
            event.accept()
        else:
            event.ignore()
    
    def mousePressEvent(self, event):
        """Handle mouse press - start pan drag."""
        if event.button() == Qt.MouseButton.LeftButton:
            self._drag_start_pos = event.pos()
            self._is_panning = True
            event.accept()
        else:
            super().mousePressEvent(event)
    
    def mouseMoveEvent(self, event):
        """Handle mouse move - pan the view."""
        if self._is_panning and self._drag_start_pos is not None:
            current_pos = event.pos()
            delta = current_pos - self._drag_start_pos
            
            # Emit pan request with screen pixel deltas
            self.panRequested.emit(delta.x(), delta.y())
            
            # Update drag start position for next move
            self._drag_start_pos = current_pos
            event.accept()
        else:
            super().mouseMoveEvent(event)
    
    def mouseReleaseEvent(self, event):
        """Handle mouse release - end pan drag."""
        if event.button() == Qt.MouseButton.LeftButton:
            self._is_panning = False
            self._drag_start_pos = None
            event.accept()
        else:
            super().mouseReleaseEvent(event)


class CachedImageDisplay(QWidget):
    """Display image with simple caching and mouse wheel support for Z navigation."""
    
    # Signal emitted when user scrolls mouse wheel
    # Args: delta (positive = scroll up/next z, negative = scroll down/prev z)
    zIndexChangeRequested = pyqtSignal(int)
    
    # Signal emitted when user pans the image with mouse drag
    # Args: delta_x, delta_y (screen pixel deltas that need to be converted to data space)
    panRequested = pyqtSignal(int, int)  # delta_x, delta_y
    
    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QVBoxLayout(self)
        
        # Use PanableImageLabel instead of regular QLabel
        self.label = PanableImageLabel()
        self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.label.setMinimumSize(256, 256)
        self.label.setStyleSheet("border: 1px solid #ccc;")
        self.label.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        
        # Connect label's signals to our signals
        self.label.zIndexChangeRequested.connect(self.zIndexChangeRequested.emit)
        self.label.panRequested.connect(self.panRequested.emit)
        
        layout.addWidget(self.label)
        
        self._current_pixmap = None
        
        self.setLayout(layout)
        self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
    
    def set_pixmap(self, pixmap: QPixmap):
        """Set and display image.
        
        If image is larger than 256px in any dimension, scale it down to fit.
        If image is smaller, display at true size.
        """
        self._current_pixmap = pixmap
        
        # Only scale if the image is larger than 256px
        if pixmap.width() > 256 or pixmap.height() > 256:
            # Scale to fit within 256x256 while maintaining aspect ratio
            scaled = pixmap.scaledToWidth(256, Qt.TransformationMode.SmoothTransformation) \
                if pixmap.width() > pixmap.height() \
                else pixmap.scaledToHeight(256, Qt.TransformationMode.SmoothTransformation)
            self.label.setPixmap(scaled)
        else:
            # Display at true size if smaller than 256
            self.label.setPixmap(pixmap)
    
    def set_text(self, text: str):
        """Display text message."""
        self._current_pixmap = None
        self.label.setText(text)
    
    def pixmap(self) -> Optional[QPixmap]:
        """Get cached pixmap."""
        return self._current_pixmap


class LoadingIndicator(QWidget):
    """Simple loading/progress indicator."""
    
    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QVBoxLayout(self)
        
        self.label = QLabel("Loading...")
        self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(self.label)
        
        self.progress = QProgressBar()
        self.progress.setMaximum(0)  # Indeterminate progress
        layout.addWidget(self.progress)
        
        self.setLayout(layout)
    
    def set_message(self, text: str):
        self.label.setText(text)
    
    def start(self):
        self.progress.setMaximum(0)
        self.show()
    
    def stop(self):
        self.hide()
