# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only


from typing import override
from warnings import catch_warnings

import structlog
from PySide6.QtCore import Signal
from PySide6.QtGui import QAction
from PySide6.QtWidgets import QGraphicsOpacityEffect, QWidget

from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.models import Tag
from tagstudio.core.utils.types import unwrap
from tagstudio.i18n.translations import Translations
from tagstudio.qt.app_settings import AppSettings
from tagstudio.qt.controllers.modal import Modal
from tagstudio.qt.controllers.modal_content import ModalContent
from tagstudio.qt.controllers.suggest_box import SuggestBox
from tagstudio.qt.controllers.underlined_widget import UnderlinedWidget
from tagstudio.qt.mixed.build_tag import BuildTagPanel
from tagstudio.qt.mixed.tag_widget import TagWidget

logger = structlog.get_logger(__name__)


class TagSuggestBox(SuggestBox[Tag]):
    search_for_tag = Signal(int)

    def __init__(self, library: Library, settings: AppSettings, placeholder_text: str = "") -> None:
        super().__init__(library, settings, placeholder_text)

        # Context Menu Actions

        edit_tag_on_create_action = QAction(Translations["settings.edit_tag_on_create"], self)
        edit_tag_on_create_action.setCheckable(True)
        self.addAction(edit_tag_on_create_action)
        self.layout().search_field.addAction(edit_tag_on_create_action)
        edit_tag_on_create_action.setChecked(self._settings.edit_tag_on_create)
        edit_tag_on_create_action.triggered.connect(
            lambda checked: self._toggle_edit_on_tag_create(checked)
        )

    def _toggle_edit_on_tag_create(self, checked: bool) -> None:
        """Toggle the setting for opening the edit window after creating a tag."""
        self._settings.edit_tag_on_create = checked
        self._settings.save()

    @override
    def _on_item_create(self) -> None:
        """Creates a new tag and adds it to the currently selected entries.

        Optionally opens up an edit panel after creation and before adding to entries.
        Populates name field using current search query.
        """
        query: str = self.layout().search_field.text()

        if self._settings.edit_tag_on_create:
            panel: BuildTagPanel = BuildTagPanel(self._lib)
            modal: Modal = Modal(
                panel, Translations["tag.new"], Translations["tag.new"], is_savable=True
            )
            if query.strip():
                panel.name_field.setText(query)

            modal.saved.connect(lambda: self._create_item_from_modal(panel))
            modal.show()
        else:
            tag = Tag(name=query)
            self._lib.add_tag(tag)
            self._on_item_chosen(tag)
            self._clear_search_query()

    @override
    def _on_item_edit(self, item: Tag) -> None:
        edit_tag_panel: BuildTagPanel = BuildTagPanel(self._lib, tag=item)
        edit_tag_modal: Modal = Modal(
            edit_tag_panel,
            self._lib.tag_display_name(item),
            Translations["tag.edit"],
            is_savable=True,
        )
        edit_tag_modal.saved.connect(lambda: self._edit_item(edit_tag_panel))
        edit_tag_modal.show()

    @override
    def _on_item_chosen(self, item: Tag) -> None:
        self.item_chosen.emit(item.id)
        self._clear_search_query()
        self.done.emit("*")  # The query does not matter

    @override
    def _update_hint_icon(self) -> None:
        results = bool(len(self._search_results) > 0)

        if not self._is_shift_held and self._is_selected_item_added():
            self.set_hint_icon(self._rm.hint_tag_added)
        elif self._is_shift_held:
            self.set_hint_icon(self._rm.hint_tag_create)
        elif results and self._is_selected_item_added():
            self.set_hint_icon(self._rm.hint_tag_added)
        elif results:
            self.set_hint_icon(self._rm.hint_tag_add)
        elif self.layout().search_field.text():
            self.set_hint_icon(self._rm.hint_tag_create)
        else:
            self.set_hint_icon(None)

    @override
    def _search_items(self, query: str) -> tuple[list[Tag], list[Tag]]:
        if query != "":
            return self._lib.search_tags(name=query, limit=0)
        else:
            return ([], [])

    @override
    def _set_item_widget(self, item: Tag | None, index: int) -> None:
        """Set the tag of a tag widget at a specific index."""
        underlined_widget: UnderlinedWidget = self._get_item_widget(index, self._lib)
        tag_widget = underlined_widget.widget
        assert isinstance(tag_widget, TagWidget)
        tag_widget.has_remove = False
        tag_widget.set_tag(item)
        underlined_widget.setHidden(item is None)
        opacity_effect = QGraphicsOpacityEffect(self)
        opacity_effect.setOpacity(0.3)
        if item and item.id in self.added:
            tag_widget.setGraphicsEffect(opacity_effect)
        else:
            tag_widget.setGraphicsEffect(None)  # pyright: ignore[reportArgumentType]

        if item is None:
            return

        # Select first item
        underlined_widget.toggle_underline(index != 0)

        # Disconnect previous callbacks
        with catch_warnings(record=True):
            tag_widget.on_edit.disconnect()
            tag_widget.bg_button.clicked.disconnect()
            tag_widget.search_for_tag_action.triggered.disconnect()

        # Connect callbacks
        tag_widget.on_edit.connect(lambda edit_tag=item: self._on_item_edit(edit_tag))
        tag_widget.bg_button.clicked.connect(
            lambda checked=False, tag=item: self._on_item_chosen(tag)
        )
        tag_widget.search_for_tag_action.triggered.connect(
            lambda checked=False, tag_id=item.id: self.search_for_tag.emit(tag_id)
        )
        tag_widget.search_for_tag_action.setEnabled(True)

    @override
    def _create_item_from_modal(self, edit_item_panel: ModalContent) -> None:
        if isinstance(edit_item_panel, BuildTagPanel):
            tag: Tag = edit_item_panel.build_tag()
            self._lib.add_tag(
                tag,
                parent_ids=edit_item_panel.parent_ids,
                aliases=edit_item_panel.aliases,
                exclusion_ids=edit_item_panel.exclusion_ids,
            )
            self._on_item_chosen(tag)

        edit_item_panel.hide()
        self._on_search_query_changed(self.layout().search_field.text())

    @override
    def _edit_item(self, edit_item_panel: ModalContent) -> None:
        if not isinstance(edit_item_panel, BuildTagPanel):
            return

        self._lib.update_tag(
            tag=edit_item_panel.build_tag(),
            parent_ids=edit_item_panel.parent_ids,
            aliases=edit_item_panel.aliases,
            exclusion_ids=edit_item_panel.exclusion_ids,
        )
        self._update_items(self.layout().search_field.text())

    @override
    def _get_item_widget(self, index: int, library: Library | None) -> UnderlinedWidget:
        """Gets the item widget at a specific index."""
        # Create any new item widgets needed up to the given index
        if self.layout().content_layout.count() <= index:
            while self.layout().content_layout.count() <= index:
                tag_widget = TagWidget(tag=None, has_edit=True, has_remove=True, library=library)
                tag_widget.on_remove.connect(self._update_items)
                widget = UnderlinedWidget(tag_widget)
                widget.setHidden(True)
                self.layout().content_layout.addWidget(widget)

        item = unwrap(self.layout().content_layout.itemAt(index))
        widget_: QWidget = unwrap(item.widget())
        assert isinstance(widget_, UnderlinedWidget)
        return widget_
