from __future__ import annotations

from typing import TYPE_CHECKING

from qtpy.QtCore import Qt
from qtpy.QtWidgets import (
    QComboBox,
    QHBoxLayout,
    QPushButton,
    QWidget,
)
from superqt import QLabeledDoubleSlider

from napari._qt.layer_controls.dynamic.widgets.qt_widget_controls_base import (
    QtWidgetControlsBase,
    QtWrappedLabel,
)
from napari._qt.utils import qt_signals_blocked
from napari.layers.image._image_constants import VolumeDepiction
from napari.layers.image._image_key_bindings import (
    orient_plane_normal_along_view_direction_no_gen,
    orient_plane_normal_along_x,
    orient_plane_normal_along_y,
    orient_plane_normal_along_z,
)

if TYPE_CHECKING:
    from napari.layers import Image


class PlaneNormalButtons(QWidget):
    """Qt buttons for controlling plane orientation.

    Attributes
    ----------
    x_button : qtpy.QtWidgets.QPushButton
        Button which orients a plane normal along the x axis.
    y_button : qtpy.QtWidgets.QPushButton
        Button which orients a plane normal along the y axis.
    z_button : qtpy.QtWidgets.QPushButton
        Button which orients a plane normal along the z axis.
    oblique_button : qtpy.QtWidgets.QPushButton
        Button which orients a plane normal along the camera view direction.
    """

    def __init__(
        self, layers: list[Image], parent: QWidget | None = None
    ) -> None:
        super().__init__(parent=parent)
        self._layers = layers
        layout = QHBoxLayout()
        self.setLayout(layout)
        layout.setSpacing(2)
        layout.setContentsMargins(0, 0, 0, 0)

        self.x_button = QPushButton('x')
        self.y_button = QPushButton('y')
        self.z_button = QPushButton('z')
        self.oblique_button = QPushButton('oblique')
        self.x_button.clicked.connect(self.update_plane_normal_x)
        self.y_button.clicked.connect(self.update_plane_normal_y)
        self.z_button.clicked.connect(self.update_plane_normal_z)
        self.oblique_button.clicked.connect(self.update_plane_normal_oblique)

        layout.addWidget(self.x_button)
        layout.addWidget(self.y_button)
        layout.addWidget(self.z_button)
        layout.addWidget(self.oblique_button)

    def update_plane_normal_x(self):
        for layer in self._layers:
            orient_plane_normal_along_x(layer)

    def update_plane_normal_y(self):
        for layer in self._layers:
            orient_plane_normal_along_y(layer)

    def update_plane_normal_z(self):
        for layer in self._layers:
            orient_plane_normal_along_z(layer)

    def update_plane_normal_oblique(self):
        for layer in self._layers:
            orient_plane_normal_along_view_direction_no_gen(layer)


