# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html

"""
See pylib/anki/hooks.py
"""

from __future__ import annotations

from typing import Any, Callable, List, Optional, Tuple, Union

import anki
import aqt
from anki.cards import Card
from anki.decks import Deck, DeckConfig
from anki.hooks import runFilter, runHook
from anki.models import NoteType
from aqt.qt import QDialog, QEvent, QMenu
from aqt.tagedit import TagEdit

# New hook/filter handling
##############################################################################
# The code in this section is automatically generated - any edits you make
# will be lost. To add new hooks, see ../tools/genhooks_gui.py
#
# @@AUTOGEN@@


class _AddCardsDidAddNoteHook:
    _hooks: List[Callable[["anki.notes.Note"], None]] = []

    def append(self, cb: Callable[["anki.notes.Note"], None]) -> None:
        """(note: anki.notes.Note)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["anki.notes.Note"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, note: anki.notes.Note) -> None:
        for hook in self._hooks:
            try:
                hook(note)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("AddCards.noteAdded", note)


add_cards_did_add_note = _AddCardsDidAddNoteHook()


class _AddCardsDidInitHook:
    _hooks: List[Callable[["aqt.addcards.AddCards"], None]] = []

    def append(self, cb: Callable[["aqt.addcards.AddCards"], None]) -> None:
        """(addcards: aqt.addcards.AddCards)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.addcards.AddCards"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, addcards: aqt.addcards.AddCards) -> None:
        for hook in self._hooks:
            try:
                hook(addcards)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


add_cards_did_init = _AddCardsDidInitHook()


class _AddCardsWillAddNoteFilter:
    """Decides whether the note should be added to the collection or
    not. It is assumed to come from the addCards window.

    reason_to_already_reject is the first reason to reject that
    was found, or None. If your filter wants to reject, it should
    replace return the reason to reject. Otherwise return the
    input."""

    _hooks: List[Callable[[Optional[str], "anki.notes.Note"], Optional[str]]] = []

    def append(
        self, cb: Callable[[Optional[str], "anki.notes.Note"], Optional[str]]
    ) -> None:
        """(problem: Optional[str], note: anki.notes.Note)"""
        self._hooks.append(cb)

    def remove(
        self, cb: Callable[[Optional[str], "anki.notes.Note"], Optional[str]]
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, problem: Optional[str], note: anki.notes.Note) -> Optional[str]:
        for filter in self._hooks:
            try:
                problem = filter(problem, note)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        return problem


add_cards_will_add_note = _AddCardsWillAddNoteFilter()


class _AddCardsWillShowHistoryMenuHook:
    _hooks: List[Callable[["aqt.addcards.AddCards", QMenu], None]] = []

    def append(self, cb: Callable[["aqt.addcards.AddCards", QMenu], None]) -> None:
        """(addcards: aqt.addcards.AddCards, menu: QMenu)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.addcards.AddCards", QMenu], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, addcards: aqt.addcards.AddCards, menu: QMenu) -> None:
        for hook in self._hooks:
            try:
                hook(addcards, menu)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("AddCards.onHistory", addcards, menu)


add_cards_will_show_history_menu = _AddCardsWillShowHistoryMenuHook()


class _AddcardsWillAddHistoryEntryFilter:
    """Allows changing the history line in the add-card window."""

    _hooks: List[Callable[[str, "anki.notes.Note"], str]] = []

    def append(self, cb: Callable[[str, "anki.notes.Note"], str]) -> None:
        """(line: str, note: anki.notes.Note)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[str, "anki.notes.Note"], str]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, line: str, note: anki.notes.Note) -> str:
        for filter in self._hooks:
            try:
                line = filter(line, note)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        return line


addcards_will_add_history_entry = _AddcardsWillAddHistoryEntryFilter()


class _AddonConfigEditorWillDisplayJsonFilter:
    """Allows changing the text of the json configuration before actually
    displaying it to the user. For example, you can replace "\n" by
    some actual new line. Then you can replace the new lines by "\n"
    while reading the file and let the user uses real new line in
    string instead of its encoding."""

    _hooks: List[Callable[[str], str]] = []

    def append(self, cb: Callable[[str], str]) -> None:
        """(text: str)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[str], str]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, text: str) -> str:
        for filter in self._hooks:
            try:
                text = filter(text)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        return text


addon_config_editor_will_display_json = _AddonConfigEditorWillDisplayJsonFilter()


class _AddonConfigEditorWillSaveJsonFilter:
    """Allows changing the text of the json configuration that was
    received from the user before actually reading it. For
    example, you can replace new line in strings by some "\n"."""

    _hooks: List[Callable[[str], str]] = []

    def append(self, cb: Callable[[str], str]) -> None:
        """(text: str)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[str], str]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, text: str) -> str:
        for filter in self._hooks:
            try:
                text = filter(text)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        return text


addon_config_editor_will_save_json = _AddonConfigEditorWillSaveJsonFilter()


class _AddonsDialogDidChangeSelectedAddonHook:
    """Allows doing an action when a single add-on is selected."""

    _hooks: List[
        Callable[["aqt.addons.AddonsDialog", "aqt.addons.AddonMeta"], None]
    ] = []

    def append(
        self, cb: Callable[["aqt.addons.AddonsDialog", "aqt.addons.AddonMeta"], None]
    ) -> None:
        """(dialog: aqt.addons.AddonsDialog, add_on: aqt.addons.AddonMeta)"""
        self._hooks.append(cb)

    def remove(
        self, cb: Callable[["aqt.addons.AddonsDialog", "aqt.addons.AddonMeta"], None]
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self, dialog: aqt.addons.AddonsDialog, add_on: aqt.addons.AddonMeta
    ) -> None:
        for hook in self._hooks:
            try:
                hook(dialog, add_on)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


addons_dialog_did_change_selected_addon = _AddonsDialogDidChangeSelectedAddonHook()


class _AddonsDialogWillShowHook:
    """Allows changing the add-on dialog before it is shown. E.g. add
    buttons."""

    _hooks: List[Callable[["aqt.addons.AddonsDialog"], None]] = []

    def append(self, cb: Callable[["aqt.addons.AddonsDialog"], None]) -> None:
        """(dialog: aqt.addons.AddonsDialog)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.addons.AddonsDialog"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, dialog: aqt.addons.AddonsDialog) -> None:
        for hook in self._hooks:
            try:
                hook(dialog)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


addons_dialog_will_show = _AddonsDialogWillShowHook()


class _AvPlayerDidBeginPlayingHook:
    _hooks: List[Callable[["aqt.sound.Player", "anki.sound.AVTag"], None]] = []

    def append(
        self, cb: Callable[["aqt.sound.Player", "anki.sound.AVTag"], None]
    ) -> None:
        """(player: aqt.sound.Player, tag: anki.sound.AVTag)"""
        self._hooks.append(cb)

    def remove(
        self, cb: Callable[["aqt.sound.Player", "anki.sound.AVTag"], None]
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, player: aqt.sound.Player, tag: anki.sound.AVTag) -> None:
        for hook in self._hooks:
            try:
                hook(player, tag)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


av_player_did_begin_playing = _AvPlayerDidBeginPlayingHook()


class _AvPlayerDidEndPlayingHook:
    _hooks: List[Callable[["aqt.sound.Player"], None]] = []

    def append(self, cb: Callable[["aqt.sound.Player"], None]) -> None:
        """(player: aqt.sound.Player)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.sound.Player"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, player: aqt.sound.Player) -> None:
        for hook in self._hooks:
            try:
                hook(player)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


