"""Reusable inline rich-text comments editor (parity plan Q6b).

One widget shared by every wizard/dialog that captures comments or
descriptions: a compact formatting toolbar (bold / italic / underline,
bullet & numbered lists, text color, link, undo / redo) over a growable
``QTextEdit``. No pop-out window — but the document is exposed via
:meth:`document` so a future pop-out can share the same ``QTextDocument``.

Storage contract (see ``comment_formats``): callers persist
:meth:`plain_text` into the canonical DB column and :meth:`html` into the
``emds_comments_html`` metadata sidecar; :meth:`set_content` resolves
sidecar-HTML / legacy-RTF / plain input on the way back in.
"""

from __future__ import annotations

from typing import Any, Optional

from .comment_formats import resolve_comment_content
from .qt_compat import (
    QColor,
    QColorDialog,
    QFont,
    QHBoxLayout,
    QInputDialog,
    QSizePolicy,
    QTextCharFormat,
    QTextCursor,
    QTextEdit,
    QTextListFormat,
    QToolButton,
    QVBoxLayout,
    QWidget,
    Qt,
)
from .translator import tr


class RichCommentEditor(QWidget):
    """Inline rich-text editor with a compact formatting toolbar."""

    def __init__(self, parent=None, placeholder: str = ""):
        super().__init__(parent)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(2)
        self.setLayout(layout)

        toolbar = QHBoxLayout()
        toolbar.setSpacing(2)
        layout.addLayout(toolbar)

        self._bold_button = self._add_tool(
            toolbar, "B", tr("rich_comments.tip_bold"), checkable=True, bold=True
        )
        self._bold_button.clicked.connect(self._toggle_bold)
        self._italic_button = self._add_tool(
            toolbar, "I", tr("rich_comments.tip_italic"), checkable=True, italic=True
        )
        self._italic_button.clicked.connect(self._toggle_italic)
        self._underline_button = self._add_tool(
            toolbar, "U", tr("rich_comments.tip_underline"), checkable=True, underline=True
        )
        self._underline_button.clicked.connect(self._toggle_underline)

        toolbar.addSpacing(8)
        bullet_button = self._add_tool(toolbar, "•", tr("rich_comments.tip_bullets"))
        bullet_button.clicked.connect(
            lambda: self._apply_list(QTextListFormat.Style.ListDisc)
        )
        numbered_button = self._add_tool(toolbar, "1.", tr("rich_comments.tip_numbering"))
        numbered_button.clicked.connect(
            lambda: self._apply_list(QTextListFormat.Style.ListDecimal)
        )

        toolbar.addSpacing(8)
        color_button = self._add_tool(toolbar, "A", tr("rich_comments.tip_color"))
        color_button.setStyleSheet("QToolButton { color: #b91c1c; font-weight: bold; }")
        color_button.clicked.connect(self._pick_color)
        link_button = self._add_tool(toolbar, "🔗", tr("rich_comments.tip_link"))
        link_button.clicked.connect(self._insert_link)

        toolbar.addSpacing(8)
        undo_button = self._add_tool(toolbar, "↶", tr("rich_comments.tip_undo"))
        redo_button = self._add_tool(toolbar, "↷", tr("rich_comments.tip_redo"))
        toolbar.addStretch()

        self._editor = QTextEdit()
        self._editor.setAcceptRichText(True)
        self._editor.setMinimumHeight(80)
        self._editor.setSizePolicy(
            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
        )
        if placeholder:
            self._editor.setPlaceholderText(placeholder)
        self._editor.currentCharFormatChanged.connect(self._sync_format_buttons)
        undo_button.clicked.connect(self._editor.undo)
        redo_button.clicked.connect(self._editor.redo)
        layout.addWidget(self._editor, 1)

    # ------------------------------------------------------------------
    # Content API (the storage contract)
    # ------------------------------------------------------------------

    def set_content(self, plain: Any, sidecar_html: Optional[str] = None) -> None:
        """Load stored content: HTML sidecar wins, legacy RTF blobs degrade
        to extracted plain text, anything else loads as plain text."""
        kind, content = resolve_comment_content(plain, sidecar_html)
        if kind == "html":
            self._editor.setHtml(content)
        else:
            self._editor.setPlainText(content)

    def plain_text(self) -> str:
        """The canonical-column value (always plain text)."""
        return self._editor.toPlainText().strip()

    def html(self) -> str:
        """The sidecar value: Qt HTML when there is any text, else ""."""
        if not self.plain_text():
            return ""
        return self._editor.toHtml()

    def document(self):
        """The QTextDocument — shareable with a future pop-out editor."""
        return self._editor.document()

    def set_placeholder(self, text: str) -> None:
        self._editor.setPlaceholderText(text)

    # ------------------------------------------------------------------
    # QTextEdit-compatible shims — existing dialog code reads and primes
    # comment boxes via toPlainText()/setPlainText(); routing setPlainText
    # through set_content() also rescues legacy RTF blobs transparently.
    # ------------------------------------------------------------------

    def toPlainText(self) -> str:  # noqa: N802 (Qt naming)
        return self._editor.toPlainText()

    def setPlainText(self, text: Any) -> None:  # noqa: N802 (Qt naming)
        self.set_content(text)

    def setHtml(self, html: str) -> None:  # noqa: N802 (Qt naming)
        self._editor.setHtml(html)

    def setPlaceholderText(self, text: str) -> None:  # noqa: N802 (Qt naming)
        self._editor.setPlaceholderText(text)

    # ------------------------------------------------------------------
    # Toolbar actions
    # ------------------------------------------------------------------

    @staticmethod
    def _add_tool(
        toolbar: QHBoxLayout,
        text: str,
        tooltip: str,
        *,
        checkable: bool = False,
        bold: bool = False,
        italic: bool = False,
        underline: bool = False,
    ) -> QToolButton:
        button = QToolButton()
        button.setText(text)
        button.setToolTip(tooltip)
        button.setCheckable(checkable)
        font = QFont(button.font())
        font.setBold(bold)
        font.setItalic(italic)
        font.setUnderline(underline)
        button.setFont(font)
        button.setFixedWidth(28)
        toolbar.addWidget(button)
        return button

    def _merge_format(self, char_format: QTextCharFormat) -> None:
        cursor = self._editor.textCursor()
        if not cursor.hasSelection():
            cursor.select(QTextCursor.SelectionType.WordUnderCursor)
        cursor.mergeCharFormat(char_format)
        self._editor.mergeCurrentCharFormat(char_format)
        self._editor.setFocus()

    def _toggle_bold(self) -> None:
        fmt = QTextCharFormat()
        fmt.setFontWeight(
            QFont.Weight.Bold if self._bold_button.isChecked() else QFont.Weight.Normal
        )
        self._merge_format(fmt)

    def _toggle_italic(self) -> None:
        fmt = QTextCharFormat()
        fmt.setFontItalic(self._italic_button.isChecked())
        self._merge_format(fmt)

    def _toggle_underline(self) -> None:
        fmt = QTextCharFormat()
        fmt.setFontUnderline(self._underline_button.isChecked())
        self._merge_format(fmt)

    def _apply_list(self, style) -> None:
        cursor = self._editor.textCursor()
        current = cursor.currentList()
        if current is not None and current.format().style() == style:
            # Toggle OFF: pull the block back out of the list.
            block_format = cursor.blockFormat()
            block_format.setIndent(0)
            block_format.setObjectIndex(-1)
            cursor.setBlockFormat(block_format)
        else:
            list_format = QTextListFormat()
            list_format.setStyle(style)
            cursor.createList(list_format)
        self._editor.setFocus()

    def _pick_color(self) -> None:
        color = QColorDialog.getColor(
            self._editor.textColor(), self, tr("rich_comments.color_dialog_title")
        )
        if not color.isValid():
            return
        fmt = QTextCharFormat()
        fmt.setForeground(color)
        self._merge_format(fmt)

    def _insert_link(self) -> None:
        cursor = self._editor.textCursor()
        url, accepted = QInputDialog.getText(
            self,
            tr("rich_comments.link_dialog_title"),
            tr("rich_comments.link_dialog_prompt"),
        )
        url = (url or "").strip()
        if not accepted or not url:
            return
        fmt = QTextCharFormat()
        fmt.setAnchor(True)
        fmt.setAnchorHref(url)
        fmt.setForeground(QColor("#2563eb"))
        fmt.setFontUnderline(True)
        if cursor.hasSelection():
            cursor.mergeCharFormat(fmt)
        else:
            cursor.insertText(url, fmt)
        # Reset the insertion format so text typed AFTER the link doesn't
        # keep extending the hyperlink styling.
        self._editor.setCurrentCharFormat(QTextCharFormat())
        self._editor.setFocus()

    def _sync_format_buttons(self, char_format: QTextCharFormat) -> None:
        for button in (self._bold_button, self._italic_button, self._underline_button):
            button.blockSignals(True)
        self._bold_button.setChecked(char_format.fontWeight() >= QFont.Weight.Bold)
        self._italic_button.setChecked(char_format.fontItalic())
        self._underline_button.setChecked(char_format.fontUnderline())
        for button in (self._bold_button, self._italic_button, self._underline_button):
            button.blockSignals(False)