class QtDepictionControl(QtWidgetControlsBase):
    """
    Class that wraps the connection of events/signals between the layer depection
    and plane value attributes and Qt widgets.

    Parameters
    ----------
    parent: qtpy.QtWidgets.QWidget
        An instance of QWidget that will be used as widgets parent
    layers : list[napari.layers.Image]
        A list of napari Image layers.

    Attributes
    ----------
    depiction_combobox : qtpy.QtWidgets.QComboBox
        ComboBox controlling current depiction value of the layer.
    depiction_label : napari._qt.layer_controls.widgets.qt_widget_controls_base.QtWrappedLabel
        Label for the depiction value chooser widget.
    plane_normal_buttons : PlaneNormalButtons
        Buttons controlling plane normal orientation when the `plane` depiction value is choosed.
    plane_normal_label : napari._qt.layer_controls.widgets.qt_widget_controls_base.QtWrappedLabel
        Label for the plane normal value chooser widget.
    plane_thickness_slider : superqt.QLabeledDoubleSlider
        Slider controlling plane normal thickness when the `plane` depiction value is choosed.
    plane_thickness_label : napari._qt.layer_controls.widgets.qt_widget_controls_base.QtWrappedLabel
        Label for the plane normal thickness value chooser widget.
    """

    _layers: list[Image]

    def __init__(
        self, layers: list[Image], parent: QWidget | None = None
    ) -> None:
        super().__init__(layers=layers, parent=parent)
        self._ndisplay = 2
        # Setup layer
        for layer in self._layers:
            layer.events.depiction.connect(self._on_depiction_change)
            layer.plane.events.thickness.connect(
                self._on_plane_thickness_change
            )

        # Setup widgets
        self.depiction_combobox = QComboBox(parent)
        depiction_options = [d.value for d in VolumeDepiction]
        self.depiction_combobox.addItems(depiction_options)
        index = self.depiction_combobox.findText(
            self._layers[0].depiction, Qt.MatchFlag.MatchFixedString
        )
        self.depiction_combobox.setCurrentIndex(index)
        self.depiction_combobox.currentTextChanged.connect(
            self.change_depiction
        )
        self.depiction_label = QtWrappedLabel('depiction:')

        # plane controls

        self.plane_normal_buttons = PlaneNormalButtons(
            parent=parent, layers=self._layers
        )
        self.plane_normal_label = QtWrappedLabel('plane normal:')

        self.plane_thickness_slider = QLabeledDoubleSlider(
            Qt.Orientation.Horizontal, parent
        )
        self.plane_thickness_slider.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        self.plane_thickness_slider.setMinimum(1)
        self.plane_thickness_slider.setMaximum(50)
        self.plane_thickness_slider.setValue(self._layers[0].plane.thickness)
        self.plane_thickness_slider.valueChanged.connect(
            self.change_plane_thickness
        )
        self.plane_thickness_label = QtWrappedLabel('plane thickness:')
        self._update_plane_parameter_visibility()

    def _change_ndisplay(self, ndisplay: int) -> None:
        self._ndisplay = ndisplay
        self._update_plane_parameter_visibility()
        if self._ndisplay == 3:
            self.depiction_combobox.show()
            self.depiction_label.show()
        else:
            self.depiction_combobox.hide()
            self.depiction_label.hide()

    def change_depiction(self, text: str) -> None:
        for layer in self._layers:
            layer.depiction = text
        self._update_plane_parameter_visibility()

    def change_plane_thickness(self, value: float) -> None:
        for layer in self._layers:
            layer.plane.thickness = value

    def _on_depiction_change(self) -> None:
        """Receive layer model depiction change event and update combobox."""
        with qt_signals_blocked(self.depiction_combobox):
            index = self.depiction_combobox.findText(
                self._layers[0].depiction, Qt.MatchFlag.MatchFixedString
            )
            self.depiction_combobox.setCurrentIndex(index)
            self._update_plane_parameter_visibility()

    def _on_plane_thickness_change(self) -> None:
        with qt_signals_blocked(self.plane_thickness_slider):
            self.plane_thickness_slider.setValue(
                self._layers[0].plane.thickness
            )

    def _update_plane_parameter_visibility(self) -> None:
        """Hide plane rendering controls if they aren't needed."""
        depiction = VolumeDepiction(self._layers[0].depiction)
        visible = (
            depiction == VolumeDepiction.PLANE
            and self._ndisplay == 3
            and self._layers[0].ndim >= 3
        )
        self.plane_normal_buttons.setVisible(visible)
        self.plane_normal_label.setVisible(visible)
        self.plane_thickness_slider.setVisible(visible)
        self.plane_thickness_label.setVisible(visible)

    def get_widget_controls(self) -> list[tuple[QtWrappedLabel, QWidget]]:
        return [
            (self.depiction_label, self.depiction_combobox),
            (self.plane_normal_label, self.plane_normal_buttons),
            (self.plane_thickness_label, self.plane_thickness_slider),
        ]
