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

"""
See pylib/tools/genhooks.py for more info.
"""

import os
import sys

from hookslib import Hook, write_file

prefix = """\
# This file is automatically generated; edit tools/genhooks_gui.py instead.
# Please import from anki.hooks instead of this file.

from __future__ import annotations

from typing import Any, Callable, List, Sequence, Tuple, Optional, 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
"""

# Hook list
######################################################################

hooks = [
    # Reviewing
    ###################
    Hook(
        name="overview_did_refresh",
        args=["overview: aqt.overview.Overview"],
        doc="""Allow to update the overview window. E.g. add the deck name in the
        title.""",
    ),
    Hook(
        name="overview_will_render_content",
        args=[
            "overview: aqt.overview.Overview",
            "content: aqt.overview.OverviewContent",
        ],
        doc="""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 += "\n<div>my html</div>"
        """,
    ),
    Hook(
        name="reviewer_did_show_question",
        args=["card: Card"],
        legacy_hook="showQuestion",
        legacy_no_args=True,
    ),
    Hook(
        name="reviewer_did_show_answer",
        args=["card: Card"],
        legacy_hook="showAnswer",
        legacy_no_args=True,
    ),
    Hook(
        name="reviewer_will_init_answer_buttons",
        args=[
            "buttons_tuple: Tuple[Tuple[int, str], ...]",
            "reviewer: aqt.reviewer.Reviewer",
            "card: Card",
        ],
        return_type="Tuple[Tuple[int, str], ...]",
        doc="""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")), ...)
        """,
    ),
    Hook(
        name="reviewer_will_answer_card",
        args=[
            "ease_tuple: Tuple[bool, int]",
            "reviewer: aqt.reviewer.Reviewer",
            "card: Card",
        ],
        return_type="Tuple[bool, int]",
        doc="""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.""",
    ),
    Hook(
        name="reviewer_did_answer_card",
        args=["reviewer: aqt.reviewer.Reviewer", "card: Card", "ease: int"],
    ),
    Hook(
        name="reviewer_will_show_context_menu",
        args=["reviewer: aqt.reviewer.Reviewer", "menu: QMenu"],
        legacy_hook="Reviewer.contextMenuEvent",
    ),
    Hook(
        name="reviewer_will_end",
        legacy_hook="reviewCleanup",
        doc="Called before Anki transitions from the review screen to another screen.",
    ),
    Hook(
        name="reviewer_will_play_question_sounds",
        args=["card: Card", "tags: List[anki.sound.AVTag]"],
        doc="""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.""",
    ),
    Hook(
        name="reviewer_will_play_answer_sounds",
        args=["card: Card", "tags: List[anki.sound.AVTag]"],
        doc="""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.""",
    ),
    # Debug
    ###################
    Hook(
        name="debug_console_will_show",
        args=["debug_window: QDialog"],
        doc="""Allows editing the debug window. E.g. setting a default code, or
        previous code.""",
    ),
    Hook(
        name="debug_console_did_evaluate_python",
        args=["output: str", "query: str", "debug_window: QDialog"],
        return_type="str",
        doc="""Allows processing the debug result. E.g. logging queries and
        result, saving last query to display it later...""",
    ),
    # Card layout
    ###################
    Hook(
        name="card_layout_will_show",
        args=["clayout: aqt.clayout.CardLayout"],
        doc="""Allow to change the display of the card layout. After most values are
         set and before the window is actually shown.""",
    ),
    # Multiple windows
    ###################
    # reviewer, clayout and browser
    Hook(
        name="card_will_show",
        args=["text: str", "card: Card", "kind: str"],
        return_type="str",
        legacy_hook="prepareQA",
        doc="Can modify card text before review/preview.",
    ),
    # Deck browser
    ###################
    Hook(
        name="deck_browser_did_render",
        args=["deck_browser: aqt.deckbrowser.DeckBrowser"],
        doc="""Allow to update the deck browser window. E.g. change its title.""",
    ),
    Hook(
        name="deck_browser_will_render_content",
        args=[
            "deck_browser: aqt.deckbrowser.DeckBrowser",
            "content: aqt.deckbrowser.DeckBrowserContent",
        ],
        doc="""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 += "\n<div>my html</div>"
        """,
    ),
    # Deck options
    ###################
    Hook(
        name="deck_conf_did_setup_ui_form",
        args=["deck_conf: aqt.deckconf.DeckConf"],
        doc="Allows modifying or adding widgets in the deck options UI form",
    ),
    Hook(
        name="deck_conf_will_show",
        args=["deck_conf: aqt.deckconf.DeckConf"],
        doc="Allows modifying the deck options dialog before it is shown",
    ),
    Hook(
        name="deck_conf_did_load_config",
        args=["deck_conf: aqt.deckconf.DeckConf", "deck: Deck", "config: DeckConfig"],
        doc="Called once widget state has been set from deck config",
    ),
    Hook(
        name="deck_conf_will_save_config",
        args=["deck_conf: aqt.deckconf.DeckConf", "deck: Deck", "config: DeckConfig"],
        doc="Called before widget state is saved to config",
    ),
    Hook(
        name="deck_conf_did_add_config",
        args=[
            "deck_conf: aqt.deckconf.DeckConf",
            "deck: Deck",
            "config: DeckConfig",
            "new_name: str",
            "new_conf_id: int",
        ],
        doc="""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.
        """,
    ),
    Hook(
        name="deck_conf_will_remove_config",
        args=["deck_conf: aqt.deckconf.DeckConf", "deck: Deck", "config: DeckConfig"],
        doc="Called before current config group is removed",
    ),
    Hook(
        name="deck_conf_will_rename_config",
        args=[
            "deck_conf: aqt.deckconf.DeckConf",
            "deck: Deck",
            "config: DeckConfig",
            "new_name: str",
        ],
        doc="Called before config group is renamed",
    ),
    # Browser
    ###################
    Hook(name="browser_will_show", args=["browser: aqt.browser.Browser"]),
    Hook(
        name="browser_menus_did_init",
        args=["browser: aqt.browser.Browser"],
        legacy_hook="browser.setupMenus",
    ),
    Hook(
        name="browser_will_show_context_menu",
        args=["browser: aqt.browser.Browser", "menu: QMenu"],
        legacy_hook="browser.onContextMenu",
    ),
    Hook(
        name="browser_header_will_show_context_menu",
        args=["browser: aqt.browser.Browser", "menu: QMenu"],
    ),
    Hook(
        name="browser_did_change_row",
        args=["browser: aqt.browser.Browser"],
        legacy_hook="browser.rowChanged",
    ),
    Hook(
        name="browser_will_build_tree",
        args=[
            "handled: bool",
            "tree: aqt.browser.SidebarItem",
            "stage: aqt.browser.SidebarStage",
            "browser: aqt.browser.Browser",
        ],
        return_type="bool",
        doc="""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
        """,
    ),
    Hook(
        name="browser_will_search",
        args=["context: aqt.browser.SearchContext"],
        doc="""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.
         """,
    ),
    Hook(
        name="browser_did_search",
        args=["context: aqt.browser.SearchContext"],
        doc="""Allows you to modify the list of returned card ids from a search.""",
    ),
    # States
    ###################
    Hook(
        name="state_will_change",
        args=["new_state: str", "old_state: str"],
        legacy_hook="beforeStateChange",
    ),
    Hook(
        name="state_did_change",
        args=["new_state: str", "old_state: str"],
        legacy_hook="afterStateChange",
    ),
    # different sig to original
    Hook(
        name="state_shortcuts_will_change",
        args=["state: str", "shortcuts: List[Tuple[str, Callable]]"],
    ),
    Hook(
        name="state_did_revert",
        args=["action: str"],
        legacy_hook="revertedState",
        doc="Called when user used the undo option to restore to an earlier database state.",
    ),
    Hook(
        name="state_did_reset",
        legacy_hook="reset",
        doc="Called when the interface needs to be redisplayed after non-trivial changes have been made.",
    ),
    # Webview
    ###################
    Hook(
        name="webview_did_receive_js_message",
        args=["handled: Tuple[bool, Any]", "message: str", "context: Any"],
        return_type="Tuple[bool, Any]",
        doc="""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
        """,
    ),
    Hook(
        name="webview_will_set_content",
        args=[
            "web_content: aqt.webview.WebContent",
            "context: Optional[Any]",
        ],
        doc="""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>"
        """,
    ),
    Hook(
        name="webview_will_show_context_menu",
        args=["webview: aqt.webview.AnkiWebView", "menu: QMenu"],
        legacy_hook="AnkiWebView.contextMenuEvent",
    ),
    Hook(
        name="webview_did_inject_style_into_page",
        args=["webview: aqt.webview.AnkiWebView"],
        doc='''Called after standard styling is injected into an external
html file, such as when loading the new graphs. You can use this hook to
mutate the DOM before the page is revealed.

For example:

def mytest(web: AnkiWebView):
    page = os.path.basename(web.page().url().path())
    if page != "graphs.html":
    	return
    web.eval(
        """
    div = document.createElement("div");
    div.innerHTML = 'hello';
    document.body.appendChild(div);
"""
    )

gui_hooks.webview_did_inject_style_into_page.append(mytest)
''',
    ),
    # Main
    ###################
    Hook(
        name="main_window_did_init",
        doc="""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.
        """,
    ),
    Hook(
        name="main_window_should_require_reset",
        args=[
            "will_reset: bool",
            "reason: Union[aqt.main.ResetReason, str]",
            "context: Optional[Any]",
        ],
        return_type="bool",
        doc="""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).
        """,
    ),
    Hook(name="backup_did_complete"),
    Hook(
        name="profile_did_open",
        legacy_hook="profileLoaded",
        doc="""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.
        """,
    ),
    Hook(name="profile_will_close", legacy_hook="unloadProfile"),
    Hook(
        name="collection_did_load",
        args=["col: anki.collection.Collection"],
        legacy_hook="colLoading",
    ),
    Hook(
        name="undo_state_did_change", args=["can_undo: bool"], legacy_hook="undoState"
    ),
    Hook(name="review_did_undo", args=["card_id: int"], legacy_hook="revertedCard"),
    Hook(
        name="style_did_init",
        args=["style: str"],
        return_type="str",
        legacy_hook="setupStyle",
    ),
    Hook(
        name="top_toolbar_did_init_links",
        args=["links: List[str]", "top_toolbar: aqt.toolbar.Toolbar"],
        doc="""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)
        """,
    ),
    Hook(
        name="top_toolbar_did_redraw",
        args=["top_toolbar: aqt.toolbar.Toolbar"],
        doc="""Executed when the top toolbar is redrawn""",
    ),
    Hook(
        name="media_sync_did_progress",
        args=["entry: aqt.mediasync.LogEntryWithTime"],
    ),
    Hook(name="media_sync_did_start_or_stop", args=["running: bool"]),
    Hook(
        name="empty_cards_will_show",
        args=["diag: aqt.emptycards.EmptyCardsDialog"],
        doc="""Allows changing the list of cards to delete.""",
    ),
    Hook(name="sync_will_start", args=[]),
    Hook(
        name="sync_did_finish",
        args=[],
        doc="""Executes after the sync of the collection concluded.

        Note that the media sync did not necessarily finish at this point.""",
    ),
    Hook(name="media_check_will_start", args=[]),
    # Adding cards
    ###################
    Hook(
        name="add_cards_will_show_history_menu",
        args=["addcards: aqt.addcards.AddCards", "menu: QMenu"],
        legacy_hook="AddCards.onHistory",
    ),
    Hook(
        name="add_cards_did_init",
        args=["addcards: aqt.addcards.AddCards"],
    ),
    Hook(
        name="add_cards_did_add_note",
        args=["note: anki.notes.Note"],
        legacy_hook="AddCards.noteAdded",
    ),
    Hook(
        name="add_cards_will_add_note",
        args=["problem: Optional[str]", "note: anki.notes.Note"],
        return_type="Optional[str]",
        doc="""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.""",
    ),
    Hook(
        name="addcards_will_add_history_entry",
        args=["line: str", "note: anki.notes.Note"],
        return_type="str",
        doc="""Allows changing the history line in the add-card window.""",
    ),
    # Editing
    ###################
    Hook(
        name="editor_did_init_left_buttons",
        args=["buttons: List[str]", "editor: aqt.editor.Editor"],
    ),
    Hook(
        name="editor_did_init_buttons",
        args=["buttons: List[str]", "editor: aqt.editor.Editor"],
    ),
    Hook(
        name="editor_did_init_shortcuts",
        args=["shortcuts: List[Tuple]", "editor: aqt.editor.Editor"],
        legacy_hook="setupEditorShortcuts",
    ),
    Hook(
        name="editor_will_show_context_menu",
        args=["editor_webview: aqt.editor.EditorWebView", "menu: QMenu"],
        legacy_hook="EditorWebView.contextMenuEvent",
    ),
    Hook(
        name="editor_did_fire_typing_timer",
        args=["note: anki.notes.Note"],
        legacy_hook="editTimer",
    ),
    Hook(
        name="editor_did_focus_field",
        args=["note: anki.notes.Note", "current_field_idx: int"],
        legacy_hook="editFocusGained",
    ),
    Hook(
        name="editor_did_unfocus_field",
        args=["changed: bool", "note: anki.notes.Note", "current_field_idx: int"],
        return_type="bool",
        legacy_hook="editFocusLost",
    ),
    Hook(
        name="editor_did_load_note",
        args=["editor: aqt.editor.Editor"],
        legacy_hook="loadNote",
    ),
    Hook(
        name="editor_did_update_tags",
        args=["note: anki.notes.Note"],
        legacy_hook="tagsUpdated",
    ),
    Hook(
        name="editor_will_munge_html",
        args=["txt: str", "editor: aqt.editor.Editor"],
        return_type="str",
        doc="""Allows manipulating the text that will be saved by the editor""",
    ),
    Hook(
        name="editor_will_use_font_for_field",
        args=["font: str"],
        return_type="str",
        legacy_hook="mungeEditingFontName",
    ),
    Hook(
        name="editor_web_view_did_init",
        args=["editor_web_view: aqt.editor.EditorWebView"],
    ),
    Hook(
        name="editor_did_init",
        args=["editor: aqt.editor.Editor"],
    ),
    Hook(
        name="editor_will_load_note",
        args=["js: str", "note: anki.notes.Note", "editor: aqt.editor.Editor"],
        return_type="str",
        doc="""Allows changing the javascript commands to load note before
        executing it and do change in the QT editor.""",
    ),
    # Tag
    ###################
    Hook(name="tag_editor_did_process_key", args=["tag_edit: TagEdit", "evt: QEvent"]),
    # Sound/video
    ###################
    Hook(name="av_player_will_play", args=["tag: anki.sound.AVTag"]),
    Hook(
        name="av_player_did_begin_playing",
        args=["player: aqt.sound.Player", "tag: anki.sound.AVTag"],
    ),
    Hook(name="av_player_did_end_playing", args=["player: aqt.sound.Player"]),
    # Addon
    ###################
    Hook(
        name="addon_config_editor_will_display_json",
        args=["text: str"],
        return_type="str",
        doc="""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.""",
    ),
    Hook(
        name="addon_config_editor_will_save_json",
        args=["text: str"],
        return_type="str",
        doc="""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".""",
    ),
    Hook(
        name="addons_dialog_will_show",
        args=["dialog: aqt.addons.AddonsDialog"],
        doc="""Allows changing the add-on dialog before it is shown. E.g. add
        buttons.""",
    ),
    Hook(
        name="addons_dialog_did_change_selected_addon",
        args=["dialog: aqt.addons.AddonsDialog", "add_on: aqt.addons.AddonMeta"],
        doc="""Allows doing an action when a single add-on is selected.""",
    ),
    # Model
    ###################
    Hook(
        name="models_advanced_will_show",
        args=["advanced: QDialog"],
    ),
    Hook(
        name="models_did_init_buttons",
        args=[
            "buttons: List[Tuple[str, Callable[[], None]]]",
            "models: aqt.models.Models",
        ],
        return_type="List[Tuple[str, Callable[[], None]]]",
        doc="""Allows adding buttons to the Model dialog""",
    ),
    # Fields
    ###################
    Hook(
        name="fields_did_rename_field",
        args=[
            "dialog: aqt.fields.FieldDialog",
            "field: anki.models.Field",
            "old_name: str",
        ],
    ),
    Hook(
        name="fields_did_delete_field",
        args=["dialog: aqt.fields.FieldDialog", "field: anki.models.Field"],
    ),
    # Stats
    ###################
    Hook(
        name="stats_dialog_will_show",
        args=["dialog: aqt.stats.NewDeckStats"],
        doc="""Allows changing the stats dialog before it is shown.""",
    ),
    Hook(
        name="stats_dialog_old_will_show",
        args=["dialog: aqt.stats.DeckStats"],
        doc="""Allows changing the old stats dialog before it is shown.""",
    ),
    # Other
    ###################
    Hook(
        name="current_note_type_did_change",
        args=["notetype: NoteType"],
        legacy_hook="currentModelChanged",
        legacy_no_args=True,
    ),
    Hook(name="sidebar_should_refresh_decks"),
    Hook(name="sidebar_should_refresh_notetypes"),
    Hook(
        name="deck_browser_will_show_options_menu",
        args=["menu: QMenu", "deck_id: int"],
        legacy_hook="showDeckOptions",
    ),
]

suffix = ""

if __name__ == "__main__":
    path = sys.argv[1]
    write_file(path, hooks, prefix, suffix)
