# Orca
#
# Copyright 2009 Sun Microsystems Inc.
# Copyright 2015-2016 Igalia, S.L.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
# Boston MA  02110-1301 USA.

# pylint: disable=too-many-lines
# pylint: disable=unused-argument

"""Superclass of classes used to generate presentations for objects."""

from __future__ import annotations

import threading
import time
from dataclasses import dataclass
from difflib import SequenceMatcher
from enum import Enum
from typing import TYPE_CHECKING, Any

import gi

gi.require_version("Atspi", "2.0")
from gi.repository import Atspi

from . import braille, debug, messages, object_properties
from .ax_hypertext import AXHypertext
from .ax_object import AXObject
from .ax_text import AXText
from .ax_utilities import AXUtilities
from .ax_value import AXValue

if TYPE_CHECKING:
    from collections.abc import Callable
    from typing import ClassVar

    from .script import Script


class GeneratorMode(Enum):
    """The type of output a generator produces."""

    SPEECH = "speech"
    BRAILLE = "braille"
    SOUND = "sound"


class WhereAmI(Enum):
    """Where-am-I detail level."""

    BASIC = "basic"
    DETAILED = "detailed"


@dataclass(frozen=True)
class GeneratorContext:
    """Base settings context shared by all generators."""

    enabled: bool
    verbose: bool
    focus: Atspi.Accessible | None
    in_say_all: bool
    in_focus_mode: bool
    active_mode: str | None
    where_am_i_type: WhereAmI | None


