from PyQt6.QtWidgets import (
    QLabel,
    QWidget,
    QLineEdit,
    QHBoxLayout,
    QSlider,
)
from PyQt6.QtCore import Qt

import abc
import typing


class DisplayField(QWidget):
    label_text: str

    def __init__(self, parent, label_text="", text=""):
        super().__init__(parent)

        self._layout = QHBoxLayout(self)
        self._layout.setContentsMargins(0, 0, 0, 0)

        self.label_text = label_text
        self.text = QLabel(f"{self.label_text}{text}")
        self.text.setAlignment(Qt.AlignmentFlag.AlignLeft)

        self._layout.addWidget(self.text)

    def set_visible(self, visible):
        self.text.setVisible(visible)

    def set_text(self, text):
        self.text.setText(f"{self.label_text}{text}")


class InputWidget(QWidget):
    value: typing.Any
    previous_value: typing.Any
    callback: typing.Callable[[typing.Self], bool] | None = None

    def init(self):
        self.value = self._get_value_internal()
        self.previous_value = self.value
        self._set_callback_internal(self._on_change_internal)

    def get_value(self) -> typing.Any:
        return self.value

    def get_previous_value(self) -> typing.Any:
        return self.previous_value

    def set_value(self, value: typing.Any):
        self._set_value_internal(value)
        self._set_value_no_sync(value)

    def set_callback(self, callback: typing.Callable[[typing.Self], bool]):
        self.callback = callback

    @abc.abstractmethod
    def _get_value_internal(self) -> typing.Any:
        pass

    @abc.abstractmethod
    def _set_value_internal(self, value: typing.Any):
        pass

    @abc.abstractmethod
    def _set_callback_internal(self, callback: typing.Callable[[], None]):
        pass

    def _set_value_no_sync(self, value: typing.Any):
        self.previous_value = self.value
        self.value = value

    def _on_change_internal(self):
        self._set_value_no_sync(self._get_value_internal())

        if self.callback:
            self.callback(self)


def make_callback_chain(
    callbacks: typing.List[typing.Callable[[InputWidget], bool]]
) -> typing.Callable[[InputWidget], bool]:
    def callback(widget: InputWidget):
        for callback in callbacks:
            if not callback(widget):
                break
        return True

    return callback


def make_min_max_callback(
    min: typing.Any, max: typing.Any
) -> typing.Callable[[InputWidget], bool]:
    def callback(widget: InputWidget):
        value = widget.get_value()

        if type(min)(value) < min:
            widget.set_value(min)
        if type(max)(value) > max:
            widget.set_value(max)

        return True

    return callback


def float_check_callback(widget: InputWidget):
    try:
        internal_value = widget._get_value_internal()
        if not (internal_value == "" or internal_value == "-" or internal_value == "."):
            _ = float(widget.get_value())
            return True
        else:
            widget.value = widget.previous_value
            return False
    except:
        widget.set_value(widget.previous_value)
        return False


def int_check_callback(widget: InputWidget):
    try:
        internal_value = widget._get_value_internal()
        if not (internal_value == "" or internal_value == "-"):
            _ = int(widget.get_value())
            return True
        else:
            widget.value = widget.previous_value
            return False
    except:
        widget.set_value(widget.previous_value)
        return False


class InputField(InputWidget):
    def __init__(
        self,
        parent,
        label_text="",
        placeholder_text="",
        lineedit_text="",
        label_position="left",
    ):
        super().__init__(parent)

        self._layout = QHBoxLayout(self)
        self._layout.setContentsMargins(0, 0, 0, 0)
        self.label = QLabel(label_text)
        self.lineedit = QLineEdit(lineedit_text)
        self.lineedit.setPlaceholderText(placeholder_text)
        self.lineedit.setMinimumWidth(50)

        if label_position == "left":
            self._layout.addWidget(self.label)
            self._layout.addWidget(self.lineedit)
        elif label_position == "right":
            self._layout.addWidget(self.lineedit)
            self._layout.addWidget(self.label)
        elif label_position == "none":
            self._layout.addWidget(self.lineedit)

        super().init()
        super().set_value(lineedit_text)

    def _get_value_internal(self):
        return self.lineedit.text()

    def _set_value_internal(self, value):
        self.lineedit.setText(str(value))

    def _set_callback_internal(self, callback):
        self.lineedit.textChanged.connect(callback)


class InputSlider(InputWidget):
    slider: QSlider

    def __init__(
        self,
        parent,
        label_text="",
        value=0,
        min_value=0,
        max_value=100,
        step=1,
        ticks=False,
    ):
        super().__init__(parent)

        self._layout = QHBoxLayout(self)
        self._layout.setContentsMargins(0, 0, 0, 0)
        self.label = QLabel(label_text)
        self.slider = QSlider(Qt.Orientation.Horizontal)
        if ticks:
            self.slider.setTickPosition(QSlider.TickPosition.TicksBelow)
            self.slider.setTickInterval(step)

        self.slider.setMinimum(min_value)
        self.slider.setMaximum(max_value)
        self.slider.setValue(value)
        self.slider.setSingleStep(step)

        self._layout.addWidget(self.label)
        self._layout.addWidget(self.slider)

        super().init()
        super().set_value(value)

    def _get_value_internal(self):
        return self.slider.value()

    def _set_value_internal(self, value):
        self.slider.setValue(value)

    def _set_callback_internal(self, callback):
        self.slider.valueChanged.connect(callback)