av_player_did_end_playing = _AvPlayerDidEndPlayingHook()


class _AvPlayerWillPlayHook:
    _hooks: List[Callable[["anki.sound.AVTag"], None]] = []

    def append(self, cb: Callable[["anki.sound.AVTag"], None]) -> None:
        """(tag: anki.sound.AVTag)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["anki.sound.AVTag"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, tag: anki.sound.AVTag) -> None:
        for hook in self._hooks:
            try:
                hook(tag)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


av_player_will_play = _AvPlayerWillPlayHook()


class _BackupDidCompleteHook:
    _hooks: List[Callable[[], None]] = []

    def append(self, cb: Callable[[], None]) -> None:
        """()"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self) -> None:
        for hook in self._hooks:
            try:
                hook()
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


backup_did_complete = _BackupDidCompleteHook()


class _BrowserDidChangeRowHook:
    _hooks: List[Callable[["aqt.browser.Browser"], None]] = []

    def append(self, cb: Callable[["aqt.browser.Browser"], None]) -> None:
        """(browser: aqt.browser.Browser)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.browser.Browser"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, browser: aqt.browser.Browser) -> None:
        for hook in self._hooks:
            try:
                hook(browser)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("browser.rowChanged", browser)


browser_did_change_row = _BrowserDidChangeRowHook()


class _BrowserDidSearchHook:
    """Allows you to modify the list of returned card ids from a search."""

    _hooks: List[Callable[["aqt.browser.SearchContext"], None]] = []

    def append(self, cb: Callable[["aqt.browser.SearchContext"], None]) -> None:
        """(context: aqt.browser.SearchContext)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.browser.SearchContext"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, context: aqt.browser.SearchContext) -> None:
        for hook in self._hooks:
            try:
                hook(context)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


browser_did_search = _BrowserDidSearchHook()


class _BrowserHeaderWillShowContextMenuHook:
    _hooks: List[Callable[["aqt.browser.Browser", QMenu], None]] = []

    def append(self, cb: Callable[["aqt.browser.Browser", QMenu], None]) -> None:
        """(browser: aqt.browser.Browser, menu: QMenu)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.browser.Browser", QMenu], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, browser: aqt.browser.Browser, menu: QMenu) -> None:
        for hook in self._hooks:
            try:
                hook(browser, menu)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


browser_header_will_show_context_menu = _BrowserHeaderWillShowContextMenuHook()


class _BrowserMenusDidInitHook:
    _hooks: List[Callable[["aqt.browser.Browser"], None]] = []

    def append(self, cb: Callable[["aqt.browser.Browser"], None]) -> None:
        """(browser: aqt.browser.Browser)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.browser.Browser"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, browser: aqt.browser.Browser) -> None:
        for hook in self._hooks:
            try:
                hook(browser)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("browser.setupMenus", browser)


browser_menus_did_init = _BrowserMenusDidInitHook()


class _BrowserWillBuildTreeFilter:
    """Used to add or replace items in the browser sidebar tree

    'tree' is the root SidebarItem that all other items are added to.

    'stage' is an enum describing the different construction stages of
    the sidebar tree at which you can interject your changes.
    The different values can be inspected by looking at
    aqt.browser.SidebarStage.

    If you want Anki to proceed with the construction of the tree stage
    in question after your have performed your changes or additions,
    return the 'handled' boolean unchanged.

    On the other hand, if you want to prevent Anki from adding its own
    items at a particular construction stage (e.g. in case your add-on
    implements its own version of that particular stage), return 'True'.

    If you return 'True' at SidebarStage.ROOT, the sidebar will not be
    populated by any of the other construction stages. For any other stage
    the tree construction will just continue as usual.

    For example, if your code wishes to replace the tag tree, you could do:

        def on_browser_will_build_tree(handled, root, stage, browser):
            if stage != SidebarStage.TAGS:
                # not at tag tree building stage, pass on
                return handled

            # your tag tree construction code
            # root.addChild(...)

            # your code handled tag tree construction, no need for Anki
            # or other add-ons to build the tag tree
            return True
    """

    _hooks: List[
        Callable[
            [
                bool,
                "aqt.browser.SidebarItem",
                "aqt.browser.SidebarStage",
                "aqt.browser.Browser",
            ],
            bool,
        ]
    ] = []

    def append(
        self,
        cb: Callable[
            [
                bool,
                "aqt.browser.SidebarItem",
                "aqt.browser.SidebarStage",
                "aqt.browser.Browser",
            ],
            bool,
        ],
    ) -> None:
        """(handled: bool, tree: aqt.browser.SidebarItem, stage: aqt.browser.SidebarStage, browser: aqt.browser.Browser)"""
        self._hooks.append(cb)

    def remove(
        self,
        cb: Callable[
            [
                bool,
                "aqt.browser.SidebarItem",
                "aqt.browser.SidebarStage",
                "aqt.browser.Browser",
            ],
            bool,
        ],
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self,
        handled: bool,
        tree: aqt.browser.SidebarItem,
        stage: aqt.browser.SidebarStage,
        browser: aqt.browser.Browser,
    ) -> bool:
        for filter in self._hooks:
            try:
                handled = filter(handled, tree, stage, browser)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        return handled


browser_will_build_tree = _BrowserWillBuildTreeFilter()


class _BrowserWillSearchHook:
    """Allows you to modify the search text, or perform your own search.

    You can modify context.search to change the text that is sent to the
    searching backend.

    If you set context.card_ids to a list of ids, the regular search will
    not be performed, and the provided ids will be used instead.

    Your add-on should check if context.card_ids is not None, and return
    without making changes if it has been set.
    """

    _hooks: List[Callable[["aqt.browser.SearchContext"], None]] = []

    def append(self, cb: Callable[["aqt.browser.SearchContext"], None]) -> None:
        """(context: aqt.browser.SearchContext)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.browser.SearchContext"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, context: aqt.browser.SearchContext) -> None:
        for hook in self._hooks:
            try:
                hook(context)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


browser_will_search = _BrowserWillSearchHook()


class _BrowserWillShowHook:
    _hooks: List[Callable[["aqt.browser.Browser"], None]] = []

    def append(self, cb: Callable[["aqt.browser.Browser"], None]) -> None:
        """(browser: aqt.browser.Browser)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.browser.Browser"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, browser: aqt.browser.Browser) -> None:
        for hook in self._hooks:
            try:
                hook(browser)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


browser_will_show = _BrowserWillShowHook()


class _BrowserWillShowContextMenuHook:
    _hooks: List[Callable[["aqt.browser.Browser", QMenu], None]] = []

    def append(self, cb: Callable[["aqt.browser.Browser", QMenu], None]) -> None:
        """(browser: aqt.browser.Browser, menu: QMenu)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.browser.Browser", QMenu], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, browser: aqt.browser.Browser, menu: QMenu) -> None:
        for hook in self._hooks:
            try:
                hook(browser, menu)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("browser.onContextMenu", browser, menu)


browser_will_show_context_menu = _BrowserWillShowContextMenuHook()


class _CardLayoutWillShowHook:
    """Allow to change the display of the card layout. After most values are
    set and before the window is actually shown."""

    _hooks: List[Callable[["aqt.clayout.CardLayout"], None]] = []

    def append(self, cb: Callable[["aqt.clayout.CardLayout"], None]) -> None:
        """(clayout: aqt.clayout.CardLayout)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.clayout.CardLayout"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, clayout: aqt.clayout.CardLayout) -> None:
        for hook in self._hooks:
            try:
                hook(clayout)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


