# -*- coding: utf-8 -*-
"""Small widget helpers shared by the plugin dialogs."""

import unicodedata

from qgis.PyQt.QtCore import QStringListModel, Qt
from qgis.PyQt.QtWidgets import QComboBox, QCompleter, QLineEdit

from .dialog_theme import build_stylesheet


def make_date_picker_only(date_edit):
    """
    Force a QDateEdit to be changed through the calendar popup only.

    The inner line edit is set read-only so the date cannot be typed, while the
    popup keeps working because QAbstractSpinBox itself stays writable. The
    drop-down button must stay visible, since it is the only way to open the
    calendar.
    """
    date_edit.setCalendarPopup(True)
    date_edit.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)

    line_edit = date_edit.findChild(QLineEdit)
    if line_edit is not None:
        line_edit.setReadOnly(True)
        # Selecting text on a read-only field only adds visual noise here.
        line_edit.setFocusPolicy(Qt.FocusPolicy.NoFocus)

    date_edit.setToolTip(
        u'Open the calendar to pick a date / Mở lịch để chọn ngày'
    )
    return date_edit


def fold_text(text):
    """
    Lower case, accent free form of a string, used as a search key.

    Vietnamese place names are unusable as search keys as they are: nobody types
    "Thừa Thiên Huế" with its diacritics, so both sides of the comparison are
    folded down to plain ASCII letters first.
    """
    if not text:
        return u''
    lowered = text.replace(u'Đ', u'D').replace(u'đ', u'd').lower()
    decomposed = unicodedata.normalize('NFD', lowered)
    return u''.join(
        char for char in decomposed if not unicodedata.combining(char)
    )


class ComboSearchCompleter(QCompleter):
    """
    Completer matching anywhere in the name and ignoring case and accents.

    Qt can only filter on the prefix of the display string, which is too strict
    for this list ("hue" would never find "Thừa Thiên Huế"). Filtering is
    therefore done here in splitPath, which Qt calls on every keystroke: the
    matching entries are pushed into the completer model and an empty prefix is
    returned so Qt keeps all of them.
    """

    def __init__(self, combo):
        super(ComboSearchCompleter, self).__init__(combo)
        self._combo = combo
        self._matches = QStringListModel(self)
        self.setModel(self._matches)
        self.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
        self.setCompletionMode(QCompleter.CompletionMode.UnfilteredPopupCompletion)
        self.setMaxVisibleItems(12)

    def splitPath(self, path):
        needle = fold_text(path)
        items = [self._combo.itemText(row) for row in range(self._combo.count())]
        if needle:
            items = [text for text in items if needle in fold_text(text)]
        self._matches.setStringList(items)
        return [u'']

    def matches(self):
        """Current match list, exposed for tests."""
        return list(self._matches.stringList())


def make_searchable_combo(combo, placeholder=u''):
    """
    Turn a drop-down into a type-to-search field without losing its item data.

    The combo stays a closed list: nothing new can be inserted, and any text
    that does not resolve to an entry is reverted when focus leaves, so
    currentIndex() and itemData() always describe what the user can see.
    """
    combo.setEditable(True)
    combo.setInsertPolicy(QComboBox.InsertPolicy.NoInsert)
    combo.setDuplicatesEnabled(False)

    completer = ComboSearchCompleter(combo)
    combo.setCompleter(completer)
    # The popup is a top level window, so the dialog stylesheet never reaches
    # it and it has to be styled on its own.
    popup = completer.popup()
    if popup is not None:
        popup.setObjectName('completerPopup')
        popup.setStyleSheet(build_stylesheet())

    line_edit = combo.lineEdit()
    if line_edit is not None:
        line_edit.setPlaceholderText(placeholder)
        line_edit.editingFinished.connect(
            lambda: _resolve_combo_text(combo)
        )

    completer.activated[str].connect(
        lambda text: _select_combo_text(combo, text)
    )
    combo.setToolTip(
        u'Type to search, or pick from the list / Nhập để tìm, hoặc chọn trong danh sách'
    )
    return combo


def _select_combo_text(combo, text):
    index = combo.findText(text, Qt.MatchFlag.MatchFixedString)
    if index >= 0:
        combo.setCurrentIndex(index)


def _resolve_combo_text(combo):
    """Keep the visible text in sync with the selected item."""
    text = combo.currentText()
    index = combo.findText(text, Qt.MatchFlag.MatchFixedString)
    if index >= 0:
        combo.setCurrentIndex(index)
        return
    combo.setEditText(combo.itemText(combo.currentIndex()))
