# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.

"""Signal horizontal or vertical cursor selection dialog."""

# pylint: disable=invalid-name  # Allows short reference names like x, y, ...

from __future__ import annotations

from typing import TYPE_CHECKING, Literal

import numpy as np
from guidata.configtools import get_icon
from plotpy.builder import make
from plotpy.plot import PlotDialog
from qtpy import QtCore as QC
from qtpy import QtGui as QG
from qtpy import QtWidgets as QW
from sigima.tools.signal.features import find_x_values_at_y

from datalab.adapters_plotpy import CURVESTYLES, create_adapter_from_object
from datalab.config import _
from datalab.utils.qthelpers import block_signals, resize_widget_to_parent

if TYPE_CHECKING:
    from plotpy.items import CurveItem, Marker
    from qtpy.QtWidgets import QWidget
    from sigima.objects import SignalObj


class SignalCursorDialog(PlotDialog):
    """Signal horizontal or vertical cursor selection dialog.

    Args:
        signal: signal object
        parent: parent widget. Defaults to None.
    """

    def __init__(
        self,
        signal: SignalObj,
        cursor_orientation: Literal["horizontal", "vertical"],
        parent: QWidget | None = None,
    ) -> None:
        assert cursor_orientation in (
            "horizontal",
            "vertical",
        ), "cursor_orientation must be 'horizontal' or 'vertical'"
        self.__curve_styles = CURVESTYLES.style_generator()
        self.__cursor_orientation = cursor_orientation
        self.__signal = signal
        self.__x_value: float | None = None
        self.__y_value: float | None = None
        self.curve: CurveItem | None = None
        self.hcursor: Marker | None = None
        self.vcursor: Marker | None = None
        self.xlineedit: QW.QLineEdit | None = None
        self.ylineedit: QW.QLineEdit | None = None
        if cursor_orientation == "horizontal":
            title = _("Select X value with cursor")
        else:
            title = _("Select Y value with cursor")
        super().__init__(title=title, edit=True, parent=parent)
        self.setObjectName("SignalCursorDialog")
        if parent is None:
            self.setWindowIcon(get_icon("DataLab.svg"))
        legend = make.legend("TR")
        self.get_plot().add_item(legend)
        self.__setup_dialog()
        resize_widget_to_parent(self, aspect_ratio=1.0)

    def __setup_dialog(self) -> None:
        """Setup dialog box"""
        apply_button = QW.QPushButton(_("Apply"))
        apply_button.setIcon(get_icon("apply.svg"))
        apply_button.setToolTip(_("Apply cursor position"))
        xlabel = QW.QLabel("X=")
        ylabel = QW.QLabel("Y=")
        self.xlineedit = QW.QLineEdit()
        self.xlineedit.editingFinished.connect(self.xlineedit_editing_finished)
        x_validator = QG.QDoubleValidator()
        x_validator.setLocale(QC.QLocale("C"))
        self.xlineedit.setValidator(x_validator)
        self.ylineedit = QW.QLineEdit()
        self.ylineedit.editingFinished.connect(self.ylineedit_editing_finished)
        y_validator = QG.QDoubleValidator()
        y_validator.setLocale(QC.QLocale("C"))
        self.ylineedit.setValidator(y_validator)
        self.xlineedit.setReadOnly(self.__cursor_orientation == "horizontal")
        self.xlineedit.setDisabled(self.__cursor_orientation == "horizontal")
        self.ylineedit.setReadOnly(self.__cursor_orientation == "vertical")
        self.ylineedit.setDisabled(self.__cursor_orientation == "vertical")
        xygroup = QW.QGroupBox(_("Cursor position"))
        xylayout = QW.QHBoxLayout()
        xylayout.addWidget(xlabel)
        xylayout.addWidget(self.xlineedit)
        if self.__cursor_orientation == "vertical":
            xylayout.addWidget(apply_button)
            apply_button.clicked.connect(self.xlineedit_editing_finished)
            xylayout.addStretch()
            xylayout.addSpacing(10)
        xylayout.addWidget(ylabel)
        xylayout.addWidget(self.ylineedit)
        if self.__cursor_orientation == "horizontal":
            xylayout.addWidget(apply_button)
            apply_button.clicked.connect(self.ylineedit_editing_finished)
        xygroup.setLayout(xylayout)
        self.button_layout.insertWidget(0, xygroup)

        obj = self.__signal
        with CURVESTYLES.alternative(self.__curve_styles):
            self.curve = create_adapter_from_object(obj).make_item()
        plot = self.get_plot()
        plot.set_antialiasing(True)

        xcursor = make.xcursor(np.mean(obj.x), np.mean(obj.y), "X = %g, Y = %g")
        xcursor.set_selectable(False)
        param = xcursor.markerparam
        param.symbol.facecolor = "blue"
        param.symbol.edgecolor = "cyan"
        param.symbol.size = 9
        param.line.style = "DotLine"
        param.line.color = "blue"
        param.line.width = 2.0
        param.update_item(xcursor)

        plot.SIG_MARKER_CHANGED.connect(self.cursor_changed)
        if self.__cursor_orientation == "horizontal":
            self.hcursor = make.hcursor(np.mean(obj.y), "Y = %g")
            self.vcursor = xcursor
            self.vcursor.setVisible(False)
        else:
            self.vcursor = make.vcursor(np.mean(obj.x), "X = %g")
            self.hcursor = xcursor
            self.hcursor.setVisible(False)
        for item in (self.curve, self.vcursor, self.hcursor):
            plot.add_item(item)
        plot.replot()
        if self.__cursor_orientation == "horizontal":
            plot.set_active_item(self.hcursor)
            self.cursor_changed(self.hcursor)
        else:
            plot.set_active_item(self.vcursor)
            self.cursor_changed(self.vcursor)

    def cursor_changed(self, item: Marker) -> None:
        """Cursor changed"""
        sig = self.__signal
        plot = self.get_plot()
        if self.__cursor_orientation == "horizontal" and item is self.hcursor:
            _x, y = item.get_pos()
            x = None
            x_values = find_x_values_at_y(sig.x, sig.y, y)
            if len(x_values) > 0:
                x = x_values[0]
                with block_signals(plot):
                    self.vcursor.set_pos(x, y)
            self.vcursor.setVisible(x is not None)
            self.button_box.button(QW.QDialogButtonBox.Ok).setEnabled(x is not None)
        elif self.__cursor_orientation == "vertical" and item is self.vcursor:
            x, _y = item.get_pos()
            y_index = np.searchsorted(self.__signal.x, x)
            if x < self.__signal.x[0] or y_index >= len(self.__signal.y):
                y = None
            else:
                y = self.__signal.y[y_index]
                with block_signals(plot):
                    self.hcursor.set_pos(x, y)
                self.hcursor.setVisible(True)
            self.hcursor.setVisible(y is not None)
            self.button_box.button(QW.QDialogButtonBox.Ok).setEnabled(y is not None)
        self.xlineedit.setText(f"{x:g}" if x is not None else "")
        self.ylineedit.setText(f"{y:g}" if y is not None else "")
        self.__x_value, self.__y_value = x, y

    def xlineedit_editing_finished(self) -> None:
        """X line edit editing finished"""
        try:
            x = float(self.xlineedit.text())
            _x, y = self.vcursor.get_pos()
            if self.__cursor_orientation == "horizontal":
                self.hcursor.set_pos(x, y)
            else:
                self.vcursor.set_pos(x, y)
        except ValueError:
            pass
        plot = self.get_plot()
        plot.replot()

    def ylineedit_editing_finished(self) -> None:
        """Y line edit editing finished"""
        try:
            y = float(self.ylineedit.text())
            x, _y = self.hcursor.get_pos()
            if self.__cursor_orientation == "horizontal":
                self.hcursor.set_pos(x, y)
            else:
                self.vcursor.set_pos(x, y)
        except ValueError:
            pass
        plot = self.get_plot()
        plot.replot()

    def get_cursor_position(self) -> tuple[float, float]:
        """Get cursor position"""
        return self.__x_value, self.__y_value

    def get_x_value(self) -> float:
        """Get cursor x value"""
        return self.__x_value

    def get_y_value(self) -> float:
        """Get cursor y value"""
        return self.__y_value