card_layout_will_show = _CardLayoutWillShowHook()


class _CardWillShowFilter:
    """Can modify card text before review/preview."""

    _hooks: List[Callable[[str, Card, str], str]] = []

    def append(self, cb: Callable[[str, Card, str], str]) -> None:
        """(text: str, card: Card, kind: str)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[str, Card, str], str]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, text: str, card: Card, kind: str) -> str:
        for filter in self._hooks:
            try:
                text = filter(text, card, kind)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        # legacy support
        text = runFilter("prepareQA", text, card, kind)
        return text


card_will_show = _CardWillShowFilter()


class _CollectionDidLoadHook:
    _hooks: List[Callable[["anki.collection.Collection"], None]] = []

    def append(self, cb: Callable[["anki.collection.Collection"], None]) -> None:
        """(col: anki.collection.Collection)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["anki.collection.Collection"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, col: anki.collection.Collection) -> None:
        for hook in self._hooks:
            try:
                hook(col)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("colLoading", col)


collection_did_load = _CollectionDidLoadHook()


class _CurrentNoteTypeDidChangeHook:
    _hooks: List[Callable[[NoteType], None]] = []

    def append(self, cb: Callable[[NoteType], None]) -> None:
        """(notetype: NoteType)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[NoteType], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, notetype: NoteType) -> None:
        for hook in self._hooks:
            try:
                hook(notetype)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("currentModelChanged")


current_note_type_did_change = _CurrentNoteTypeDidChangeHook()


class _DebugConsoleDidEvaluatePythonFilter:
    """Allows processing the debug result. E.g. logging queries and
    result, saving last query to display it later..."""

    _hooks: List[Callable[[str, str, QDialog], str]] = []

    def append(self, cb: Callable[[str, str, QDialog], str]) -> None:
        """(output: str, query: str, debug_window: QDialog)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[str, str, QDialog], str]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, output: str, query: str, debug_window: QDialog) -> str:
        for filter in self._hooks:
            try:
                output = filter(output, query, debug_window)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        return output


debug_console_did_evaluate_python = _DebugConsoleDidEvaluatePythonFilter()


class _DebugConsoleWillShowHook:
    """Allows editing the debug window. E.g. setting a default code, or
    previous code."""

    _hooks: List[Callable[[QDialog], None]] = []

    def append(self, cb: Callable[[QDialog], None]) -> None:
        """(debug_window: QDialog)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[QDialog], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, debug_window: QDialog) -> None:
        for hook in self._hooks:
            try:
                hook(debug_window)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


debug_console_will_show = _DebugConsoleWillShowHook()


class _DeckBrowserDidRenderHook:
    """Allow to update the deck browser window. E.g. change its title."""

    _hooks: List[Callable[["aqt.deckbrowser.DeckBrowser"], None]] = []

    def append(self, cb: Callable[["aqt.deckbrowser.DeckBrowser"], None]) -> None:
        """(deck_browser: aqt.deckbrowser.DeckBrowser)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.deckbrowser.DeckBrowser"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, deck_browser: aqt.deckbrowser.DeckBrowser) -> None:
        for hook in self._hooks:
            try:
                hook(deck_browser)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


deck_browser_did_render = _DeckBrowserDidRenderHook()


class _DeckBrowserWillRenderContentHook:
    """Used to modify HTML content sections in the deck browser body

            'content' contains the sections of HTML content the deck browser body
            will be updated with.

            When modifying the content of a particular section, please make sure your
            changes only perform the minimum required edits to make your add-on work.
            You should avoid overwriting or interfering with existing data as much
            as possible, instead opting to append your own changes, e.g.:

                def on_deck_browser_will_render_content(deck_browser, content):
                    content.stats += "
    <div>my html</div>"
    """

    _hooks: List[
        Callable[
            ["aqt.deckbrowser.DeckBrowser", "aqt.deckbrowser.DeckBrowserContent"], None
        ]
    ] = []

    def append(
        self,
        cb: Callable[
            ["aqt.deckbrowser.DeckBrowser", "aqt.deckbrowser.DeckBrowserContent"], None
        ],
    ) -> None:
        """(deck_browser: aqt.deckbrowser.DeckBrowser, content: aqt.deckbrowser.DeckBrowserContent)"""
        self._hooks.append(cb)

    def remove(
        self,
        cb: Callable[
            ["aqt.deckbrowser.DeckBrowser", "aqt.deckbrowser.DeckBrowserContent"], None
        ],
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self,
        deck_browser: aqt.deckbrowser.DeckBrowser,
        content: aqt.deckbrowser.DeckBrowserContent,
    ) -> None:
        for hook in self._hooks:
            try:
                hook(deck_browser, content)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


deck_browser_will_render_content = _DeckBrowserWillRenderContentHook()


class _DeckBrowserWillShowOptionsMenuHook:
    _hooks: List[Callable[[QMenu, int], None]] = []

    def append(self, cb: Callable[[QMenu, int], None]) -> None:
        """(menu: QMenu, deck_id: int)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[QMenu, int], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, menu: QMenu, deck_id: int) -> None:
        for hook in self._hooks:
            try:
                hook(menu, deck_id)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("showDeckOptions", menu, deck_id)


deck_browser_will_show_options_menu = _DeckBrowserWillShowOptionsMenuHook()


class _DeckConfDidAddConfigHook:
    """Allows modification of a newly created config group

    This hook is called after the config group was created, but
    before initializing the widget state.

    `deck_conf` will point to the old config group, `new_conf_id` will
    point to the newly created config group.

    Config groups are created as clones of the current one.
    """

    _hooks: List[
        Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig, str, int], None]
    ] = []

    def append(
        self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig, str, int], None]
    ) -> None:
        """(deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig, new_name: str, new_conf_id: int)"""
        self._hooks.append(cb)

    def remove(
        self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig, str, int], None]
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self,
        deck_conf: aqt.deckconf.DeckConf,
        deck: Deck,
        config: DeckConfig,
        new_name: str,
        new_conf_id: int,
    ) -> None:
        for hook in self._hooks:
            try:
                hook(deck_conf, deck, config, new_name, new_conf_id)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


deck_conf_did_add_config = _DeckConfDidAddConfigHook()


class _DeckConfDidLoadConfigHook:
    """Called once widget state has been set from deck config"""

    _hooks: List[Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None]] = []

    def append(
        self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None]
    ) -> None:
        """(deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig)"""
        self._hooks.append(cb)

    def remove(
        self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None]
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self, deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig
    ) -> None:
        for hook in self._hooks:
            try:
                hook(deck_conf, deck, config)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


deck_conf_did_load_config = _DeckConfDidLoadConfigHook()


class _DeckConfDidSetupUiFormHook:
    """Allows modifying or adding widgets in the deck options UI form"""

    _hooks: List[Callable[["aqt.deckconf.DeckConf"], None]] = []

    def append(self, cb: Callable[["aqt.deckconf.DeckConf"], None]) -> None:
        """(deck_conf: aqt.deckconf.DeckConf)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.deckconf.DeckConf"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, deck_conf: aqt.deckconf.DeckConf) -> None:
        for hook in self._hooks:
            try:
                hook(deck_conf)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


