# This class is intended to help with line numbers area on the left side of the main document editor.
# The bale functions are:
# - Show line/block number for better text recognition.
# - Highlight current line where the cursor located.

# If used with PyQt6 make sure slots and signals are declared correctly, as there are some differences:
# from PyQt6.QtCore import pyqtSlot as Slot

from PySide6.QtCore import Qt, QRect, QSize, Slot
from PySide6.QtWidgets import QWidget, QTextEdit, QPlainTextEdit
from PySide6.QtGui import QPainter, QColor, QTextFormat

from typing import TYPE_CHECKING, Union

from . import Settings
from . import ThemeHelper

from ..edit_widget import EditWidget

if TYPE_CHECKING:
    from PySide6.QtCore import QObject  # noqa: F401
    from PySide6.QtGui import QTextCharFormat  # noqa: F401


class LineNumbers(QWidget):
    def __init__(self, editor: Union[EditWidget, QPlainTextEdit] = None):
        QWidget.__init__(self, editor)

        self.editor = editor

        """
        # Alternatively it can be get from AppConfig:
        font = QFont()
        font.setPointSize(AppConfig().get_font_size())
        self.setFont(font)
        """
        self.setFont(self.editor.document().defaultFont())  # Set global font to the line area

        # Theme helper
        self.theme_helper = ThemeHelper()

        # Settings
        self.settings = Settings(parent=self)

        editor.updateRequest.connect(self.update_request)
        editor.blockCountChanged.connect(self.update_width)
        # TODO: Improve line highlighting on hover.
        # Current method of mixing background colors does not yield clear results.
        # editor.cursorPositionChanged.connect(self.highlight_current_line)

        # Init object with direct updates
        self.update_numbers()

        # self.highlight_current_line()

    def sizeHint(self) -> QSize:
        return QSize(self.line_numbers_width(), 0)

    def paintEvent(self, event) -> None:
        self.line_numbers_paint_event(event)

    @Slot(QRect, int)
    def update_request(self, rect, dy) -> None:
        if dy:
            self.scroll(0, dy)
        else:
            self.update(0, rect.y(), self.width(), rect.height())
        if rect.contains(self.editor.viewport().rect()):
            self.update_width()

    @Slot(int)
    def update_width(self) -> None:
        self.editor.setViewportMargins(self.line_numbers_width(), 0, 0, 0)

    @Slot()
    def highlight_current_line(self) -> None:
        # The text edit widget is not intended to be a read only atm, but check it anyway to make it future-proof
        if not self.editor.isReadOnly():
            selection = QTextEdit.ExtraSelection()  # type: Union[QTextEdit.ExtraSelection, QTextCharFormat, None]
            if not selection:
                return
            selection.cursor = self.editor.textCursor()
            selection.cursor.clearSelection()
            # Set selection properties
            # line_color = QColor(Qt.GlobalColor.green).lighter(175)  # As an example of how to shift base color tone
            line_color = QColor(self.theme_helper.get_color('edit_widget_line_numbers_active_line_background'))
            _format = selection.format  # type: ignore
            _format.setBackground(line_color)
            _format.setProperty(QTextFormat.Property.FullWidthSelection, True)
            # extra_selections.append(selection)
            self.editor.setExtraSelections([selection])

    def update_numbers(self) -> None:
        # Get element parent's contents size as the text edit widget may not be resized yet
        parent_obj = self.editor.parent()  # type: Union[QObject, QWidget]
        element_cr = parent_obj.contentsRect()
        # Update width first
        self.update_width()
        # Or it can be like this: height = self.editor.frameGeometry().height()
        self.setGeometry(
            QRect(element_cr.left(), element_cr.top(), self.line_numbers_width(), element_cr.height())
        )

    def line_numbers_paint_event(self, event) -> None:
        painter = QPainter(self)
        """
        More info about painter https://doc.qt.io/qt-6/qpainter.html#translate-2
        Example: painter.translate(3, 0)

        Global color enum https://doc.qt.io/qt-6/qt.html#GlobalColor-enum
        It can be a color value or pre-set enum's value by name, like: Qt.GlobalColor.transparent
        """
        painter.fillRect(event.rect(), QColor(self.theme_helper.get_color('edit_widget_line_numbers_background')))

        # Get current line number (block) to highlight
        cursor = self.editor.textCursor()
        current_line_block_number = cursor.block().blockNumber()

        block = self.editor.firstVisibleBlock()
        block_number = block.blockNumber()

        top = self.editor.blockBoundingGeometry(block).translated(self.editor.contentOffset()).top()
        bottom = top + self.editor.blockBoundingRect(block).height()

        number_color = QColor(self.theme_helper.get_color('edit_widget_line_numbers_color'))
        # Also: current_number_color = QColor(Qt.GlobalColor.green).lighter(179)
        current_number_color = QColor(self.theme_helper.get_color('edit_widget_line_numbers_active_number'))
        current_number_background = QColor(
            self.theme_helper.get_color('edit_widget_line_numbers_active_number_background'))

        while block.isValid() and top <= event.rect().bottom():
            if block.isVisible() and bottom >= event.rect().top():
                if current_line_block_number == block_number:
                    """
                    Highlight current line number
                    More info of how to fill rectangle area https://doc.qt.io/qt-6/qpainter.html#fillRect-4
                    """
                    painter.fillRect(0, int(top) + 1, self.width(), self.fontMetrics().height(),
                                     current_number_background)
                    painter.setPen(current_number_color)
                else:
                    # Common number color
                    # Also: painter.setPen(Qt.GlobalColor.darkGray)
                    painter.setPen(number_color)

                number = str(block_number + 1)
                # Minus shift to the left to get the right padding from the overall padding
                painter.drawText(-2, int(top) + 1, self.width(),
                                 self.fontMetrics().height(),
                                 Qt.AlignmentFlag.AlignRight, number)
            block = block.next()
            top = bottom
            bottom = top + self.editor.blockBoundingRect(block).height()
            block_number += 1

    def line_numbers_width(self) -> int:
        """
        Get line space width
        @return: int value of space occupied by digits (incl. paddings)
        """
        if not self.visible():
            return 0

        digits = max(2, len(str(self.editor.blockCount())))
        # '5' is an overall value of left and right padding
        space = 5 + self.fontMetrics().horizontalAdvance("0") * digits
        return space

    def visible(self) -> bool:
        return self.settings.show_line_numbers