class Generator:
    """Superclass of classes used to generate presentations for objects."""

    CACHED_DESCRIPTION: ClassVar[dict] = {}
    CACHED_IMAGE_DESCRIPTION: ClassVar[dict] = {}
    CACHED_IS_NAMELESS_TOGGLE: ClassVar[dict] = {}
    CACHED_NESTING_LEVEL: ClassVar[dict] = {}
    CACHED_STATIC_TEXT: ClassVar[dict] = {}
    CACHED_TEXT_SUBSTRING: ClassVar[dict] = {}
    CACHED_TEXT_LINE: ClassVar[dict] = {}
    CACHED_TEXT: ClassVar[dict] = {}
    CACHED_TEXT_EXPANDING_EOCS: ClassVar[dict] = {}
    CACHED_TREE_ITEM_LEVEL: ClassVar[dict] = {}
    CACHED_DESCENDANTS: ClassVar[dict] = {}
    USED_DESCRIPTION_FOR_NAME: ClassVar[dict] = {}
    USED_DESCRIPTION_FOR_STATIC_TEXT: ClassVar[dict] = {}

    _lock = threading.Lock()

    def __init__(self, script: Script, mode: GeneratorMode) -> None:
        self._mode: GeneratorMode = mode
        self._script: Script = script
        self._context: GeneratorContext = None  # type: ignore[assignment]
        self._active_progress_bars: dict[Atspi.Accessible, tuple[float, Any]] = {}
        self._generators = {
            Atspi.Role.ALERT: self._generate_alert,
            Atspi.Role.ANIMATION: self._generate_animation,
            Atspi.Role.ARTICLE: self._generate_article,
            "ROLE_ARTICLE_IN_FEED": self._generate_article_in_feed,
            Atspi.Role.BLOCK_QUOTE: self._generate_block_quote,
            Atspi.Role.BUTTON: self._generate_push_button,
            Atspi.Role.CANVAS: self._generate_canvas,
            Atspi.Role.CAPTION: self._generate_caption,
            Atspi.Role.CHECK_BOX: self._generate_check_box,
            Atspi.Role.CHECK_MENU_ITEM: self._generate_check_menu_item,
            Atspi.Role.COLOR_CHOOSER: self._generate_color_chooser,
            Atspi.Role.COLUMN_HEADER: self._generate_column_header,
            Atspi.Role.COMBO_BOX: self._generate_combo_box,
            Atspi.Role.COMMENT: self._generate_comment,
            Atspi.Role.CONTENT_DELETION: self._generate_content_deletion,
            Atspi.Role.CONTENT_INSERTION: self._generate_content_insertion,
            Atspi.Role.DEFINITION: self._generate_definition,
            Atspi.Role.DESCRIPTION_LIST: self._generate_description_list,
            Atspi.Role.DESCRIPTION_TERM: self._generate_description_term,
            Atspi.Role.DESCRIPTION_VALUE: self._generate_description_value,
            Atspi.Role.DIAL: self._generate_dial,
            Atspi.Role.DIALOG: self._generate_dialog,
            Atspi.Role.DOCUMENT_EMAIL: self._generate_document_email,
            Atspi.Role.DOCUMENT_FRAME: self._generate_document_frame,
            Atspi.Role.DOCUMENT_PRESENTATION: self._generate_document_presentation,
            Atspi.Role.DOCUMENT_SPREADSHEET: self._generate_document_spreadsheet,
            Atspi.Role.DOCUMENT_TEXT: self._generate_document_text,
            Atspi.Role.DOCUMENT_WEB: self._generate_document_web,
            "ROLE_DPUB_LANDMARK": self._generate_dpub_landmark,
            "ROLE_DPUB_SECTION": self._generate_dpub_section,
            Atspi.Role.EDITBAR: self._generate_editbar,
            Atspi.Role.EMBEDDED: self._generate_embedded,
            Atspi.Role.ENTRY: self._generate_entry,
            "ROLE_FEED": self._generate_feed,
            Atspi.Role.FOOTNOTE: self._generate_footnote,
            Atspi.Role.FOOTER: self._generate_footer,
            Atspi.Role.FORM: self._generate_form,
            Atspi.Role.FRAME: self._generate_frame,
            Atspi.Role.GROUPING: self._generate_grouping,
            Atspi.Role.HEADER: self._generate_header,
            Atspi.Role.HEADING: self._generate_heading,
            Atspi.Role.ICON: self._generate_icon,
            Atspi.Role.IMAGE: self._generate_image,
            Atspi.Role.INFO_BAR: self._generate_info_bar,
            Atspi.Role.INTERNAL_FRAME: self._generate_internal_frame,
            Atspi.Role.LABEL: self._generate_label,
            Atspi.Role.LANDMARK: self._generate_landmark,
            Atspi.Role.LAYERED_PANE: self._generate_layered_pane,
            Atspi.Role.LINK: self._generate_link,
            Atspi.Role.LEVEL_BAR: self._generate_level_bar,
            Atspi.Role.LIST: self._generate_list,
            Atspi.Role.LIST_BOX: self._generate_list_box,
            Atspi.Role.LIST_ITEM: self._generate_list_item,
            Atspi.Role.MATH: self._generate_math,
            "ROLE_MATH_ENCLOSED": self._generate_math_enclosed,
            "ROLE_MATH_FENCED": self._generate_math_fenced,
            Atspi.Role.MATH_FRACTION: self._generate_math_fraction,
            Atspi.Role.MATH_ROOT: self._generate_math_root,
            "ROLE_MATH_MULTISCRIPT": self._generate_math_multiscript,
            "ROLE_MATH_SCRIPT_SUBSUPER": self._generate_math_script_subsuper,
            "ROLE_MATH_SCRIPT_UNDEROVER": self._generate_math_script_underover,
            "ROLE_MATH_TABLE": self._generate_math_table,
            "ROLE_MATH_TABLE_ROW": self._generate_math_row,
            Atspi.Role.MARK: self._generate_mark,
            Atspi.Role.MENU: self._generate_menu,
            Atspi.Role.MENU_ITEM: self._generate_menu_item,
            Atspi.Role.NOTIFICATION: self._generate_notification,
            Atspi.Role.PAGE: self._generate_page,
            Atspi.Role.PAGE_TAB: self._generate_page_tab,
            Atspi.Role.PANEL: self._generate_panel,
            Atspi.Role.PARAGRAPH: self._generate_paragraph,
            Atspi.Role.PASSWORD_TEXT: self._generate_password_text,
            Atspi.Role.PROGRESS_BAR: self._generate_progress_bar,
            Atspi.Role.RADIO_BUTTON: self._generate_radio_button,
            Atspi.Role.RADIO_MENU_ITEM: self._generate_radio_menu_item,
            "ROLE_REGION": self._generate_region,
            Atspi.Role.ROOT_PANE: self._generate_root_pane,
            Atspi.Role.ROW_HEADER: self._generate_row_header,
            Atspi.Role.SCROLL_BAR: self._generate_scroll_bar,
            Atspi.Role.SCROLL_PANE: self._generate_scroll_pane,
            Atspi.Role.SECTION: self._generate_section,
            Atspi.Role.SLIDER: self._generate_slider,
            Atspi.Role.SPIN_BUTTON: self._generate_spin_button,
            Atspi.Role.SEPARATOR: self._generate_separator,
            Atspi.Role.SPLIT_PANE: self._generate_split_pane,
            Atspi.Role.STATIC: self._generate_static,
            Atspi.Role.STATUS_BAR: self._generate_status_bar,
            Atspi.Role.SUBSCRIPT: self._generate_subscript,
            Atspi.Role.SUGGESTION: self._generate_suggestion,
            Atspi.Role.SUPERSCRIPT: self._generate_superscript,
            Atspi.Role.SWITCH: self._generate_switch,
            Atspi.Role.TABLE: self._generate_table,
            Atspi.Role.TABLE_CELL: self._generate_table_cell_in_row,
            "REAL_ROLE_TABLE_CELL": self._generate_table_cell,
            Atspi.Role.TABLE_ROW: self._generate_table_row,
            Atspi.Role.TEAROFF_MENU_ITEM: self._generate_tearoff_menu_item,
            Atspi.Role.TERMINAL: self._generate_terminal,
            Atspi.Role.TEXT: self._generate_text,
            Atspi.Role.TOGGLE_BUTTON: self._generate_toggle_button,
            Atspi.Role.TOOL_BAR: self._generate_tool_bar,
            Atspi.Role.TOOL_TIP: self._generate_tool_tip,
            Atspi.Role.TREE: self._generate_tree,
            Atspi.Role.TREE_ITEM: self._generate_tree_item,
            Atspi.Role.WINDOW: self._generate_window,
        }

    @staticmethod
    def log_generator_output(func):
        """Decorator for logging."""

        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            tokens = [f"GENERATOR: {func.__name__}:", result]
            debug.print_tokens(debug.LEVEL_INFO, tokens, True)
            return result

        return wrapper

    @staticmethod
    def _clear_stored_data():
        """Clears any data we have cached for objects"""

        while True:
            time.sleep(2)
            msg = "GENERATOR: Clearing cache."
            debug.print_message(debug.LEVEL_INFO, msg, True)
            with Generator._lock:
                Generator.CACHED_DESCRIPTION = {}
                Generator.CACHED_IMAGE_DESCRIPTION = {}
                Generator.CACHED_IS_NAMELESS_TOGGLE = {}
                Generator.CACHED_NESTING_LEVEL = {}
                Generator.CACHED_STATIC_TEXT = {}
                Generator.CACHED_TEXT_SUBSTRING = {}
                Generator.CACHED_TEXT_LINE = {}
                Generator.CACHED_TEXT = {}
                Generator.CACHED_TEXT_EXPANDING_EOCS = {}
                Generator.CACHED_TREE_ITEM_LEVEL = {}
                Generator.CACHED_DESCENDANTS = {}
                Generator.USED_DESCRIPTION_FOR_NAME = {}
                Generator.USED_DESCRIPTION_FOR_STATIC_TEXT = {}

    @staticmethod
    def start_cache_clearing_thread():
        """Starts thread to periodically clear cached details."""

        thread = threading.Thread(target=Generator._clear_stored_data)
        thread.daemon = True
        thread.start()

    def _strings_are_redundant(self, str1, str2, threshold=0.7):
        if not (str1 and str2):
            return False

        if (str1 in str2 and len(str1.split()) > 3) or (str2 in str1 and len(str2.split()) > 3):
            msg = f"GENERATOR: Treating '{str2}' as redundant to '{str1}'"
            debug.print_message(debug.LEVEL_INFO, msg, True)
            return True

        similarity = round(SequenceMatcher(None, str1.lower(), str2.lower()).ratio(), 2)
        msg = (
            f"GENERATOR: Similarity between '{str1}', '{str2}': {similarity} "
            f"(threshold: {threshold})"
        )
        debug.print_message(debug.LEVEL_INFO, msg, True)
        return similarity >= threshold

    def generate_contents(self, contents: Any, context: GeneratorContext, **args) -> list[Any]:
        """Returns presentation for a list of [obj, start, end, string]."""

        return []

    def generate_context(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Returns the presentation of the context of the object. Subclasses must override this."""

        return []

    def generate(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Returns the presentation of the object."""

        if not args.get("role"):
            args["role"] = self._get_functional_role(obj)

        _generator = self._generators.get(  # type: ignore
            args.get("role") or AXObject.get_role(obj),
        )
        if _generator is None:
            tokens = [f"{self._mode.name} GENERATOR:", obj, "lacks dedicated generator"]
            debug.print_tokens(debug.LEVEL_INFO, tokens, True)
            _generator = self._generate_default_presentation

        if not args.get("formatType"):
            if (
                args.get("alreadyFocused", False)
                and self._context is not None
                and self._context.where_am_i_type is None
            ):
                args["formatType"] = "focused"
            else:
                args["formatType"] = "unfocused"

        tokens = [f"{self._mode.name} GENERATOR:", _generator, "for", obj, "args:", args]
        debug.print_tokens(debug.LEVEL_INFO, tokens, True)

        result = _generator(obj, **args)  # type: ignore[misc]
        tokens = [f"{self._mode.name} GENERATOR: Results:", result]
        debug.print_tokens(debug.LEVEL_INFO, tokens, True)

        if args.get("isProgressBarUpdate") and result and result[0]:
            self._set_progress_bar_update_time_and_value(obj)

        return result

    def get_localized_role_name(self, obj: Atspi.Accessible, **args) -> str:
        """Returns a string representing the localized rolename of obj."""

        result = AXUtilities.get_localized_role_name(obj, args.get("role"))
        return result

    def get_state_indicator(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Returns an array with the generated state of obj."""

        role = args.get("role", AXObject.get_role(obj))
        checks: list[tuple[Callable[..., bool], Callable[..., list[Any]]]] = [
            (
                lambda: AXUtilities.is_menu_item(obj, role),
                lambda: self._generate_state_checked_if_checkable(obj, **args),
            ),
            (
                lambda: (
                    AXUtilities.is_radio_button(obj, role)
                    or AXUtilities.is_radio_menu_item(obj, role)
                ),
                lambda: self._generate_state_selected_for_radio_button(obj, **args),
            ),
            (
                lambda: (
                    AXUtilities.is_check_box(obj, role)
                    or AXUtilities.is_check_menu_item(obj, **args)
                ),
                lambda: self._generate_state_checked(obj, **args),
            ),
            (
                lambda: AXUtilities.is_switch(obj, role),
                lambda: self._generate_state_checked_for_switch(obj, **args),
            ),
            (
                lambda: AXUtilities.is_toggle_button(obj, role),
                lambda: self._generate_state_pressed(obj, **args),
            ),
            (
                lambda: AXUtilities.is_table_cell(obj, role),
                lambda: self._generate_state_checked_for_cell(obj, **args),
            ),
        ]
        for predicate, generator in checks:
            if predicate():
                return generator()
        return []

    def get_value(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Returns an array with the generated value."""

        role = args.get("role", AXObject.get_role(obj))
        if AXUtilities.is_progress_bar(obj, role):
            return self._generate_progress_bar_value(obj, **args)

        if AXUtilities.is_scroll_bar(obj, role) or AXUtilities.is_slider(obj, role):
            return self._generate_value_as_percentage(obj, **args)

        return []

    def _generate_result_separator(self, obj: Atspi.Accessible, **args) -> list[Any]:
        return []

    ################################# BASIC DETAILS #################################

    def _prefer_description_over_name(self, obj):
        if not AXObject.get_description(obj):
            return False

        name = AXObject.get_name(obj)
        if len(name) == 1:
            if ord(name) in range(0xE000, 0xF8FF):
                tokens = ["GENERATOR: Name of", obj, "is in unicode private use area."]
                debug.print_tokens(debug.LEVEL_INFO, tokens, True)
                return True
            if AXUtilities.is_push_button(obj):
                tokens = ["GENERATOR: Preferring description over name of", obj]
                debug.print_tokens(debug.LEVEL_INFO, tokens, True)
                return True

        return False

    @log_generator_output
    def _generate_accessible_description(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if args.get("omitDescription") or AXUtilities.is_terminal(obj):
            return []

        obj_hash = hash(obj)
        if obj_hash in Generator.CACHED_DESCRIPTION:
            return Generator.CACHED_DESCRIPTION.get(obj_hash, [])

        if Generator.USED_DESCRIPTION_FOR_STATIC_TEXT.get(
            obj_hash
        ) or Generator.USED_DESCRIPTION_FOR_NAME.get(obj_hash):
            Generator.CACHED_DESCRIPTION[obj_hash] = []
            return []

        description = AXObject.get_description(obj) or AXUtilities.get_displayed_description(obj)
        if not description or self._strings_are_redundant(AXObject.get_name(obj), description):
            Generator.CACHED_DESCRIPTION[obj_hash] = []
            return []

        # TODO - JD: The table-cell check is a workaround for
        # https://bugreports.qt.io/browse/QTBUG-128558 in which Qt gives us a different
        # object each time we ask for the cell, causing obj != focus even though they are
        # functionally the same object.
        focus: Atspi.Accessible | None = self._context.focus
        if (
            focus
            and obj != focus
            and not (AXUtilities.is_table_cell(obj) and AXUtilities.is_table_cell(focus))
            and description in [AXObject.get_name(focus), AXObject.get_description(focus)]
        ):
            Generator.CACHED_DESCRIPTION[obj_hash] = []
            return []

        Generator.CACHED_DESCRIPTION[obj_hash] = [description]
        return [description]

    @log_generator_output
    def _generate_accessible_image_description(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if hash(obj) in Generator.CACHED_IMAGE_DESCRIPTION:
            return Generator.CACHED_IMAGE_DESCRIPTION.get(hash(obj), [])

        description = AXObject.get_image_description(obj)
        if not description:
            Generator.CACHED_IMAGE_DESCRIPTION[hash(obj)] = []
            return []

        Generator.CACHED_IMAGE_DESCRIPTION[hash(obj)] = [description]
        return [description]

    @log_generator_output
    def _generate_accessible_label(self, obj: Atspi.Accessible, **args) -> list[Any]:
        result = []
        label = AXUtilities.get_displayed_label(obj)
        if label:
            result.append(label)
        return result

    @log_generator_output
    def _generate_accessible_label_and_name(self, obj: Atspi.Accessible, **args) -> list[Any]:
        focus: Atspi.Accessible | None = self._context.focus

        # TODO - JD: The role check is a quick workaround for issue #535 in which we stopped
        # presenting Qt table cells because Qt keeps giving us a different object each and
        # every time we ask for the cell. https://bugreports.qt.io/browse/QTBUG-128558
        # It was fixed in Qt in 2024, but users are still using apps without the fix, like
        # TeamTalk.
        if (
            focus
            and obj != focus
            and not AXUtilities.is_dialog_or_window(obj)
            and AXObject.get_role(obj) != AXObject.get_role(focus)
        ):
            obj_name = AXObject.get_name(obj) or AXObject.get_description(obj)
            if obj_name and obj_name in [AXObject.get_name(focus), AXObject.get_description(focus)]:
                return []

        result = []
        label = self._generate_accessible_label(obj, **args)
        name = self._generate_accessible_name(obj, **args)
        role = args.get("role", AXObject.get_role(obj))
        if not (label or name) and role == Atspi.Role.TABLE_CELL:
            descendant = AXUtilities.active_descendant(obj)
            if descendant is not None:
                name = self._generate_accessible_name(descendant)

        # If we don't have a label, always use the name.
        if not label:
            return name

        result.extend(label)
        if not name:
            return result

        if name and label and self._strings_are_redundant(name[0], label[0]):
            return label if len(name[0]) < len(label[0]) else name

        result.extend(name)
        if result:
            return result

        parent = AXObject.get_parent(obj)
        if AXUtilities.is_autocomplete(parent):
            result = self._generate_accessible_label_and_name(parent, **args)

        return result

    @log_generator_output
    def _generate_accessible_name(self, obj: Atspi.Accessible, **args) -> list[Any]:
        Generator.USED_DESCRIPTION_FOR_NAME[hash(obj)] = False
        name = AXObject.get_name(obj)
        if name:
            return [name]

        description = AXObject.get_description(obj)
        if description:
            Generator.USED_DESCRIPTION_FOR_NAME[hash(obj)] = True
            return [description]

        link = None
        parent = AXObject.get_parent(obj)
        if AXUtilities.is_link(obj, args.get("role")):
            link = obj
        elif AXUtilities.is_link(parent):
            link = parent
        if link:
            basename = AXHypertext.get_link_basename(link, remove_extension=True)
            if basename:
                return [basename]

        # To make the unlabeled icons in gnome-panel more accessible.
        if AXUtilities.is_icon(obj) and AXUtilities.is_panel(parent):
            return self._generate_accessible_name(parent)

        return []

    @log_generator_output
    def _generate_accessible_placeholder_text(self, obj: Atspi.Accessible, **args) -> list[Any]:
        attrs = AXObject.get_attributes_dict(obj)
        placeholder = attrs.get("placeholder-text")
        if placeholder and placeholder != AXObject.get_name(obj):
            return [placeholder]

        placeholder = attrs.get("placeholder")
        if placeholder and placeholder != AXObject.get_name(obj):
            return [placeholder]

        return []

    @log_generator_output
    def _generate_accessible_role(self, obj: Atspi.Accessible, **args) -> list[Any]:
        return []

    @log_generator_output
    def _generate_accessible_static_text(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if hash(obj) in Generator.CACHED_STATIC_TEXT:
            return Generator.CACHED_STATIC_TEXT.get(hash(obj), [])

        result = self._generate_accessible_description(obj, **args)
        Generator.USED_DESCRIPTION_FOR_STATIC_TEXT[hash(obj)] = bool(result)
        if result:
            Generator.CACHED_STATIC_TEXT[hash(obj)] = result
            return result

        if args.get("formatType") != "ancestor":
            result = self._generate_text_expanding_embedded_objects(obj, **args)
            if result:
                Generator.CACHED_STATIC_TEXT[hash(obj)] = result
                return result

        labels = self._script.utilities.unrelated_labels(obj)
        for label in labels:
            result.extend(self._generate_accessible_name(label, **args))

        Generator.CACHED_STATIC_TEXT[hash(obj)] = result
        return result

    @log_generator_output
    def _get_functional_math_role(self, obj: Atspi.Accessible) -> str | None:
        """Returns the functional math role string, or None if not a recognized math role."""

        math_checks: list[tuple[Callable[[Atspi.Accessible], bool], str]] = [
            (AXUtilities.is_math_sub_or_super_script, "ROLE_MATH_SCRIPT_SUBSUPER"),
            (AXUtilities.is_math_under_or_over_script, "ROLE_MATH_SCRIPT_UNDEROVER"),
            (AXUtilities.is_math_multi_script, "ROLE_MATH_MULTISCRIPT"),
            (AXUtilities.is_math_enclose, "ROLE_MATH_ENCLOSED"),
            (AXUtilities.is_math_fenced, "ROLE_MATH_FENCED"),
            (AXUtilities.is_math_table, "ROLE_MATH_TABLE"),
            (AXUtilities.is_math_table_row, "ROLE_MATH_TABLE_ROW"),
        ]
        for predicate, result in math_checks:
            if predicate(obj):
                return result
        return None

    @log_generator_output
    def _get_functional_role(self, obj, **args):
        role = args.get("role", AXObject.get_role(obj))
        if AXUtilities.is_math_related(obj):
            math_role = self._get_functional_math_role(obj)
            if math_role is not None:
                return math_role
        role_checks: list[tuple[Callable[[], bool], Atspi.Role | str]] = [
            (
                lambda: AXUtilities.is_dpub(obj, role) and AXUtilities.is_landmark(obj),
                "ROLE_DPUB_LANDMARK",
            ),
            (
                lambda: AXUtilities.is_dpub(obj, role) and AXUtilities.is_section(obj, role),
                "ROLE_DPUB_SECTION",
            ),
            (lambda: AXUtilities.is_anchor(obj), Atspi.Role.STATIC),
            (lambda: AXUtilities.is_block_quote(obj, role), Atspi.Role.BLOCK_QUOTE),
            (lambda: AXUtilities.is_comment(obj, role), Atspi.Role.COMMENT),
            (lambda: AXUtilities.is_description_list(obj, role), Atspi.Role.DESCRIPTION_LIST),
            (lambda: AXUtilities.is_description_term(obj, role), Atspi.Role.DESCRIPTION_TERM),
            (lambda: AXUtilities.is_description_value(obj, role), Atspi.Role.DESCRIPTION_VALUE),
            (lambda: AXUtilities.is_feed_article(obj, role), "ROLE_ARTICLE_IN_FEED"),
            (lambda: AXUtilities.is_feed(obj, role), "ROLE_FEED"),
        ]
        for predicate, result in role_checks:
            if predicate():
                return result

        if AXUtilities.is_landmark(obj, role):
            return "ROLE_REGION" if AXUtilities.is_landmark_region(obj) else Atspi.Role.LANDMARK

        if self._script.utilities.is_document(obj) and AXObject.supports_image(obj):
            return Atspi.Role.IMAGE
        return role

    @log_generator_output
    def _generate_keyboard_mnemonic(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if mnemonic := AXUtilities.get_mnemonic(obj):
            return [mnemonic]
        return []

    @log_generator_output
    def _get_presentable_descendants(self, obj: Atspi.Accessible) -> list[Atspi.Accessible]:
        """Returns a list of presentable descendants of obj."""

        if hash(obj) in Generator.CACHED_DESCENDANTS:
            return Generator.CACHED_DESCENDANTS.get(hash(obj), [])

        descendants = AXUtilities.get_on_screen_objects(obj)
        if not descendants:
            Generator.CACHED_DESCENDANTS[hash(obj)] = []
            return []

        labelled_by = AXUtilities.get_is_labelled_by(obj)
        obj_name = AXObject.get_name(obj) or AXUtilities.get_displayed_label(obj)

        skip_roles: tuple[Callable[[Atspi.Accessible], bool], ...] = (
            AXUtilities.is_section,
            AXUtilities.is_paragraph,
            AXUtilities.is_table_related,
            AXUtilities.is_static,
            AXUtilities.is_link,
            AXUtilities.is_image,
            AXUtilities.is_separator,
        )
        presentable_descendants = []
        for child in descendants:
            if child == obj or child in labelled_by:
                continue
            if any(check(child) for check in skip_roles):
                continue

            child_name = AXObject.get_name(child)
            if self._strings_are_redundant(obj_name, child_name):
                continue

            if AXUtilities.is_label(child):
                if not AXUtilities.has_presentable_text(child) or AXUtilities.get_is_label_for(
                    child
                ):
                    continue

            presentable_descendants.append(child)

        Generator.CACHED_DESCENDANTS[hash(obj)] = presentable_descendants
        return presentable_descendants

    @log_generator_output
    def _generate_descendants(self, obj: Atspi.Accessible, **args) -> list[Any]:
        descendants = self._get_presentable_descendants(obj)
        if not descendants:
            return []

        result = []
        used_description_as_static_text = False
        obj_desc = AXObject.get_description(obj) or AXUtilities.get_displayed_description(obj)

        for child in descendants:
            if AXUtilities.is_label(child):
                if self._strings_are_redundant(obj_desc, AXObject.get_name(child)):
                    used_description_as_static_text = True

            child_result = self.generate(child, includeContext=False, omitDescription=True)
            if child_result:
                result.extend(child_result)
                result.extend(self._generate_result_separator(child, **args))

        Generator.USED_DESCRIPTION_FOR_STATIC_TEXT[hash(obj)] = used_description_as_static_text
        return result

    @log_generator_output
    def _generate_focused_item(self, obj: Atspi.Accessible, **args) -> list[Any]:
        role = args.get("role")
        if not (AXUtilities.is_list(obj, role) or AXUtilities.is_list_box(obj, role)):
            return []

        if AXObject.supports_selection(obj):
            items = AXUtilities.selected_children(obj)
        else:
            items = [AXUtilities.get_focused_object(obj)]
        if not (items and items[0]):
            return []

        result = []
        for item in map(self._generate_accessible_name, items):
            result.extend(item)

        return result

    @log_generator_output
    def _generate_radio_button_group(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if not AXUtilities.is_radio_button(obj):
            return []

        radio_group_label = None
        labels = AXUtilities.get_is_labelled_by(obj, False)
        if labels:
            radio_group_label = labels[0]
        if radio_group_label:
            name = AXObject.get_name(radio_group_label)
            if name and name != AXObject.get_name(obj):
                return [name]

        parent = AXObject.get_parent_checked(obj)
        while parent:
            if AXUtilities.is_list(parent):
                break
            if AXUtilities.is_panel(parent) or AXUtilities.is_filler(parent):
                label = self._generate_accessible_label_and_name(parent)
                if label:
                    return label
            parent = AXObject.get_parent_checked(parent)
        return []

    def _get_values_for_term(self, obj):
        if not AXUtilities.is_description_term(obj):
            return []

        values = []
        obj = AXObject.get_next_sibling(obj)
        while obj and AXUtilities.is_description_value(obj):
            values.append(obj)
            obj = AXObject.get_next_sibling(obj)

        return values

    @log_generator_output
    def _generate_term_value_count(self, obj: Atspi.Accessible, **args) -> list[Any]:
        count = len(self._get_values_for_term(obj))
        if count in (-1, 1):
            return []

        return [f"({messages.value_count_for_term(count)})"]

    ##################################### STATE #####################################

    @log_generator_output
    def _generate_state_current(self, obj: Atspi.Accessible, **args) -> list[Any]:
        result = AXUtilities.get_current_item_status_string(obj)
        if not result:
            return []
        return [f"({result})"]

    @log_generator_output
    def _generate_state_checked(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if self._mode is GeneratorMode.BRAILLE:
            indicators = object_properties.CHECK_BOX_INDICATORS_BRAILLE
        elif self._mode is GeneratorMode.SPEECH:
            indicators = object_properties.CHECK_BOX_INDICATORS_SPEECH
        elif self._mode is GeneratorMode.SOUND:
            indicators = object_properties.CHECK_BOX_INDICATORS_SOUND
        else:
            return []

        if AXUtilities.is_checked(obj):
            return [indicators[1]]
        if AXUtilities.is_indeterminate(obj):
            return [indicators[2]]
        return [indicators[0]]

    @log_generator_output
    def _generate_state_checked_for_cell(self, obj: Atspi.Accessible, **args) -> list[Any]:
        result = []
        if self._script.utilities.has_meaningful_toggle_action(obj):
            args["role"] = Atspi.Role.CHECK_BOX
            args["includeContext"] = False
            result.extend(self.generate(obj, **args))

        return result

    @log_generator_output
    def _generate_state_checked_for_switch(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if self._mode is GeneratorMode.BRAILLE:
            indicators = object_properties.SWITCH_INDICATORS_BRAILLE
        elif self._mode is GeneratorMode.SPEECH:
            indicators = object_properties.SWITCH_INDICATORS_SPEECH
        elif self._mode is GeneratorMode.SOUND:
            indicators = object_properties.SWITCH_INDICATORS_SOUND
        else:
            return []

        if AXUtilities.is_checked(obj) or AXUtilities.is_pressed(obj):
            return [indicators[1]]
        return [indicators[0]]

    @log_generator_output
    def _generate_state_checked_if_checkable(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if AXUtilities.is_checkable(obj) or AXUtilities.is_check_menu_item(obj):
            return self._generate_state_checked(obj, **args)

        if AXUtilities.is_checked(obj):
            return self._generate_state_checked(obj, **args)

        return []

    @log_generator_output
    def _generate_state_expanded(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if self._mode is GeneratorMode.BRAILLE:
            indicators = object_properties.EXPANSION_INDICATORS_BRAILLE
        elif self._mode is GeneratorMode.SPEECH:
            indicators = object_properties.EXPANSION_INDICATORS_SPEECH
        elif self._mode is GeneratorMode.SOUND:
            indicators = object_properties.EXPANSION_INDICATORS_SOUND
        else:
            return []

        if AXUtilities.is_collapsed(obj):
            return [indicators[0]]
        if AXUtilities.is_expanded(obj):
            return [indicators[1]]
        if AXUtilities.is_expandable(obj):
            return [indicators[0]]
        return []

    @log_generator_output
    def _generate_state_has_popup(self, obj: Atspi.Accessible, **args) -> list[Any]:
        return []

    @log_generator_output
    def _generate_state_invalid(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if not AXUtilities.is_invalid_entry(obj):
            return []

        attrs, _start, _end = AXText.get_text_attributes_at_offset(obj)
        error = attrs.get("invalid")
        if error == "false":
            return []

        if self._mode is GeneratorMode.BRAILLE:
            indicators = object_properties.INVALID_INDICATORS_BRAILLE
        elif self._mode is GeneratorMode.SPEECH:
            indicators = object_properties.INVALID_INDICATORS_SPEECH
        elif self._mode is GeneratorMode.SOUND:
            indicators = object_properties.INVALID_INDICATORS_SOUND
        else:
            return []

        result = []
        if error == "spelling":
            indicator = indicators[1]
        elif error == "grammar":
            indicator = indicators[2]
        else:
            indicator = indicators[0]

        targets = AXUtilities.get_error_message(obj) or ""
        error_message = "\n".join(map(self._script.utilities.expand_eocs, targets or []))
        if error_message:
            result.append(f"{indicator}: {error_message}")
        else:
            result.append(indicator)

        return result

    @log_generator_output
    def _generate_state_multiselectable(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if not (AXUtilities.is_multiselectable(obj) and AXObject.get_child_count(obj)):
            return []

        # TODO - JD: There is no braille property and the braille generation
        # doesn't generate this state. Shouldn't it be presented in braille?

        if self._mode is GeneratorMode.SPEECH:
            return [object_properties.STATE_MULTISELECT_SPEECH]
        if self._mode is GeneratorMode.SOUND:
            return [object_properties.STATE_MULTISELECT_SOUND]
        return []

    @log_generator_output
    def _generate_state_pressed(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if self._mode is GeneratorMode.BRAILLE:
            indicators = object_properties.TOGGLE_BUTTON_INDICATORS_BRAILLE
        elif self._mode is GeneratorMode.SPEECH:
            indicators = object_properties.TOGGLE_BUTTON_INDICATORS_SPEECH
        elif self._mode is GeneratorMode.SOUND:
            indicators = object_properties.TOGGLE_BUTTON_INDICATORS_SOUND
        else:
            return []

        if AXUtilities.is_checked(obj) or AXUtilities.is_pressed(obj):
            return [indicators[1]]
        return [indicators[0]]

    @log_generator_output
    def _generate_state_read_only(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if not AXUtilities.is_read_only(obj):
            return []

        if self._mode is GeneratorMode.BRAILLE:
            return [object_properties.STATE_READ_ONLY_BRAILLE]
        if self._mode is GeneratorMode.SPEECH:
            return [object_properties.STATE_READ_ONLY_SPEECH]
        if self._mode is GeneratorMode.SOUND:
            return [object_properties.STATE_READ_ONLY_SOUND]

        return []

    @log_generator_output
    def _generate_state_required(self, obj: Atspi.Accessible, **args) -> list[Any]:
        is_required = AXUtilities.is_required(obj)
        if not is_required and AXUtilities.is_radio_button(obj):
            is_required = AXUtilities.is_required(AXObject.get_parent(obj))
        if not is_required:
            return []

        if self._mode is GeneratorMode.BRAILLE:
            return [object_properties.STATE_REQUIRED_BRAILLE]
        if self._mode is GeneratorMode.SPEECH:
            return [object_properties.STATE_REQUIRED_SPEECH]
        if self._mode is GeneratorMode.SOUND:
            return [object_properties.STATE_REQUIRED_SOUND]

        return []

    @log_generator_output
    def _generate_state_selected_for_radio_button(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if self._mode is GeneratorMode.BRAILLE:
            indicators = object_properties.RADIO_BUTTON_INDICATORS_BRAILLE
        elif self._mode is GeneratorMode.SPEECH:
            indicators = object_properties.RADIO_BUTTON_INDICATORS_SPEECH
        elif self._mode is GeneratorMode.SOUND:
            indicators = object_properties.RADIO_BUTTON_INDICATORS_SOUND
        else:
            return []

        if AXUtilities.is_checked(obj):
            return [indicators[1]]
        return [indicators[0]]

    @log_generator_output
    def _generate_state_sensitive(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if AXUtilities.is_sensitive(obj):
            return []

        if AXUtilities.is_editable(obj):
            return []

        if self._mode is GeneratorMode.BRAILLE:
            return [object_properties.STATE_INSENSITIVE_BRAILLE]
        if self._mode is GeneratorMode.SPEECH:
            return [object_properties.STATE_INSENSITIVE_SPEECH]
        if self._mode is GeneratorMode.SOUND:
            return [object_properties.STATE_INSENSITIVE_SOUND]

        return []

    @log_generator_output
    def _generate_state_unselected(self, obj: Atspi.Accessible, **args) -> list[Any]:
        return []

    @log_generator_output
    def _generate_state_visited(self, obj: Atspi.Accessible, **args) -> list[Any]:
        # Note that in the case of speech, this state is added to the role name.
        return []

    ##################################### TEXT ######################################

    @log_generator_output
    def _generate_text_substring(self, obj: Atspi.Accessible, **args) -> list[Any]:
        start = args.get("startOffset")
        end = args.get("endOffset")
        if (hash(obj), start, end) in Generator.CACHED_TEXT_SUBSTRING:
            return Generator.CACHED_TEXT_SUBSTRING.get((hash(obj), start, end), [])

        if start is None or end is None:
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT_SUBSTRING[(hash(obj), start, end)] = []
            return []

        substring = args.get("string", AXText.get_substring(obj, start, end))
        if "\ufffc" not in substring:
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT_SUBSTRING[(hash(obj), start, end)] = [substring]
            return [substring]

        if not AXUtilities.is_editable(obj):
            Generator.CACHED_TEXT_SUBSTRING[(hash(obj), start, end)] = []
        return []

    @log_generator_output
    def _generate_text_line(self, obj: Atspi.Accessible, **args) -> list[Any]:
        start = args.get("startOffset")
        end = args.get("endOffset")
        if (hash(obj), start, end) in Generator.CACHED_TEXT_LINE:
            return Generator.CACHED_TEXT_LINE.get((hash(obj), start, end), [])

        result = Generator._generate_text_substring(self, obj, **args)
        if result:
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT_LINE[(hash(obj), start, end)] = result
            return result

        text = AXText.get_line_at_offset(obj)[0]
        if text and "\ufffc" not in text:
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT_LINE[(hash(obj), start, end)] = [text]
            return [text]

        if not AXUtilities.is_editable(obj):
            Generator.CACHED_TEXT_LINE[(hash(obj), start, end)] = []
        return []

    @log_generator_output
    def _generate_text_content(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if hash(obj) in Generator.CACHED_TEXT:
            return Generator.CACHED_TEXT.get(hash(obj), [])

        result = Generator._generate_text_substring(self, obj, **args)
        if result:
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT[hash(obj)] = result
            return result

        text = AXText.get_all_text(obj)
        if not text and AXUtilities.is_table_cell_or_header(obj) and AXUtilities.is_editable(obj):
            text = " ".join(AXText.get_all_text(child) for child in AXObject.iter_children(obj))

        if text and "\ufffc" not in text:
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT[hash(obj)] = [text]
            return [text]

        if not AXUtilities.is_editable(obj):
            Generator.CACHED_TEXT[hash(obj)] = []
        return []

    @log_generator_output
    def _generate_text_expanding_embedded_objects(self, obj: Atspi.Accessible, **args) -> list[Any]:
        start = args.get("startOffset")
        end = args.get("endOffset")
        if (hash(obj), start, end) in Generator.CACHED_TEXT_EXPANDING_EOCS:
            return Generator.CACHED_TEXT_EXPANDING_EOCS.get((hash(obj), start, end), [])

        text = self._script.utilities.expand_eocs(
            obj,
            args.get("startOffset", 0),
            args.get("endOffset", -1),
        )
        if (
            text.strip()
            and "\ufffc" not in text
            and not self._strings_are_redundant(AXObject.get_name(obj), text)
        ):
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT_EXPANDING_EOCS[hash(obj), start, end] = [text]
            return [text]

        if not AXUtilities.is_editable(obj):
            Generator.CACHED_TEXT_EXPANDING_EOCS[(hash(obj), start, end)] = []
        return []

    ################################## POSITION #####################################

    @log_generator_output
    def _get_nesting_level(self, obj):
        level = Generator.CACHED_NESTING_LEVEL.get(hash(obj))
        if level is None:
            level = AXUtilities.get_nesting_level(obj)
            Generator.CACHED_NESTING_LEVEL[hash(obj)] = level
        return level

    @log_generator_output
    def _generate_nesting_level(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if args.get("startOffset") is not None and args.get("endOffset") is not None:
            return []

        level = self._get_nesting_level(obj)
        if not level:
            return []

        if self._mode is GeneratorMode.BRAILLE:
            return [object_properties.NESTING_LEVEL_BRAILLE % (level)]
        if self._mode is GeneratorMode.SPEECH:
            return [object_properties.NESTING_LEVEL_SPEECH % (level)]
        return []

    @log_generator_output
    def _generate_position_in_list(self, obj: Atspi.Accessible, **args) -> list[Any]:
        return []

    @log_generator_output
    def _generate_tree_item_level(self, obj: Atspi.Accessible, **args) -> list[Any]:
        level = Generator.CACHED_TREE_ITEM_LEVEL.get(hash(obj))
        if level is None:
            level = self._script.utilities.node_level(obj)
            Generator.CACHED_TREE_ITEM_LEVEL[hash(obj)] = level

        if level < 0:
            return []

        prior_object = args.get("priorObj")
        if args.get("newOnly") and prior_object:
            old_level = Generator.CACHED_TREE_ITEM_LEVEL.get(hash(prior_object))
            if old_level is None:
                old_level = self._script.utilities.node_level(prior_object)
                Generator.CACHED_TREE_ITEM_LEVEL[hash(prior_object)] = old_level
            if old_level == level:
                return []

        if self._mode is GeneratorMode.BRAILLE:
            return [object_properties.NODE_LEVEL_BRAILLE % (level + 1)]
        if self._mode is GeneratorMode.SPEECH:
            return [object_properties.NODE_LEVEL_SPEECH % (level + 1)]
        return []

    ################################ PROGRESS BARS ##################################

    @log_generator_output
    def _generate_progress_bar_index(self, obj: Atspi.Accessible, **args) -> list[Any]:
        return []

    @log_generator_output
    def _generate_progress_bar_value(self, obj: Atspi.Accessible, **args) -> list[Any]:
        return []

    def _clean_up_cached_progress_bars(self):
        bars = list(filter(AXObject.is_valid, self._active_progress_bars))
        self._active_progress_bars = {x: self._active_progress_bars.get(x) for x in bars}

    def _get_most_recent_progress_bar_update(self):
        self._clean_up_cached_progress_bars()
        if not self._active_progress_bars.values():
            return None, 0.0, None

        sorted_values = sorted(self._active_progress_bars.values(), key=lambda x: x[0])
        prev_time, prev_value = sorted_values[-1]
        return list(self._active_progress_bars.keys())[-1], prev_time, prev_value

    def _get_progress_bar_number_and_count(self, obj):
        self._clean_up_cached_progress_bars()
        if obj not in self._active_progress_bars:
            self._active_progress_bars[obj] = 0.0, None

        this_value = self._get_progress_bar_update_time_and_value(obj)
        index = list(self._active_progress_bars.values()).index(this_value)
        return index + 1, len(self._active_progress_bars)

    def _get_progress_bar_update_time_and_value(self, obj, **args):
        if obj not in self._active_progress_bars:
            self._active_progress_bars[obj] = 0.0, None

        return self._active_progress_bars.get(obj)

    def _set_progress_bar_update_time_and_value(self, obj, last_time=None, last_value=None):
        last_time = last_time or time.time()
        last_value = last_value or AXValue.get_value_as_percent(obj)
        self._active_progress_bars[obj] = last_time, last_value

    ##################################### TABLE #####################################

    # TODO - JD: This function and fake role really need to die....
    @log_generator_output
    def _generate_real_table_cell(self, obj: Atspi.Accessible, **args) -> list[Any]:
        result = []
        args["role"] = "REAL_ROLE_TABLE_CELL"
        result.extend(self.generate(obj, **args))
        return result

    def _get_is_nameless_toggle(self, obj):
        if hash(obj) in Generator.CACHED_IS_NAMELESS_TOGGLE:
            return Generator.CACHED_IS_NAMELESS_TOGGLE[hash(obj)]

        if not self._script.utilities.has_meaningful_toggle_action(obj):
            Generator.CACHED_IS_NAMELESS_TOGGLE[hash(obj)] = False
            return False

        descendant = AXUtilities.active_descendant(obj)
        if AXObject.get_name(descendant) or AXText.get_all_text(descendant):
            Generator.CACHED_IS_NAMELESS_TOGGLE[hash(obj)] = False
            return False

        Generator.CACHED_IS_NAMELESS_TOGGLE[hash(obj)] = True
        return True

    # TODO - JD: This is part of the complicated "REAL_ROLE_TABLE_CELL" mess.
    @log_generator_output
    def _generate_table_cell_row(self, obj: Atspi.Accessible, **args) -> list[Any]:
        present_all = (
            args.get("readingRow") is True
            or self._context.where_am_i_type == WhereAmI.DETAILED
            or self._script.utilities.should_read_full_row(obj, args.get("priorObj"))
        )

        if not present_all:
            return self._generate_real_table_cell(obj, **args)

        row = AXUtilities.find_ancestor(obj, AXUtilities.is_table_row)
        if row and AXObject.get_name(row) and not AXUtilities.is_layout_only(row):
            return self.generate(row)

        args["readingRow"] = True
        result: list[Any] = []
        cells = AXUtilities.get_showing_cells_in_same_row(
            obj,
            clip_to_window=AXUtilities.is_spreadsheet_cell(obj),
        )

        # Remove any pre-calculated values which only apply to obj and not row cells.
        do_not_include = ["startOffset", "endOffset", "string"]
        other_cell_args = args.copy()
        for arg in do_not_include:
            other_cell_args.pop(arg, None)

        for cell in cells:
            if cell == obj:
                cell_result = self._generate_real_table_cell(cell, **args)
            else:
                cell_result = self._generate_real_table_cell(cell, **other_cell_args)
            if cell_result and result and self._mode is GeneratorMode.BRAILLE:
                result.append(braille.Region(object_properties.TABLE_CELL_DELIMITER_BRAILLE))
            result.extend(cell_result)
            args["priorObj"] = cell
            other_cell_args["priorObj"] = cell

        result.extend(self._generate_position_in_list(obj, **args))
        return result

    # TODO - JD: If we had dedicated generators for cell types, we wouldn't need this.
    @log_generator_output
    def _generate_column_header_if_toggle_and_no_text(
        self,
        obj: Atspi.Accessible,
        **args,
    ) -> list[Any]:
        if not self._get_is_nameless_toggle(obj):
            return []

        result = []
        headers = AXUtilities.get_column_headers(obj)
        if headers:
            result.append(AXObject.get_name(headers[0]) or AXText.get_all_text(headers[0]))

        return result

    # TODO - JD: This needs to also be looked into.
    @log_generator_output
    def _generate_real_active_descendant_displayed_text(
        self,
        obj: Atspi.Accessible,
        **args,
    ) -> list[Any]:
        rad = AXUtilities.active_descendant(obj)

        if not (AXUtilities.is_table_cell(rad) and AXObject.get_child_count(rad)):
            return self._generate_text_content(rad, **args)

        content = {AXObject.get_name(x) for x in AXObject.iter_children(rad)}
        rv = " ".join(filter(lambda x: x, content))
        if not rv:
            return self._generate_text_content(rad, **args)
        return [rv]

    @log_generator_output
    def _generate_table_cell_column_header(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if args.get("readingRow") and not self._get_is_nameless_toggle(obj):
            return []

        result: list[Any] = []
        if args.get("newOnly"):
            headers = AXUtilities.get_new_column_headers(obj, args.get("priorObj"))
        else:
            headers = AXUtilities.get_column_headers(obj)

        tokens = []
        for header in headers:
            name = self._generate_accessible_name(header)
            if name and name[0].strip():
                tokens.append(name[0])
            else:
                text = self._generate_text_content(header)
                if text and text[0].strip():
                    tokens.append(text[0])

        if not tokens:
            return result

        text = ". ".join(tokens)
        if not self._get_is_nameless_toggle(obj):
            role_string = self.get_localized_role_name(obj, role=Atspi.Role.COLUMN_HEADER)
            if self._mode is GeneratorMode.SPEECH:
                if self._context.verbose and self._context.where_am_i_type is None:
                    text = f"{text} {role_string}"
            elif self._mode is GeneratorMode.BRAILLE and self._context.verbose:
                text = f"{text} {role_string}"

        result.append(text)
        return result

    @log_generator_output
    def _generate_table_cell_row_header(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if args.get("readingRow"):
            return []

        result: list[Any] = []
        if args.get("newOnly"):
            headers = AXUtilities.get_new_row_headers(obj, args.get("priorObj"))
        else:
            headers = AXUtilities.get_row_headers(obj)

        tokens = []
        for header in headers:
            name = self._generate_accessible_name(header)
            if name and name[0].strip():
                tokens.append(name[0])
            else:
                text = self._generate_text_content(header)
                if text and text[0].strip():
                    tokens.append(text[0])

        if not tokens:
            return result

        text = ". ".join(tokens)
        role_string = self.get_localized_role_name(obj, role=Atspi.Role.ROW_HEADER)
        if self._mode is GeneratorMode.SPEECH:
            if self._context.verbose and self._context.where_am_i_type is None:
                text = f"{text} {role_string}"
        elif self._mode is GeneratorMode.BRAILLE and self._context.verbose:
            text = f"{text} {role_string}"

        result.append(text)
        return result

    @log_generator_output
    def _generate_table_sort_order(self, obj: Atspi.Accessible, **args) -> list[Any]:
        description = AXUtilities.get_presentable_sort_order_from_header(obj)
        if not description:
            return []

        return [description]

    ##################################### VALUE #####################################

    def _get_combo_box_value(self, obj):
        attrs = AXObject.get_attributes_dict(obj, False)
        if "valuetext" in attrs:
            return attrs.get("valuetext")

        if not AXObject.get_child_count(obj):
            return AXObject.get_name(obj) or AXText.get_all_text(obj)

        children = list(AXObject.iter_children(obj, AXUtilities.is_text_input))
        if len(children) == 1:
            return AXText.get_all_text(children[0])

        selected = AXUtilities.selected_children(obj)
        selected = selected or AXUtilities.selected_children(AXObject.get_child(obj, 0))
        if len(selected) == 1:
            return AXObject.get_name(selected[0]) or AXText.get_all_text(selected[0])

        return AXObject.get_name(obj) or AXText.get_all_text(obj)

    @log_generator_output
    def _generate_value(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if AXUtilities.is_combo_box(obj, args.get("role")):
            if value := self._get_combo_box_value(obj):
                return [value]
            return []

        if AXUtilities.is_separator(obj, args.get("role")) and not AXUtilities.is_focused(obj):
            return []

        result = AXValue.get_current_value_text(obj)
        if result:
            return [result]
        return []

    @log_generator_output
    def _generate_value_as_percentage(self, obj: Atspi.Accessible, **args) -> list[Any]:
        percent = AXValue.get_value_as_percent(obj)
        if percent is not None:
            return [f"{percent}%"]

        return []

    ################################### PER-ROLE ###################################

    def _generate_default_presentation(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Provides a default/role-agnostic presentation of obj."""

        return []

    def _generate_accelerator_label(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the accelerator-label role."""

        return []

    def _generate_alert(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the alert role."""

        return []

    def _generate_animation(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the animation role."""

        return []

    def _generate_application(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the application role."""

        return []

    def _generate_arrow(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the arrow role."""

        return []

    def _generate_article(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the article role."""

        return []

    def _generate_article_in_feed(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the article role when the article is in a feed."""

        return []

    def _generate_audio(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the audio role."""

        return []

    def _generate_autocomplete(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the autocomplete role."""

        return []

    def _generate_block_quote(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the block-quote role."""

        return []

    def _generate_calendar(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the calendar role."""

        return []

    def _generate_canvas(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the canvas role."""

        return []

    def _generate_caption(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the caption role."""

        return []

    def _generate_chart(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the chart role."""

        return []

    def _generate_check_box(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the check-box role."""

        return []

    def _generate_check_menu_item(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the check-menu-item role."""

        return []

    def _generate_color_chooser(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the color-chooser role."""

        return []

    def _generate_column_header(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the column-header role."""

        return []

    def _generate_combo_box(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the combo-box role."""

        return []

    def _generate_comment(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the comment role."""

        return []

    def _generate_content_deletion(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the content-deletion role."""

        return []

    def _generate_content_insertion(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the content-insertion role."""

        return []

    def _generate_date_editor(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the date-editor role."""

        return []

    def _generate_definition(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the definition role."""

        return []

    def _generate_description_list(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the description-list role."""

        return []

    def _generate_description_term(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the description-term role."""

        return []

    def _generate_description_value(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the description-value role."""

        return []

    def _generate_desktop_frame(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the desktop-frame role."""

        return []

    def _generate_desktop_icon(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the desktop-icon role."""

        return []

    def _generate_dial(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the dial role."""

        return []

    def _generate_dialog(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the dialog role."""

        return []

    def _generate_directory_pane(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the directory_pane role."""

        return []

    def _generate_document(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for document-related roles."""

        return []

    def _generate_document_email(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the document-email role."""

        return []

    def _generate_document_frame(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the document-frame role."""

        return []

    def _generate_document_presentation(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the document-presentation role."""

        return []

    def _generate_document_spreadsheet(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the document-spreadsheet role."""

        return []

    def _generate_document_text(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the document-text role."""

        return []

    def _generate_document_web(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the document-web role."""

        return []

    def _generate_dpub_landmark(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the dpub section role."""

        return []

    def _generate_dpub_section(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the dpub section role."""

        return []

    def _generate_drawing_area(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the drawing-area role."""

        return []

    def _generate_editbar(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the editbar role."""

        return []

    def _generate_embedded(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the embedded role."""

        return []

    def _generate_entry(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the entry role."""

        return []

    def _generate_feed(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the feed role."""

        return []

    def _generate_file_chooser(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the file-chooser role."""

        return []

    def _generate_filler(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the filler role."""

        return []

    def _generate_font_chooser(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the font-chooser role."""

        return []

    def _generate_footer(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the footer role."""

        return []

    def _generate_footnote(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the footnote role."""

        return []

    def _generate_form(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the form role."""

        return []

    def _generate_frame(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the frame role."""

        return []

    def _generate_glass_pane(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the glass-pane role."""

        return []

    def _generate_grouping(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the grouping role."""

        return []

    def _generate_header(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the header role."""

        return []

    def _generate_heading(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the heading role."""

        return []

    def _generate_html_container(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the html-container role."""

        return []

    def _generate_icon(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the icon role."""

        return []

    def _generate_image(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the image role."""

        return []

    def _generate_image_map(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the image-map role."""

        return []

    def _generate_info_bar(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the info-bar role."""

        return []

    def _generate_input_method_window(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the input-method-window role."""

        return []

    def _generate_internal_frame(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the internal-frame role."""

        return []

    def _generate_label(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the label role."""

        return []

    def _generate_landmark(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the landmark role."""

        return []

    def _generate_layered_pane(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the layered-pane role."""

        return []

    def _generate_level_bar(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the level-bar role."""

        return []

    def _generate_link(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the link role."""

        return []

    def _generate_list(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the list role."""

        return []

    def _generate_list_box(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the list-box role."""

        return []

    def _generate_list_item(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the list-item role."""

        return []

    def _generate_log(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the log role."""

        return []

    def _generate_mark(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the mark role."""

        return []

    def _generate_marquee(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the marquee role."""

        return []

    def _generate_math(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the math role."""

        return []

    def _generate_math_enclosed(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the math-enclosed role."""

        return []

    def _generate_math_fenced(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the math-fenced role."""

        return []

    def _generate_math_fraction(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the math-fraction role."""

        return []

    def _generate_math_multiscript(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the math-multiscript role."""

        return []

    def _generate_math_root(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the math-root role."""

        return []

    def _generate_math_row(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the math-row role."""

        return []

    def _generate_math_script_subsuper(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the math script subsuper role."""

        return []

    def _generate_math_script_underover(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the math script underover role."""

        return []

    def _generate_math_table(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the math-table role."""

        return []

    def _generate_menu(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the menu role."""

        return []

    def _generate_menu_bar(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the menu-bar role."""

        return []

    def _generate_menu_item(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the menu-item role."""

        return []

    def _generate_notification(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the notification role."""

        return []

    def _generate_option_pane(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the option-pane role."""

        return []

    def _generate_page(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the page role."""

        return []

    def _generate_page_tab(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the page-tab role."""

        return []

    def _generate_page_tab_list(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the page-tab-list role."""

        return []

    def _generate_panel(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the panel role."""

        return []

    def _generate_paragraph(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the paragraph role."""

        return []

    def _generate_password_text(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the password-text role."""

        return []

    def _generate_popup_menu(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the popup-menu role."""

        return []

    def _generate_progress_bar(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the progress-bar role."""

        return []

    def _generate_push_button(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the push-button role."""

        return []

    def _generate_push_button_menu(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the push-button-menu role."""

        return []

    def _generate_radio_button(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the radio-button role."""

        return []

    def _generate_radio_menu_item(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the radio-menu-item role."""

        return []

    def _generate_rating(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the rating role."""

        return []

    def _generate_region(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the region landmark role."""

        return []

    def _generate_root_pane(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the root-pane role."""

        return []

    def _generate_row_header(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the row-header role."""

        return []

    def _generate_ruler(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the ruler role."""

        return []

    def _generate_scroll_bar(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the scroll-bar role."""

        return []

    def _generate_scroll_pane(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the scroll-pane role."""

        return []

    def _generate_section(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the section role."""

        return []

    def _generate_separator(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the separator role."""

        return []

    def _generate_slider(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the slider role."""

        return []

    def _generate_spin_button(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the spin-button role."""

        return []

    def _generate_split_pane(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the split-pane role."""

        return []

    def _generate_static(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the static role."""

        return []

    def _generate_status_bar(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the status-bar role."""

        return []

    def _generate_subscript(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the subscript role."""

        return []

    def _generate_suggestion(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the suggestion role."""

        return []

    def _generate_superscript(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the superscript role."""

        return []

    def _generate_switch(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the switch role."""

        return []

    def _generate_table(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the table role."""

        return []

    def _generate_table_cell(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the table-cell role."""

        return []

    def _generate_table_cell_in_row(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the table-cell role in the context of its row."""

        return []

    def _generate_table_column_header(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the table-column-header role."""

        return []

    def _generate_table_row(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the table-row role."""

        return []

    def _generate_table_row_header(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the table-row-header role."""

        return []

    def _generate_tearoff_menu_item(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the tearoff-menu-item role."""

        return []

    def _generate_terminal(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the terminal role."""

        return []

    def _generate_text(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the text role."""

        return []

    def _generate_timer(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the timer role."""

        return []

    def _generate_title_bar(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the title-bar role."""

        return []

    def _generate_toggle_button(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the toggle-button role."""

        return []

    def _generate_tool_bar(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the tool-bar role."""

        return []

    def _generate_tool_tip(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the tool-tip role."""

        return []

    def _generate_tree(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the tree role."""

        return []

    def _generate_tree_item(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the tree-item role."""

        return []

    def _generate_tree_table(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the tree-table role."""

        return []

    def _generate_unknown(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the unknown role."""

        return []

    def _generate_video(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the video role."""

        return []

    def _generate_viewport(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the viewport role."""

        return []

    def _generate_window(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Generates presentation for the window role."""

        return []


Generator.start_cache_clearing_thread()