deck_conf_did_setup_ui_form = _DeckConfDidSetupUiFormHook()


class _DeckConfWillRemoveConfigHook:
    """Called before current config group is removed"""

    _hooks: List[Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None]] = []

    def append(
        self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None]
    ) -> None:
        """(deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig)"""
        self._hooks.append(cb)

    def remove(
        self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None]
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self, deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig
    ) -> None:
        for hook in self._hooks:
            try:
                hook(deck_conf, deck, config)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


deck_conf_will_remove_config = _DeckConfWillRemoveConfigHook()


class _DeckConfWillRenameConfigHook:
    """Called before config group is renamed"""

    _hooks: List[Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig, str], None]] = []

    def append(
        self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig, str], None]
    ) -> None:
        """(deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig, new_name: str)"""
        self._hooks.append(cb)

    def remove(
        self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig, str], None]
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self,
        deck_conf: aqt.deckconf.DeckConf,
        deck: Deck,
        config: DeckConfig,
        new_name: str,
    ) -> None:
        for hook in self._hooks:
            try:
                hook(deck_conf, deck, config, new_name)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


deck_conf_will_rename_config = _DeckConfWillRenameConfigHook()


class _DeckConfWillSaveConfigHook:
    """Called before widget state is saved to config"""

    _hooks: List[Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None]] = []

    def append(
        self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None]
    ) -> None:
        """(deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig)"""
        self._hooks.append(cb)

    def remove(
        self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None]
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self, deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig
    ) -> None:
        for hook in self._hooks:
            try:
                hook(deck_conf, deck, config)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


deck_conf_will_save_config = _DeckConfWillSaveConfigHook()


class _DeckConfWillShowHook:
    """Allows modifying the deck options dialog before it is shown"""

    _hooks: List[Callable[["aqt.deckconf.DeckConf"], None]] = []

    def append(self, cb: Callable[["aqt.deckconf.DeckConf"], None]) -> None:
        """(deck_conf: aqt.deckconf.DeckConf)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.deckconf.DeckConf"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, deck_conf: aqt.deckconf.DeckConf) -> None:
        for hook in self._hooks:
            try:
                hook(deck_conf)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


deck_conf_will_show = _DeckConfWillShowHook()


class _EditorDidFireTypingTimerHook:
    _hooks: List[Callable[["anki.notes.Note"], None]] = []

    def append(self, cb: Callable[["anki.notes.Note"], None]) -> None:
        """(note: anki.notes.Note)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["anki.notes.Note"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, note: anki.notes.Note) -> None:
        for hook in self._hooks:
            try:
                hook(note)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("editTimer", note)


editor_did_fire_typing_timer = _EditorDidFireTypingTimerHook()


class _EditorDidFocusFieldHook:
    _hooks: List[Callable[["anki.notes.Note", int], None]] = []

    def append(self, cb: Callable[["anki.notes.Note", int], None]) -> None:
        """(note: anki.notes.Note, current_field_idx: int)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["anki.notes.Note", int], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, note: anki.notes.Note, current_field_idx: int) -> None:
        for hook in self._hooks:
            try:
                hook(note, current_field_idx)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("editFocusGained", note, current_field_idx)


editor_did_focus_field = _EditorDidFocusFieldHook()


class _EditorDidInitHook:
    _hooks: List[Callable[["aqt.editor.Editor"], None]] = []

    def append(self, cb: Callable[["aqt.editor.Editor"], None]) -> None:
        """(editor: aqt.editor.Editor)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.editor.Editor"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, editor: aqt.editor.Editor) -> None:
        for hook in self._hooks:
            try:
                hook(editor)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


editor_did_init = _EditorDidInitHook()


class _EditorDidInitButtonsHook:
    _hooks: List[Callable[[List[str], "aqt.editor.Editor"], None]] = []

    def append(self, cb: Callable[[List[str], "aqt.editor.Editor"], None]) -> None:
        """(buttons: List[str], editor: aqt.editor.Editor)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[List[str], "aqt.editor.Editor"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, buttons: List[str], editor: aqt.editor.Editor) -> None:
        for hook in self._hooks:
            try:
                hook(buttons, editor)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


editor_did_init_buttons = _EditorDidInitButtonsHook()


class _EditorDidInitShortcutsHook:
    _hooks: List[Callable[[List[Tuple], "aqt.editor.Editor"], None]] = []

    def append(self, cb: Callable[[List[Tuple], "aqt.editor.Editor"], None]) -> None:
        """(shortcuts: List[Tuple], editor: aqt.editor.Editor)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[List[Tuple], "aqt.editor.Editor"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, shortcuts: List[Tuple], editor: aqt.editor.Editor) -> None:
        for hook in self._hooks:
            try:
                hook(shortcuts, editor)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("setupEditorShortcuts", shortcuts, editor)


editor_did_init_shortcuts = _EditorDidInitShortcutsHook()


class _EditorDidLoadNoteHook:
    _hooks: List[Callable[["aqt.editor.Editor"], None]] = []

    def append(self, cb: Callable[["aqt.editor.Editor"], None]) -> None:
        """(editor: aqt.editor.Editor)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.editor.Editor"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, editor: aqt.editor.Editor) -> None:
        for hook in self._hooks:
            try:
                hook(editor)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("loadNote", editor)


editor_did_load_note = _EditorDidLoadNoteHook()


class _EditorDidUnfocusFieldFilter:
    _hooks: List[Callable[[bool, "anki.notes.Note", int], bool]] = []

    def append(self, cb: Callable[[bool, "anki.notes.Note", int], bool]) -> None:
        """(changed: bool, note: anki.notes.Note, current_field_idx: int)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[bool, "anki.notes.Note", int], bool]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self, changed: bool, note: anki.notes.Note, current_field_idx: int
    ) -> bool:
        for filter in self._hooks:
            try:
                changed = filter(changed, note, current_field_idx)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        # legacy support
        changed = runFilter("editFocusLost", changed, note, current_field_idx)
        return changed


editor_did_unfocus_field = _EditorDidUnfocusFieldFilter()


class _EditorDidUpdateTagsHook:
    _hooks: List[Callable[["anki.notes.Note"], None]] = []

    def append(self, cb: Callable[["anki.notes.Note"], None]) -> None:
        """(note: anki.notes.Note)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["anki.notes.Note"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, note: anki.notes.Note) -> None:
        for hook in self._hooks:
            try:
                hook(note)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("tagsUpdated", note)


editor_did_update_tags = _EditorDidUpdateTagsHook()


class _EditorWebViewDidInitHook:
    _hooks: List[Callable[["aqt.editor.EditorWebView"], None]] = []

    def append(self, cb: Callable[["aqt.editor.EditorWebView"], None]) -> None:
        """(editor_web_view: aqt.editor.EditorWebView)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.editor.EditorWebView"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, editor_web_view: aqt.editor.EditorWebView) -> None:
        for hook in self._hooks:
            try:
                hook(editor_web_view)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


editor_web_view_did_init = _EditorWebViewDidInitHook()


class _EditorWillLoadNoteFilter:
    """Allows changing the javascript commands to load note before
    executing it and do change in the QT editor."""

    _hooks: List[Callable[[str, "anki.notes.Note", "aqt.editor.Editor"], str]] = []

    def append(
        self, cb: Callable[[str, "anki.notes.Note", "aqt.editor.Editor"], str]
    ) -> None:
        """(js: str, note: anki.notes.Note, editor: aqt.editor.Editor)"""
        self._hooks.append(cb)

    def remove(
        self, cb: Callable[[str, "anki.notes.Note", "aqt.editor.Editor"], str]
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self, js: str, note: anki.notes.Note, editor: aqt.editor.Editor
    ) -> str:
        for filter in self._hooks:
            try:
                js = filter(js, note, editor)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        return js


editor_will_load_note = _EditorWillLoadNoteFilter()


class _EditorWillMungeHtmlFilter:
    """Allows manipulating the text that will be saved by the editor"""

    _hooks: List[Callable[[str, "aqt.editor.Editor"], str]] = []

    def append(self, cb: Callable[[str, "aqt.editor.Editor"], str]) -> None:
        """(txt: str, editor: aqt.editor.Editor)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[str, "aqt.editor.Editor"], str]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, txt: str, editor: aqt.editor.Editor) -> str:
        for filter in self._hooks:
            try:
                txt = filter(txt, editor)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        return txt


editor_will_munge_html = _EditorWillMungeHtmlFilter()


class _EditorWillShowContextMenuHook:
    _hooks: List[Callable[["aqt.editor.EditorWebView", QMenu], None]] = []

    def append(self, cb: Callable[["aqt.editor.EditorWebView", QMenu], None]) -> None:
        """(editor_webview: aqt.editor.EditorWebView, menu: QMenu)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.editor.EditorWebView", QMenu], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, editor_webview: aqt.editor.EditorWebView, menu: QMenu) -> None:
        for hook in self._hooks:
            try:
                hook(editor_webview, menu)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("EditorWebView.contextMenuEvent", editor_webview, menu)


editor_will_show_context_menu = _EditorWillShowContextMenuHook()


class _EditorWillUseFontForFieldFilter:
    _hooks: List[Callable[[str], str]] = []

    def append(self, cb: Callable[[str], str]) -> None:
        """(font: str)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[str], str]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, font: str) -> str:
        for filter in self._hooks:
            try:
                font = filter(font)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        # legacy support
        font = runFilter("mungeEditingFontName", font)
        return font


editor_will_use_font_for_field = _EditorWillUseFontForFieldFilter()


class _EmptyCardsWillShowHook:
    """Allows changing the list of cards to delete."""

    _hooks: List[Callable[["aqt.emptycards.EmptyCardsDialog"], None]] = []

    def append(self, cb: Callable[["aqt.emptycards.EmptyCardsDialog"], None]) -> None:
        """(diag: aqt.emptycards.EmptyCardsDialog)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.emptycards.EmptyCardsDialog"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, diag: aqt.emptycards.EmptyCardsDialog) -> None:
        for hook in self._hooks:
            try:
                hook(diag)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


empty_cards_will_show = _EmptyCardsWillShowHook()


class _MainWindowDidInitHook:
    """Executed after the main window is fully initialized

    A sample use case for this hook would be to delay actions until Anki objects
    like the profile or collection are fully initialized. In contrast to
    `profile_did_open`, this hook will only fire once per Anki session and
    is thus suitable for single-shot subscribers.
    """

    _hooks: List[Callable[[], None]] = []

    def append(self, cb: Callable[[], None]) -> None:
        """()"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self) -> None:
        for hook in self._hooks:
            try:
                hook()
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


main_window_did_init = _MainWindowDidInitHook()


class _MainWindowShouldRequireResetFilter:
    """Executed before the main window will require a reset

    This hook can be used to change the behavior of the main window,
    when other dialogs, like the AddCards or Browser, require a reset
    from the main window.
    If you decide to use this hook, make you sure you check the reason for the reset.
    Some reasons require more attention than others, and skipping important ones might
    put the main window into an invalid state (e.g. display a deleted note).
    """

    _hooks: List[
        Callable[[bool, "Union[aqt.main.ResetReason, str]", Optional[Any]], bool]
    ] = []

    def append(
        self,
        cb: Callable[[bool, "Union[aqt.main.ResetReason, str]", Optional[Any]], bool],
    ) -> None:
        """(will_reset: bool, reason: Union[aqt.main.ResetReason, str], context: Optional[Any])"""
        self._hooks.append(cb)

    def remove(
        self,
        cb: Callable[[bool, "Union[aqt.main.ResetReason, str]", Optional[Any]], bool],
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self,
        will_reset: bool,
        reason: Union[aqt.main.ResetReason, str],
        context: Optional[Any],
    ) -> bool:
        for filter in self._hooks:
            try:
                will_reset = filter(will_reset, reason, context)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        return will_reset


main_window_should_require_reset = _MainWindowShouldRequireResetFilter()


class _MediaSyncDidProgressHook:
    _hooks: List[Callable[["aqt.mediasync.LogEntryWithTime"], None]] = []

    def append(self, cb: Callable[["aqt.mediasync.LogEntryWithTime"], None]) -> None:
        """(entry: aqt.mediasync.LogEntryWithTime)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.mediasync.LogEntryWithTime"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, entry: aqt.mediasync.LogEntryWithTime) -> None:
        for hook in self._hooks:
            try:
                hook(entry)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


media_sync_did_progress = _MediaSyncDidProgressHook()


class _MediaSyncDidStartOrStopHook:
    _hooks: List[Callable[[bool], None]] = []

    def append(self, cb: Callable[[bool], None]) -> None:
        """(running: bool)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[bool], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, running: bool) -> None:
        for hook in self._hooks:
            try:
                hook(running)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


media_sync_did_start_or_stop = _MediaSyncDidStartOrStopHook()


class _ModelsAdvancedWillShowHook:
    _hooks: List[Callable[[QDialog], None]] = []

    def append(self, cb: Callable[[QDialog], None]) -> None:
        """(advanced: QDialog)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[QDialog], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, advanced: QDialog) -> None:
        for hook in self._hooks:
            try:
                hook(advanced)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


models_advanced_will_show = _ModelsAdvancedWillShowHook()


class _ModelsDidInitButtonsFilter:
    """Allows adding buttons to the Model dialog"""

    _hooks: List[
        Callable[
            [List[Tuple[str, Callable[[], None]]], "aqt.models.Models"],
            List[Tuple[str, Callable[[], None]]],
        ]
    ] = []

    def append(
        self,
        cb: Callable[
            [List[Tuple[str, Callable[[], None]]], "aqt.models.Models"],
            List[Tuple[str, Callable[[], None]]],
        ],
    ) -> None:
        """(buttons: List[Tuple[str, Callable[[], None]]], models: aqt.models.Models)"""
        self._hooks.append(cb)

    def remove(
        self,
        cb: Callable[
            [List[Tuple[str, Callable[[], None]]], "aqt.models.Models"],
            List[Tuple[str, Callable[[], None]]],
        ],
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self, buttons: List[Tuple[str, Callable[[], None]]], models: aqt.models.Models
    ) -> List[Tuple[str, Callable[[], None]]]:
        for filter in self._hooks:
            try:
                buttons = filter(buttons, models)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        return buttons


models_did_init_buttons = _ModelsDidInitButtonsFilter()


class _OverviewDidRefreshHook:
    """Allow to update the overview window. E.g. add the deck name in the
    title."""

    _hooks: List[Callable[["aqt.overview.Overview"], None]] = []

    def append(self, cb: Callable[["aqt.overview.Overview"], None]) -> None:
        """(overview: aqt.overview.Overview)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.overview.Overview"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, overview: aqt.overview.Overview) -> None:
        for hook in self._hooks:
            try:
                hook(overview)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


overview_did_refresh = _OverviewDidRefreshHook()


class _OverviewWillRenderContentHook:
    """Used to modify HTML content sections in the overview body

            'content' contains the sections of HTML content the overview body
            will be updated with.

            When modifying the content of a particular section, please make sure your
            changes only perform the minimum required edits to make your add-on work.
            You should avoid overwriting or interfering with existing data as much
            as possible, instead opting to append your own changes, e.g.:

                def on_overview_will_render_content(overview, content):
                    content.table += "
    <div>my html</div>"
    """

    _hooks: List[
        Callable[["aqt.overview.Overview", "aqt.overview.OverviewContent"], None]
    ] = []

    def append(
        self,
        cb: Callable[["aqt.overview.Overview", "aqt.overview.OverviewContent"], None],
    ) -> None:
        """(overview: aqt.overview.Overview, content: aqt.overview.OverviewContent)"""
        self._hooks.append(cb)

    def remove(
        self,
        cb: Callable[["aqt.overview.Overview", "aqt.overview.OverviewContent"], None],
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self, overview: aqt.overview.Overview, content: aqt.overview.OverviewContent
    ) -> None:
        for hook in self._hooks:
            try:
                hook(overview, content)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


overview_will_render_content = _OverviewWillRenderContentHook()


class _ProfileDidOpenHook:
    """Executed whenever a user profile has been opened

    Please note that this hook will also be called on profile switches, so if you
    are looking to simply delay an add-on action in a single-shot manner,
    `main_window_did_init` is likely the more suitable choice.
    """

    _hooks: List[Callable[[], None]] = []

    def append(self, cb: Callable[[], None]) -> None:
        """()"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self) -> None:
        for hook in self._hooks:
            try:
                hook()
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("profileLoaded")


profile_did_open = _ProfileDidOpenHook()


class _ProfileWillCloseHook:
    _hooks: List[Callable[[], None]] = []

    def append(self, cb: Callable[[], None]) -> None:
        """()"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self) -> None:
        for hook in self._hooks:
            try:
                hook()
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("unloadProfile")


profile_will_close = _ProfileWillCloseHook()


class _ReviewDidUndoHook:
    _hooks: List[Callable[[int], None]] = []

    def append(self, cb: Callable[[int], None]) -> None:
        """(card_id: int)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[int], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, card_id: int) -> None:
        for hook in self._hooks:
            try:
                hook(card_id)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("revertedCard", card_id)


review_did_undo = _ReviewDidUndoHook()


class _ReviewerDidAnswerCardHook:
    _hooks: List[Callable[["aqt.reviewer.Reviewer", Card, int], None]] = []

    def append(self, cb: Callable[["aqt.reviewer.Reviewer", Card, int], None]) -> None:
        """(reviewer: aqt.reviewer.Reviewer, card: Card, ease: int)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.reviewer.Reviewer", Card, int], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, reviewer: aqt.reviewer.Reviewer, card: Card, ease: int) -> None:
        for hook in self._hooks:
            try:
                hook(reviewer, card, ease)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


reviewer_did_answer_card = _ReviewerDidAnswerCardHook()


class _ReviewerDidShowAnswerHook:
    _hooks: List[Callable[[Card], None]] = []

    def append(self, cb: Callable[[Card], None]) -> None:
        """(card: Card)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[Card], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, card: Card) -> None:
        for hook in self._hooks:
            try:
                hook(card)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("showAnswer")


reviewer_did_show_answer = _ReviewerDidShowAnswerHook()


class _ReviewerDidShowQuestionHook:
    _hooks: List[Callable[[Card], None]] = []

    def append(self, cb: Callable[[Card], None]) -> None:
        """(card: Card)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[Card], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, card: Card) -> None:
        for hook in self._hooks:
            try:
                hook(card)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("showQuestion")


reviewer_did_show_question = _ReviewerDidShowQuestionHook()


class _ReviewerWillAnswerCardFilter:
    """Used to modify the ease at which a card is rated or to bypass
    rating the card completely.

    ease_tuple is a tuple consisting of a boolean expressing whether the reviewer
    should continue with rating the card, and an integer expressing the ease at
    which the card should be rated.

    If your code just needs to be notified of the card rating event, you should use
    the reviewer_did_answer_card hook instead."""

    _hooks: List[
        Callable[[Tuple[bool, int], "aqt.reviewer.Reviewer", Card], Tuple[bool, int]]
    ] = []

    def append(
        self,
        cb: Callable[
            [Tuple[bool, int], "aqt.reviewer.Reviewer", Card], Tuple[bool, int]
        ],
    ) -> None:
        """(ease_tuple: Tuple[bool, int], reviewer: aqt.reviewer.Reviewer, card: Card)"""
        self._hooks.append(cb)

    def remove(
        self,
        cb: Callable[
            [Tuple[bool, int], "aqt.reviewer.Reviewer", Card], Tuple[bool, int]
        ],
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self, ease_tuple: Tuple[bool, int], reviewer: aqt.reviewer.Reviewer, card: Card
    ) -> Tuple[bool, int]:
        for filter in self._hooks:
            try:
                ease_tuple = filter(ease_tuple, reviewer, card)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        return ease_tuple


reviewer_will_answer_card = _ReviewerWillAnswerCardFilter()


class _ReviewerWillEndHook:
    """Called before Anki transitions from the review screen to another screen."""

    _hooks: List[Callable[[], None]] = []

    def append(self, cb: Callable[[], None]) -> None:
        """()"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self) -> None:
        for hook in self._hooks:
            try:
                hook()
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("reviewCleanup")


reviewer_will_end = _ReviewerWillEndHook()


class _ReviewerWillInitAnswerButtonsFilter:
    """Used to modify list of answer buttons

    buttons_tuple is a tuple of buttons, with each button represented by a
    tuple containing an int for the button's ease and a string for the
    button's label.

    Return a tuple of the form ((int, str), ...), e.g.:
        ((1, "Label1"), (2, "Label2"), ...)

    Note: import _ from anki.lang to support translation, using, e.g.,
        ((1, _("Label1")), ...)
    """

    _hooks: List[
        Callable[
            ["Tuple[Tuple[int, str], ...]", "aqt.reviewer.Reviewer", Card],
            Tuple[Tuple[int, str], ...],
        ]
    ] = []

    def append(
        self,
        cb: Callable[
            ["Tuple[Tuple[int, str], ...]", "aqt.reviewer.Reviewer", Card],
            Tuple[Tuple[int, str], ...],
        ],
    ) -> None:
        """(buttons_tuple: Tuple[Tuple[int, str], ...], reviewer: aqt.reviewer.Reviewer, card: Card)"""
        self._hooks.append(cb)

    def remove(
        self,
        cb: Callable[
            ["Tuple[Tuple[int, str], ...]", "aqt.reviewer.Reviewer", Card],
            Tuple[Tuple[int, str], ...],
        ],
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self,
        buttons_tuple: Tuple[Tuple[int, str], ...],
        reviewer: aqt.reviewer.Reviewer,
        card: Card,
    ) -> Tuple[Tuple[int, str], ...]:
        for filter in self._hooks:
            try:
                buttons_tuple = filter(buttons_tuple, reviewer, card)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        return buttons_tuple


reviewer_will_init_answer_buttons = _ReviewerWillInitAnswerButtonsFilter()


class _ReviewerWillPlayAnswerSoundsHook:
    """Called before showing the answer/back side.

    `tags` can be used to inspect and manipulate the sounds
    that will be played (if any).

    This won't be called when the user manually plays sounds
    using `Replay Audio`.

    Note that this hook is called even when the `Automatically play audio`
    option is unchecked; This is so as to allow playing custom
    sounds regardless of that option."""

    _hooks: List[Callable[[Card, "List[anki.sound.AVTag]"], None]] = []

    def append(self, cb: Callable[[Card, "List[anki.sound.AVTag]"], None]) -> None:
        """(card: Card, tags: List[anki.sound.AVTag])"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[Card, "List[anki.sound.AVTag]"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, card: Card, tags: List[anki.sound.AVTag]) -> None:
        for hook in self._hooks:
            try:
                hook(card, tags)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


reviewer_will_play_answer_sounds = _ReviewerWillPlayAnswerSoundsHook()


class _ReviewerWillPlayQuestionSoundsHook:
    """Called before showing the question/front side.

    `tags` can be used to inspect and manipulate the sounds
    that will be played (if any).

    This won't be called when the user manually plays sounds
    using `Replay Audio`.

    Note that this hook is called even when the `Automatically play audio`
    option is unchecked; This is so as to allow playing custom
    sounds regardless of that option."""

    _hooks: List[Callable[[Card, "List[anki.sound.AVTag]"], None]] = []

    def append(self, cb: Callable[[Card, "List[anki.sound.AVTag]"], None]) -> None:
        """(card: Card, tags: List[anki.sound.AVTag])"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[Card, "List[anki.sound.AVTag]"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, card: Card, tags: List[anki.sound.AVTag]) -> None:
        for hook in self._hooks:
            try:
                hook(card, tags)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


reviewer_will_play_question_sounds = _ReviewerWillPlayQuestionSoundsHook()


class _ReviewerWillShowContextMenuHook:
    _hooks: List[Callable[["aqt.reviewer.Reviewer", QMenu], None]] = []

    def append(self, cb: Callable[["aqt.reviewer.Reviewer", QMenu], None]) -> None:
        """(reviewer: aqt.reviewer.Reviewer, menu: QMenu)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.reviewer.Reviewer", QMenu], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, reviewer: aqt.reviewer.Reviewer, menu: QMenu) -> None:
        for hook in self._hooks:
            try:
                hook(reviewer, menu)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("Reviewer.contextMenuEvent", reviewer, menu)


reviewer_will_show_context_menu = _ReviewerWillShowContextMenuHook()


class _SidebarShouldRefreshDecksHook:
    _hooks: List[Callable[[], None]] = []

    def append(self, cb: Callable[[], None]) -> None:
        """()"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self) -> None:
        for hook in self._hooks:
            try:
                hook()
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


sidebar_should_refresh_decks = _SidebarShouldRefreshDecksHook()


class _SidebarShouldRefreshNotetypesHook:
    _hooks: List[Callable[[], None]] = []

    def append(self, cb: Callable[[], None]) -> None:
        """()"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self) -> None:
        for hook in self._hooks:
            try:
                hook()
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


sidebar_should_refresh_notetypes = _SidebarShouldRefreshNotetypesHook()


class _StateDidChangeHook:
    _hooks: List[Callable[[str, str], None]] = []

    def append(self, cb: Callable[[str, str], None]) -> None:
        """(new_state: str, old_state: str)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[str, str], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, new_state: str, old_state: str) -> None:
        for hook in self._hooks:
            try:
                hook(new_state, old_state)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("afterStateChange", new_state, old_state)


state_did_change = _StateDidChangeHook()


class _StateDidResetHook:
    """Called when the interface needs to be redisplayed after non-trivial changes have been made."""

    _hooks: List[Callable[[], None]] = []

    def append(self, cb: Callable[[], None]) -> None:
        """()"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self) -> None:
        for hook in self._hooks:
            try:
                hook()
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("reset")


state_did_reset = _StateDidResetHook()


class _StateDidRevertHook:
    """Called when user used the undo option to restore to an earlier database state."""

    _hooks: List[Callable[[str], None]] = []

    def append(self, cb: Callable[[str], None]) -> None:
        """(action: str)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[str], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, action: str) -> None:
        for hook in self._hooks:
            try:
                hook(action)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("revertedState", action)


state_did_revert = _StateDidRevertHook()


class _StateShortcutsWillChangeHook:
    _hooks: List[Callable[[str, List[Tuple[str, Callable]]], None]] = []

    def append(self, cb: Callable[[str, List[Tuple[str, Callable]]], None]) -> None:
        """(state: str, shortcuts: List[Tuple[str, Callable]])"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[str, List[Tuple[str, Callable]]], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, state: str, shortcuts: List[Tuple[str, Callable]]) -> None:
        for hook in self._hooks:
            try:
                hook(state, shortcuts)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


state_shortcuts_will_change = _StateShortcutsWillChangeHook()


class _StateWillChangeHook:
    _hooks: List[Callable[[str, str], None]] = []

    def append(self, cb: Callable[[str, str], None]) -> None:
        """(new_state: str, old_state: str)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[str, str], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, new_state: str, old_state: str) -> None:
        for hook in self._hooks:
            try:
                hook(new_state, old_state)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("beforeStateChange", new_state, old_state)


state_will_change = _StateWillChangeHook()


class _StatsDialogOldWillShowHook:
    """Allows changing the old stats dialog before it is shown."""

    _hooks: List[Callable[["aqt.stats.DeckStats"], None]] = []

    def append(self, cb: Callable[["aqt.stats.DeckStats"], None]) -> None:
        """(dialog: aqt.stats.DeckStats)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.stats.DeckStats"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, dialog: aqt.stats.DeckStats) -> None:
        for hook in self._hooks:
            try:
                hook(dialog)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


stats_dialog_old_will_show = _StatsDialogOldWillShowHook()


class _StatsDialogWillShowHook:
    """Allows changing the stats dialog before it is shown."""

    _hooks: List[Callable[["aqt.stats.NewDeckStats"], None]] = []

    def append(self, cb: Callable[["aqt.stats.NewDeckStats"], None]) -> None:
        """(dialog: aqt.stats.NewDeckStats)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.stats.NewDeckStats"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, dialog: aqt.stats.NewDeckStats) -> None:
        for hook in self._hooks:
            try:
                hook(dialog)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


stats_dialog_will_show = _StatsDialogWillShowHook()


class _StyleDidInitFilter:
    _hooks: List[Callable[[str], str]] = []

    def append(self, cb: Callable[[str], str]) -> None:
        """(style: str)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[str], str]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, style: str) -> str:
        for filter in self._hooks:
            try:
                style = filter(style)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        # legacy support
        style = runFilter("setupStyle", style)
        return style


style_did_init = _StyleDidInitFilter()


class _SyncDidFinishHook:
    """Executes after the sync of the collection concluded.

    Note that the media sync did not necessarily finish at this point."""

    _hooks: List[Callable[[], None]] = []

    def append(self, cb: Callable[[], None]) -> None:
        """()"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self) -> None:
        for hook in self._hooks:
            try:
                hook()
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


sync_did_finish = _SyncDidFinishHook()


class _SyncWillStartHook:
    _hooks: List[Callable[[], None]] = []

    def append(self, cb: Callable[[], None]) -> None:
        """()"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self) -> None:
        for hook in self._hooks:
            try:
                hook()
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


sync_will_start = _SyncWillStartHook()


class _TagEditorDidProcessKeyHook:
    _hooks: List[Callable[[TagEdit, QEvent], None]] = []

    def append(self, cb: Callable[[TagEdit, QEvent], None]) -> None:
        """(tag_edit: TagEdit, evt: QEvent)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[TagEdit, QEvent], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, tag_edit: TagEdit, evt: QEvent) -> None:
        for hook in self._hooks:
            try:
                hook(tag_edit, evt)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


tag_editor_did_process_key = _TagEditorDidProcessKeyHook()


class _TopToolbarDidInitLinksHook:
    """Used to modify or add links in the top toolbar of Anki's main window

    'links' is a list of HTML link elements. Add-ons can generate their own links
    by using aqt.toolbar.Toolbar.create_link. Links created in that way can then be
    appended to the link list, e.g.:

        def on_top_toolbar_did_init_links(links, toolbar):
            my_link = toolbar.create_link(...)
            links.append(my_link)
    """

    _hooks: List[Callable[[List[str], "aqt.toolbar.Toolbar"], None]] = []

    def append(self, cb: Callable[[List[str], "aqt.toolbar.Toolbar"], None]) -> None:
        """(links: List[str], top_toolbar: aqt.toolbar.Toolbar)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[List[str], "aqt.toolbar.Toolbar"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, links: List[str], top_toolbar: aqt.toolbar.Toolbar) -> None:
        for hook in self._hooks:
            try:
                hook(links, top_toolbar)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


top_toolbar_did_init_links = _TopToolbarDidInitLinksHook()


class _TopToolbarDidRedrawHook:
    """Executed when the top toolbar is redrawn"""

    _hooks: List[Callable[["aqt.toolbar.Toolbar"], None]] = []

    def append(self, cb: Callable[["aqt.toolbar.Toolbar"], None]) -> None:
        """(top_toolbar: aqt.toolbar.Toolbar)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.toolbar.Toolbar"], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, top_toolbar: aqt.toolbar.Toolbar) -> None:
        for hook in self._hooks:
            try:
                hook(top_toolbar)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


top_toolbar_did_redraw = _TopToolbarDidRedrawHook()


class _UndoStateDidChangeHook:
    _hooks: List[Callable[[bool], None]] = []

    def append(self, cb: Callable[[bool], None]) -> None:
        """(can_undo: bool)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[[bool], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, can_undo: bool) -> None:
        for hook in self._hooks:
            try:
                hook(can_undo)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("undoState", can_undo)


undo_state_did_change = _UndoStateDidChangeHook()


class _WebviewDidReceiveJsMessageFilter:
    """Used to handle pycmd() messages sent from Javascript.

    Message is the string passed to pycmd().

    For messages you don't want to handle, return 'handled' unchanged.

    If you handle a message and don't want it passed to the original
    bridge command handler, return (True, None).

    If you want to pass a value to pycmd's result callback, you can
    return it with (True, some_value).

    Context is the instance that was passed to set_bridge_command().
    It can be inspected to check which screen this hook is firing
    in, and to get a reference to the screen. For example, if your
    code wishes to function only in the review screen, you could do:

        if not isinstance(context, aqt.reviewer.Reviewer):
            # not reviewer, pass on message
            return handled

        if message == "my-mark-action":
            # our message, call onMark() on the reviewer instance
            context.onMark()
            # and don't pass message to other handlers
            return (True, None)
        else:
            # some other command, pass it on
            return handled
    """

    _hooks: List[Callable[[Tuple[bool, Any], str, Any], Tuple[bool, Any]]] = []

    def append(
        self, cb: Callable[[Tuple[bool, Any], str, Any], Tuple[bool, Any]]
    ) -> None:
        """(handled: Tuple[bool, Any], message: str, context: Any)"""
        self._hooks.append(cb)

    def remove(
        self, cb: Callable[[Tuple[bool, Any], str, Any], Tuple[bool, Any]]
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self, handled: Tuple[bool, Any], message: str, context: Any
    ) -> Tuple[bool, Any]:
        for filter in self._hooks:
            try:
                handled = filter(handled, message, context)
            except:
                # if the hook fails, remove it
                self._hooks.remove(filter)
                raise
        return handled


webview_did_receive_js_message = _WebviewDidReceiveJsMessageFilter()


class _WebviewWillSetContentHook:
    """Used to modify web content before it is rendered.

    Web_content contains the HTML, JS, and CSS the web view will be
    populated with.

    Context is the instance that was passed to stdHtml().
    It can be inspected to check which screen this hook is firing
    in, and to get a reference to the screen. For example, if your
    code wishes to function only in the review screen, you could do:

        def on_webview_will_set_content(web_content: WebContent, context):

            if not isinstance(context, aqt.reviewer.Reviewer):
                # not reviewer, do not modify content
                return

            # reviewer, perform changes to content

            context: aqt.reviewer.Reviewer

            addon_package = mw.addonManager.addonFromModule(__name__)

            web_content.css.append(
                f"/_addons/{addon_package}/web/my-addon.css")
            web_content.js.append(
                f"/_addons/{addon_package}/web/my-addon.js")

            web_content.head += "<script>console.log('my-addon')</script>"
            web_content.body += "<div id='my-addon'></div>"
    """

    _hooks: List[Callable[["aqt.webview.WebContent", Optional[Any]], None]] = []

    def append(
        self, cb: Callable[["aqt.webview.WebContent", Optional[Any]], None]
    ) -> None:
        """(web_content: aqt.webview.WebContent, context: Optional[Any])"""
        self._hooks.append(cb)

    def remove(
        self, cb: Callable[["aqt.webview.WebContent", Optional[Any]], None]
    ) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(
        self, web_content: aqt.webview.WebContent, context: Optional[Any]
    ) -> None:
        for hook in self._hooks:
            try:
                hook(web_content, context)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise


webview_will_set_content = _WebviewWillSetContentHook()


class _WebviewWillShowContextMenuHook:
    _hooks: List[Callable[["aqt.webview.AnkiWebView", QMenu], None]] = []

    def append(self, cb: Callable[["aqt.webview.AnkiWebView", QMenu], None]) -> None:
        """(webview: aqt.webview.AnkiWebView, menu: QMenu)"""
        self._hooks.append(cb)

    def remove(self, cb: Callable[["aqt.webview.AnkiWebView", QMenu], None]) -> None:
        if cb in self._hooks:
            self._hooks.remove(cb)

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self, webview: aqt.webview.AnkiWebView, menu: QMenu) -> None:
        for hook in self._hooks:
            try:
                hook(webview, menu)
            except:
                # if the hook fails, remove it
                self._hooks.remove(hook)
                raise
        # legacy support
        runHook("AnkiWebView.contextMenuEvent", webview, menu)


webview_will_show_context_menu = _WebviewWillShowContextMenuHook()
# @@AUTOGEN@@
