// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

// cspell:ignore descendents qobject qwidget

use crate::accessible_generated::*;
use crate::qt_window::QtWindow;

use i_slint_core::SharedVector;
use i_slint_core::accessibility::{
    AccessibilityAction, AccessibleStringProperty, SupportedAccessibilityAction,
};
use i_slint_core::item_tree::{ItemRc, ItemWeak};
use i_slint_core::properties::{PropertyDirtyHandler, PropertyTracker};
use i_slint_core::window::WindowInner;

use cpp::*;
use pin_project::pin_project;
use qttypes::QString;

use core::ffi::c_void;
use std::pin::Pin;

// KEEP IN SYNC WITH CONSTANTS IN C++
const NAME: u32 = QAccessible_Text_Name;
const DESCRIPTION: u32 = QAccessible_Text_Description;
const VALUE: u32 = QAccessible_Text_Value;
const CHECKED: u32 = QAccessible_Text_UserText;
const VALUE_MINIMUM: u32 = CHECKED + 1;
const VALUE_MAXIMUM: u32 = VALUE_MINIMUM + 1;
const VALUE_STEP: u32 = VALUE_MAXIMUM + 1;
const CHECKABLE: u32 = VALUE_STEP + 1;
const EXPANDABLE: u32 = CHECKABLE + 1;
const EXPANDED: u32 = EXPANDABLE + 1;
const READ_ONLY: u32 = EXPANDED + 1;

pub struct AccessibleItemPropertiesTracker {
    obj: *mut c_void,
}

impl PropertyDirtyHandler for AccessibleItemPropertiesTracker {
    fn notify(self: Pin<&Self>) {
        let obj = self.obj;
        cpp!(unsafe [obj as "QObject*"] {
            QTimer::singleShot(0, [obj = QPointer(obj)]() {
                if (!obj)
                    return;

                auto accessible_item = static_cast<Slint_accessible_item*>(QAccessible::queryAccessibleInterface(obj));
                auto data = accessible_item->data();
                rust!(AccessibleItemPropertiesTracker_rearm [data: Pin<&SlintAccessibleItemData> as "void*"] {
                    data.arm_state_tracker();
                });

                QAccessible::State s = {};
                s.checked = true; // Mark checked as changed!
                auto event = QAccessibleStateChangeEvent(obj, s);
                QAccessible::updateAccessibility(&event);
            });
        });
    }
}

pub struct ValuePropertyTracker {
    obj: *mut c_void,
}

impl PropertyDirtyHandler for ValuePropertyTracker {
    fn notify(self: Pin<&Self>) {
        let obj = self.obj;
        cpp!(unsafe [obj as "QObject*"] {
            QTimer::singleShot(0, [ obj = QPointer(obj)]() {
                if (!obj)
                    return;

                auto accessible_item = static_cast<Slint_accessible_item*>(QAccessible::queryAccessibleInterface(obj));
                auto data = accessible_item->data();
                rust!(ValuePropertyTracker_rearm [data: Pin<&SlintAccessibleItemData> as "void*"] {
                    data.arm_value_tracker();
                });

                auto event = QAccessibleValueChangeEvent(obj, accessible_item->currentValue());
                QAccessible::updateAccessibility(&event);
            });
        });
    }
}

pub struct LabelPropertyTracker {
    obj: *mut c_void,
}

impl PropertyDirtyHandler for LabelPropertyTracker {
    fn notify(self: Pin<&Self>) {
        let obj = self.obj;
        cpp!(unsafe [obj as "QObject*"] {
            QTimer::singleShot(0, [obj = QPointer(obj)]() {
                if (!obj)
                    return;

                auto accessible_item = static_cast<Slint_accessible_item*>(QAccessible::queryAccessibleInterface(obj));
                auto data = accessible_item->data();
                rust!(LabelPropertyTracker_rearm [data: Pin<&SlintAccessibleItemData> as "void*"] {
                    data.arm_label_tracker();
                });

                auto event = QAccessibleEvent(obj, QAccessible::NameChanged);
                QAccessible::updateAccessibility(&event);
            });
        });
    }
}

pub struct DescriptionPropertyTracker {
    obj: *mut c_void,
}

impl PropertyDirtyHandler for DescriptionPropertyTracker {
    fn notify(self: Pin<&Self>) {
        let obj = self.obj;
        cpp!(unsafe [obj as "QObject*"] {
            QTimer::singleShot(0, [obj = QPointer(obj)]() {
                if (!obj)
                    return;

                auto accessible_item = static_cast<Slint_accessible_item*>(QAccessible::queryAccessibleInterface(obj));
                auto data = accessible_item->data();
                rust!(DescriptionPropertyTracker_rearm [data: Pin<&SlintAccessibleItemData> as "void*"] {
                    data.arm_description_tracker();
                });

                auto event = QAccessibleEvent(obj, QAccessible::DescriptionChanged);
                QAccessible::updateAccessibility(&event);
            });
        });
    }
}

pub struct FocusDelegationPropertyTracker {
    obj: *mut c_void,
}

impl PropertyDirtyHandler for FocusDelegationPropertyTracker {
    fn notify(self: Pin<&Self>) {
        let obj = self.obj;
        cpp!(unsafe [obj as "QObject*"] {
            QTimer::singleShot(0, [obj = QPointer(obj)]() {
                if (!obj)
                    return;

                auto accessible_item = static_cast<Slint_accessible_item*>(QAccessible::queryAccessibleInterface(obj));
                auto data = accessible_item->data();
                rust!(FocusDelegationPropertyTracker_rearm [data: Pin<&SlintAccessibleItemData> as "void*"] {
                    data.arm_focus_delegation_tracker();
                });

                accessible_item->delegateFocus();
            });
        });
    }
}

#[pin_project]
pub struct SlintAccessibleItemData {
    #[pin]
    state_tracker: PropertyTracker<false, AccessibleItemPropertiesTracker>,
    #[pin]
    value_tracker: PropertyTracker<false, ValuePropertyTracker>,
    #[pin]
    label_tracker: PropertyTracker<false, LabelPropertyTracker>,
    #[pin]
    description_tracker: PropertyTracker<false, DescriptionPropertyTracker>,
    #[pin]
    focus_delegation_tracker: PropertyTracker<false, FocusDelegationPropertyTracker>,
    item: ItemWeak,
}

impl SlintAccessibleItemData {
    fn new_pin_box(obj: *mut c_void, item: &ItemWeak) -> Pin<Box<Self>> {
        let state_tracker =
            PropertyTracker::new_with_dirty_handler(AccessibleItemPropertiesTracker { obj });
        let value_tracker = PropertyTracker::new_with_dirty_handler(ValuePropertyTracker { obj });
        let label_tracker = PropertyTracker::new_with_dirty_handler(LabelPropertyTracker { obj });
        let description_tracker =
            PropertyTracker::new_with_dirty_handler(DescriptionPropertyTracker { obj });
        let focus_delegation_tracker =
            PropertyTracker::new_with_dirty_handler(FocusDelegationPropertyTracker { obj });

        let result = Box::pin(Self {
            state_tracker,
            value_tracker,
            label_tracker,
            description_tracker,
            focus_delegation_tracker,
            item: item.clone(),
        });

        result.as_ref().arm_state_tracker();
        result.as_ref().arm_value_tracker();
        result.as_ref().arm_label_tracker();
        result.as_ref().arm_description_tracker();
        result.as_ref().arm_focus_delegation_tracker();

        result
    }

    fn arm_state_tracker(self: Pin<&Self>) {
        let item = self.item.clone();
        let p = self.project_ref();
        p.state_tracker.evaluate_as_dependency_root(move || {
            if let Some(item_rc) = item.upgrade() {
                item_rc.accessible_string_property(AccessibleStringProperty::Checkable);
                item_rc.accessible_string_property(AccessibleStringProperty::Checked);
                item_rc.accessible_string_property(AccessibleStringProperty::Expandable);
                item_rc.accessible_string_property(AccessibleStringProperty::Expanded);
                item_rc.accessible_string_property(AccessibleStringProperty::ReadOnly);
                item_rc.accessible_string_property(AccessibleStringProperty::Orientation);
            }
        });
    }

    fn arm_value_tracker(self: Pin<&Self>) {
        let item = self.item.clone();
        let p = self.project_ref();
        p.value_tracker.evaluate_as_dependency_root(move || {
            if let Some(item_rc) = item.upgrade() {
                item_rc.accessible_string_property(AccessibleStringProperty::Value);
                item_rc.accessible_string_property(AccessibleStringProperty::ValueMinimum);
                item_rc.accessible_string_property(AccessibleStringProperty::ValueMaximum);
                item_rc.accessible_string_property(AccessibleStringProperty::ValueStep);
            }
        });
    }

    fn arm_label_tracker(self: Pin<&Self>) {
        let item = self.item.clone();
        let p = self.project_ref();
        p.label_tracker.evaluate_as_dependency_root(move || {
            if let Some(item_rc) = item.upgrade() {
                item_rc.accessible_string_property(AccessibleStringProperty::Label);
            }
        });
    }

    fn arm_description_tracker(self: Pin<&Self>) {
        let item = self.item.clone();
        let p = self.project_ref();
        p.description_tracker.evaluate_as_dependency_root(move || {
            if let Some(item_rc) = item.upgrade() {
                item_rc.accessible_string_property(AccessibleStringProperty::Description);
            }
        });
    }

    fn arm_focus_delegation_tracker(self: Pin<&Self>) {
        let item = self.item.clone();
        let p = self.project_ref();
        p.focus_delegation_tracker.evaluate_as_dependency_root(move || {
            if let Some(item_rc) = item.upgrade() {
                item_rc.accessible_string_property(AccessibleStringProperty::DelegateFocus);
            }
        });
    }
}

cpp! {{
    // Note: Do not include <QtWidgets> to avoid inclusion of gl.h (see #10989).
    #include <QtGui/QAccessible>
    #include <QtWidgets/QWidget>

    #include <memory>

    /// KEEP IN SYNC WITH CONSTANTS IN RUST!
    const uint32_t CHECKED { QAccessible::UserText };
    const uint32_t VALUE_MINIMUM { CHECKED + 1 };
    const uint32_t VALUE_MAXIMUM { VALUE_MINIMUM + 1 };
    const uint32_t VALUE_STEP { VALUE_MAXIMUM + 1 };
    const uint32_t CHECKABLE { VALUE_STEP + 1 };
    const uint32_t EXPANDABLE { CHECKABLE + 1 };
    const uint32_t EXPANDED { EXPANDABLE + 1 };
    const uint32_t READ_ONLY { EXPANDED + 1 };

    // ------------------------------------------------------------------------------
    // Helper:
    // ------------------------------------------------------------------------------

    class Descendents {
    public:
        Descendents(void *root_item) {
            rustDescendents = rust!(Descendents_ctor [root_item: *mut c_void as "void*"] ->
                    SharedVector<ItemRc> as "void*" {
                i_slint_core::accessibility::accessible_descendents(
                        unsafe { &*(root_item as *mut ItemRc) })
                .collect()
            });
        }

        size_t count() const {
            return rust!(Descendents_count [rustDescendents: SharedVector<ItemRc> as "void*"] -> usize as "size_t" {
               rustDescendents.len()
            });
        }

        void* itemAt(size_t index) {
            return rust!(Descendents_itemAt [rustDescendents: SharedVector<ItemRc> as "void*",
                                             index: usize as "size_t"]
                    -> *mut ItemWeak as "void*" {
                let item_rc = rustDescendents[index].clone();
                let mut item_weak = Box::new(item_rc.downgrade());

                Box::into_raw(item_weak)
            });
        }

        QAccessible::Role roleAt(size_t index) const {
            return rust!(Descendents_roleAt [rustDescendents: SharedVector<ItemRc> as "void*",
                                             index: usize as "size_t"]
                    -> u32 as "QAccessible::Role" {
                match rustDescendents[index].accessible_role() {
                    i_slint_core::items::AccessibleRole::None => QAccessible_Role_NoRole,
                    i_slint_core::items::AccessibleRole::Button => QAccessible_Role_Button,
                    i_slint_core::items::AccessibleRole::Checkbox => QAccessible_Role_CheckBox,
                    i_slint_core::items::AccessibleRole::Combobox => QAccessible_Role_ComboBox,
                    i_slint_core::items::AccessibleRole::List => QAccessible_Role_List,
                    i_slint_core::items::AccessibleRole::Slider => QAccessible_Role_Slider,
                    i_slint_core::items::AccessibleRole::Spinbox => QAccessible_Role_SpinBox,
                    i_slint_core::items::AccessibleRole::Tab => QAccessible_Role_PageTab,
                    i_slint_core::items::AccessibleRole::TabList => QAccessible_Role_PageTabList,
                    i_slint_core::items::AccessibleRole::Text => QAccessible_Role_StaticText,
                    i_slint_core::items::AccessibleRole::ProgressIndicator => QAccessible_Role_ProgressBar,
                    i_slint_core::items::AccessibleRole::Table => QAccessible_Role_Table,
                    i_slint_core::items::AccessibleRole::Tree => QAccessible_Role_Tree,
                    i_slint_core::items::AccessibleRole::TextInput => QAccessible_Role_EditableText,
                    i_slint_core::items::AccessibleRole::Switch => QAccessible_Role_CheckBox,
                    i_slint_core::items::AccessibleRole::ListItem => QAccessible_Role_ListItem,
                    i_slint_core::items::AccessibleRole::TabPanel => QAccessible_Role_Pane,
                    i_slint_core::items::AccessibleRole::Groupbox => QAccessible_Role_Grouping,
                    i_slint_core::items::AccessibleRole::Image => QAccessible_Role_Graphic,
                    i_slint_core::items::AccessibleRole::RadioButton => QAccessible_Role_RadioButton,
                    i_slint_core::items::AccessibleRole::RadioGroup => QAccessible_Role_Grouping,
                    i_slint_core::items::AccessibleRole::Banner => QAccessible_Role_Section,
                    i_slint_core::items::AccessibleRole::Complementary => QAccessible_Role_ComplementaryContent,
                    i_slint_core::items::AccessibleRole::ContentInfo => QAccessible_Role_Footer,
                    i_slint_core::items::AccessibleRole::Form => QAccessible_Role_Form,
                    i_slint_core::items::AccessibleRole::Main => QAccessible_Role_Grouping,
                    i_slint_core::items::AccessibleRole::Navigation => QAccessible_Role_Grouping,
                    i_slint_core::items::AccessibleRole::Region => QAccessible_Role_Section,
                    i_slint_core::items::AccessibleRole::Search => QAccessible_Role_Grouping,
                    _ => QAccessible_Role_NoRole,
                }
            });
        }

        ~Descendents() {
            auto descendentsPtr = &rustDescendents;
            rust!(Descendents_dtor [descendentsPtr: *mut SharedVector<ItemRc> as "void**"] {
                unsafe { core::ptr::read(descendentsPtr); }
            });
        }

    private:
        void *rustDescendents;
    };

    void *root_item_for_window(void *rustWindow) {
        return rust!(root_item_for_window_ [rustWindow: &crate::qt_window::QtWindow as "void*"]
                -> *mut c_void as "void*" {
            let root_item = Box::new(ItemRc::new_root(WindowInner::from_pub(&rustWindow.window).component()).downgrade());
            Box::into_raw(root_item) as _
        });
    }

    // Returns the orientation of the item as a Qt::Orientation value
    // (Qt::Horizontal=1, Qt::Vertical=2), or 0 if the property is not set.
    int item_qt_orientation(void *data) {
        return rust!(item_qt_orientation_
            [data: &SlintAccessibleItemData as "void*"]
                -> i32 as "int" {
            data.item.upgrade()
                .and_then(|i| i.accessible_string_property(AccessibleStringProperty::Orientation))
                .and_then(|s| s.parse::<i_slint_core::items::Orientation>().ok())
                .map(|o| match o {
                    i_slint_core::items::Orientation::Horizontal => 1,
                    i_slint_core::items::Orientation::Vertical => 2,
                })
                .unwrap_or(0)
        });
    }

    QString item_string_property(void *data, uint32_t what) {
        return rust!(item_string_property_
            [data: &SlintAccessibleItemData as "void*", what: u32 as "uint32_t"]
                -> QString as "QString" {

            if let Some(item) = data.item.upgrade() {
                let string = match what {
                    NAME => item.accessible_string_property(AccessibleStringProperty::Label),
                    DESCRIPTION => item.accessible_string_property(AccessibleStringProperty::Description),
                    VALUE => item.accessible_string_property(AccessibleStringProperty::Value),
                    CHECKED => item.accessible_string_property(AccessibleStringProperty::Checked),
                    VALUE_MINIMUM => item.accessible_string_property(AccessibleStringProperty::ValueMinimum),
                    VALUE_MAXIMUM => item.accessible_string_property(AccessibleStringProperty::ValueMaximum),
                    VALUE_STEP => item.accessible_string_property(AccessibleStringProperty::ValueStep),
                    CHECKABLE => item.accessible_string_property(AccessibleStringProperty::Checkable),
                    EXPANDABLE => item.accessible_string_property(AccessibleStringProperty::Expandable),
                    EXPANDED => item.accessible_string_property(AccessibleStringProperty::Expanded),
                    READ_ONLY => item.accessible_string_property(AccessibleStringProperty::ReadOnly),
                    _ => None,
                };
                if let Some(string) = string {
                    return QString::from(string.as_ref())
                }
            };
            QString::default()
        });
    }

    // ------------------------------------------------------------------------------
    // Slint_accessible:
    // ------------------------------------------------------------------------------

    // Base object for accessibility support
    class Slint_accessible : public QAccessibleInterface {
    public:
        Slint_accessible(QAccessible::Role role, QAccessibleInterface *parent) :
             has_focus(false), has_focus_delegation(false), m_role(role), m_parent(parent)
        { }

        ~Slint_accessible() {
            qDeleteAll(m_children);
        }

        virtual void *rustItem() const = 0;

        // Returns the SlintWidget of the window... we have no other.
        virtual QWidget *qwidget() const = 0;

        QPoint mapToGlobal(const QPoint p) const {
            return qwidget()->mapToGlobal(p);
        }

        QPoint mapFromGlobal(const QPoint p) const {
            return qwidget()->mapFromGlobal(p);
        }

        void clearFocus() {
            has_focus = false;
            has_focus_delegation = false;

            for (int i = 0; i < rawChildCount(); ++i) {
                static_cast<Slint_accessible *>(child(i))->clearFocus();
            }
        }

        virtual void delegateFocus() const {
            sendFocusChangeEvent();
        }

        // Returns true if the item accepted the focus; false otherwise.
        bool focusItem(void *item) const {
            auto my_item = rustItem();
            if (rust!(Slint_accessible_findItem [item: &ItemWeak as "void *", my_item: &ItemWeak as "void*"] -> bool as "bool" {
                item == my_item
            })) {
                has_focus = true;

                delegateFocus();
                return true;
            }

            for (int i = 0; i < rawChildCount(); ++i) {
                if (static_cast<Slint_accessible *>(child(i))->focusItem(item)) {
                    return true;
                }
            }
            return false;
        }

        void sendFocusChangeEvent() const {
            auto event = QAccessibleEvent(object(), QAccessible::Focus);
            QAccessible::updateAccessibility(&event);
            has_focus_delegation = true;
        }

        bool isValid() const override {
            return true;
        }

        // navigation, hierarchy
        QAccessibleInterface *parent() const override {
            return m_parent;
        }

        QAccessibleInterface *focusChild() const override {
            if (has_focus_delegation) {
                return const_cast<QAccessibleInterface *>(static_cast<const QAccessibleInterface *>(this));
            }
            for (int i = 0; i < childCount(); ++i)  {
                if (auto focus = child(i)->focusChild()) return focus;
            }
            return nullptr;
        }

        int indexOfChild(const QAccessibleInterface *child) const override {
            return m_children.indexOf(child->object()); // FIXME: Theoretically we can have several QAIs per QObject!
        }

        // Will *not* trigger a build of the accessibility item tree!
        // Use this from the Slint side to make sure the accessibility
        // item tree is not generated needlessly.
        int rawChildCount() const {
            return m_children.count();
        }

        /// Will *not* trigger a build of the accessibility tree!
        QAccessibleInterface *rawChild(int index) const {
            if (0 <= index && index < rawChildCount())
                return QAccessible::queryAccessibleInterface(m_children[index]);
            return nullptr;
        }

        // May trigger a build of the accessibility item tree!
        // Use this from the Qt API side (which is triggered by the OS accessibility
        // layer to make sure accessibility information is up-to-date.
        int childCount() const override {
            return rawChildCount();
        }

        QAccessibleInterface *child(int index) const override {
            if (0 <= index && index < childCount())
                return QAccessible::queryAccessibleInterface(m_children[index]);
            return nullptr;
        }

        void setText(QAccessible::Text t, const QString &text) override {
            Q_UNUSED(t); Q_UNUSED(text);
        }

        QAccessible::Role role() const override {
            return m_role;
        }

        QRect rect() const override {
            auto item = rustItem();
            QRectF r = rust!(Slint_accessible_item_rect
                [item: *const ItemWeak as "void*"] -> qttypes::QRectF as "QRectF" {
                    if let Some(item_rc) = unsafe { item.as_ref().unwrap().upgrade() } {
                        let geometry = item_rc.geometry();

                        let mapped = item_rc.map_to_window(geometry.origin);

                        qttypes::QRectF {
                            x: mapped.x as _,
                            y: mapped.y as _,
                            width: geometry.width() as _,
                            height: geometry.height() as _,
                        }
                    } else {
                        Default::default()
                    }
                });
            auto topLeft = mapToGlobal(QPoint(static_cast<int>(r.left()), static_cast<int>(r.top())));
            auto bottomRight = mapToGlobal(QPoint(static_cast<int>(r.right()), static_cast<int>(r.bottom())));
            return QRect(topLeft, bottomRight);
        }

        QAccessibleInterface *childAt(int x, int y) const override {
            for (int i = 0; i < childCount(); ++i)  {
                auto c = child(i);
                auto r = c->rect();
                if (r.contains(x, y)) return c;
            }
            return nullptr;
        }

        void updateAccessibilityTree() const;

    protected:
        mutable bool has_focus;
        mutable bool has_focus_delegation;

    private:
        QAccessible::Role m_role = QAccessible::NoRole;
        QAccessibleInterface *m_parent = nullptr;
        mutable QList<QObject*> m_children;
    };

    // ------------------------------------------------------------------------------
    // Slint_accessible_item:
    // ------------------------------------------------------------------------------

    class Slint_accessible_item : public Slint_accessible, public QAccessibleValueInterface, public QAccessibleActionInterface
#if QT_VERSION >= QT_VERSION_CHECK(6, 11, 0)
        , public QAccessibleAttributesInterface
#endif
    {
    public:
        Slint_accessible_item(void *item, QObject *obj, QAccessible::Role role, QAccessibleInterface *parent) :
            Slint_accessible(role, parent), m_object(obj)
        {
            m_data = rust!(Slint_accessible_item_ctor [obj: *mut c_void as "QObject*",
                    item: &ItemWeak as "void*"] ->
                    *mut SlintAccessibleItemData as "void*" {
                        let data = SlintAccessibleItemData::new_pin_box(obj, item);
                        unsafe { Box::into_raw(Pin::into_inner_unchecked(data)) }
            });
        }

        QAccessibleActionInterface *actionInterface() { return this; }
        QAccessibleValueInterface *valueInterface() { return this; }


        ~Slint_accessible_item() {
            rust!(Slint_accessible_item_dtor [m_data: *mut SlintAccessibleItemData as "void*"] {
                unsafe { Pin::new_unchecked(Box::from_raw(m_data)) };
            });
        }

        void *rustItem() const override {
            return rust!(Slint_accessible_item_rustItem [m_data: Pin<&SlintAccessibleItemData> as "void*"] -> *const ItemWeak as "void*" {
                &m_data.item
            });
        }

        QObject *object() const override {
            return m_object;
        }

        QWidget *qwidget() const override {
            return dynamic_cast<Slint_accessible *>(parent())->qwidget();
        }

        void *data() const {
            return m_data;
        }

        QWindow *window() const override {
            return parent()->window();
        }

        void delegateFocus() const override {
            if (!has_focus) { return; }

            auto index = rust!(Slint_accessible_item_delegate_focus [m_data: Pin<&SlintAccessibleItemData> as "void*"] -> i32 as "int" {
                m_data.item.upgrade()
                    .and_then(|i| { i.accessible_string_property(AccessibleStringProperty::DelegateFocus) })
                    .and_then(|s| s.as_str().parse::<i32>().ok()).unwrap_or(-1)
            });

            if (index >= 0 && index < rawChildCount()) {
                static_cast<Slint_accessible_item*>(rawChild(index))->sendFocusChangeEvent();
            } else {
                sendFocusChangeEvent();
            }
        }

        // properties and state
        QString text(QAccessible::Text t) const override {
            return item_string_property(m_data, t);
        }

        QAccessible::State state() const override {
            auto checked = item_string_property(m_data, CHECKED);

            QAccessible::State state;
            state.active = 1;
            state.focusable = 1;
            state.focused = has_focus_delegation;
            state.checked = (checked == "true") ? 1 : 0;
            state.checkable = (item_string_property(m_data, CHECKABLE) == "true") ? 1 : 0;
            if (item_string_property(m_data, EXPANDABLE) == "true") {
                state.expandable = 1;
                if (item_string_property(m_data, EXPANDED) == "true") {
                    state.expanded = 1;
                } else {
                    state.collapsed = 1;
                }
            }
            state.readOnly = (item_string_property(m_data, READ_ONLY) == "true") ? 1 : 0;
            return state; /* FIXME */
        }

        void *interface_cast(QAccessible::InterfaceType t) override {
            if (t == QAccessible::ValueInterface && !item_string_property(m_data, QAccessible::Value).isEmpty()) {
                return static_cast<QAccessibleValueInterface*>(this);
            } else if (t == QAccessible::ActionInterface) {
                return static_cast<QAccessibleActionInterface*>(this);
            }
#if QT_VERSION >= QT_VERSION_CHECK(6, 11, 0)
            else if (t == QAccessible::AttributesInterface) {
                return static_cast<QAccessibleAttributesInterface*>(this);
            }
#endif
            return QAccessibleInterface::interface_cast(t);
        }

#if QT_VERSION >= QT_VERSION_CHECK(6, 11, 0)
        // QAccessibleAttributesInterface
        QList<QAccessible::Attribute> attributeKeys() const override {
            QList<QAccessible::Attribute> keys;
            if (item_qt_orientation(m_data) != 0) {
                keys << QAccessible::Attribute::Orientation;
            }
            return keys;
        }

        QVariant attributeValue(QAccessible::Attribute key) const override {
            if (key == QAccessible::Attribute::Orientation) {
                int o = item_qt_orientation(m_data);
                if (o != 0) {
                    return QVariant::fromValue(static_cast<Qt::Orientation>(o));
                }
            }
            return QVariant();
        }
#endif

        // AccessibleValueInterface:
        QVariant currentValue() const override {
            return item_string_property(m_data, QAccessible::Value);
        }

        void setCurrentValue(const QVariant &value) override {
            QString value_string = value.toString();
            rust!(Slint_accessible_setCurrentValue [m_data: Pin<&SlintAccessibleItemData> as "void*", value_string: qttypes::QString as "QString"] {
                let Some(item) = m_data.item.upgrade() else {return};
                item.accessible_action(&AccessibilityAction::SetValue(i_slint_core::format!("{value_string}")));
            });
        }

        QVariant maximumValue() const override {
            return item_string_property(m_data, VALUE_MAXIMUM);
        }

        QVariant minimumValue() const override {
            return item_string_property(m_data, VALUE_MINIMUM);
        }

        QVariant minimumStepSize() const override {
            return item_string_property(m_data, VALUE_STEP);
        }

        QStringList actionNames() const override {
            int supported = rust!(Slint_accessible_item_supported [m_data: Pin<&SlintAccessibleItemData> as "void*"] -> SupportedAccessibilityAction as "uint" {
                m_data.item.upgrade().map(|i| i.supported_accessibility_actions()).unwrap_or_default()
            });
            QStringList actions;
            if (supported & rust!(Slint_accessible_item_an1 [] -> SupportedAccessibilityAction as "uint" { SupportedAccessibilityAction::Default }))
                actions << QAccessibleActionInterface::pressAction();
            if (supported & rust!(Slint_accessible_item_an2 [] -> SupportedAccessibilityAction as "uint" { SupportedAccessibilityAction::Increment }))
                actions << QAccessibleActionInterface::increaseAction();
            if (supported & rust!(Slint_accessible_item_an3 [] -> SupportedAccessibilityAction as "uint" { SupportedAccessibilityAction::Decrement }))
                actions << QAccessibleActionInterface::decreaseAction();
            if (supported & rust!(Slint_accessible_item_an4 [] -> SupportedAccessibilityAction as "uint" { SupportedAccessibilityAction::Expand }))
                actions << QAccessibleActionInterface::pressAction();
            return actions;
        }

        void doAction(const QString &actionName) override {
            if (actionName == QAccessibleActionInterface::pressAction()) {
                rust!(Slint_accessible_item_do_action1 [m_data: Pin<&SlintAccessibleItemData> as "void*"] {
                    let Some(item) = m_data.item.upgrade() else {return};
                    let supported_actions = item.supported_accessibility_actions();
                    if supported_actions.contains(SupportedAccessibilityAction::Expand) {
                        item.accessible_action(&AccessibilityAction::Expand);
                    } else {
                        item.accessible_action(&AccessibilityAction::Default);
                    }
                });
            } else if (actionName == QAccessibleActionInterface::increaseAction()) {
                rust!(Slint_accessible_item_do_action2 [m_data: Pin<&SlintAccessibleItemData> as "void*"] {
                    let Some(item) = m_data.item.upgrade() else {return};
                    item.accessible_action(&AccessibilityAction::Increment);
                });
            } else if (actionName == QAccessibleActionInterface::decreaseAction()) {
                rust!(Slint_accessible_item_do_action3 [m_data: Pin<&SlintAccessibleItemData> as "void*"] {
                    let Some(item) = m_data.item.upgrade() else {return};
                    item.accessible_action(&AccessibilityAction::Decrement);
                });
            }
        }

        QStringList keyBindingsForAction(const QString &) const override {
            return QStringList();
        }

    private:
        QObject *m_object = nullptr;
        mutable void *m_data = nullptr;
    };

    // ------------------------------------------------------------------------------
    // Slint_accessible_window:
    // ------------------------------------------------------------------------------

    class Slint_accessible_window : public Slint_accessible {
    public:
        Slint_accessible_window(QWidget *widget, void *rust_window) :
            Slint_accessible(QAccessible::Window, QAccessible::queryAccessibleInterface(qApp)),
            m_widget(widget),
            m_rustWindow(rust_window)
        { }

        ~Slint_accessible_window()
        {
            rust!(Slint_accessible_window_dtor [m_rustWindow: *mut c_void as "void*"] {
                unsafe {
                    alloc::rc::Weak::from_raw(m_rustWindow as *const QtWindow); // Consume the Weak<QtWindow> we hold in our void*!
                }
            });
        }

        bool isUsed() const {
            return is_used;
        }

        void *rustItem() const override {
            return root_item_for_window(m_rustWindow);
        }

        QObject *object() const override {
            return m_widget;
        }

        QWidget *qwidget() const override {
            return m_widget;
        }

        QWindow *window() const override {
            return qobject_cast<QWidget *>(object())->windowHandle();
        }

        int childCount() const override {
            if (!is_used) { updateAccessibilityTree(); }
            is_used = true;
            return Slint_accessible::childCount();
        }

        // properties and state
        QString text(QAccessible::Text t) const override {
            switch (t) {
                case QAccessible::Name: return qobject_cast<QWidget*>(object())->windowTitle();
                default: return QString();
            }
        }

        QAccessible::State state() const override {
            QAccessible::State state;
            state.active = 1;
            state.focusable = 1;
            return state;
        }

    private:
        QWidget *m_widget;
        void *m_rustWindow; // *const QtWindow
        mutable bool is_used = false;
    };

    QList<QObject *> deleteStaleItems(QList<QObject *> &&current_children) {
        // Delete no longer valid objects:
        current_children.erase(std::remove_if(current_children.begin(), current_children.end(), [](QObject *o) {
            auto ai = dynamic_cast<Slint_accessible_item *>(QAccessible::queryAccessibleInterface(o));
            Q_ASSERT(ai);
            auto data = ai->data();

            if (rust!(Slint_delete_stale_items
                    [data: Pin<&SlintAccessibleItemData> as "void*"] -> bool as "bool" {
                data.item.upgrade().is_none()
            })) {
                o->deleteLater();
                return true;
            } else {
                return false;
            }
        }), current_children.end());

        return std::move(current_children);
    }

    int indexOfItem(const QList<QObject *> &existing, void *item) {
        for (int i = 0; i < existing.count(); ++i) {
            auto data = dynamic_cast<Slint_accessible_item *>(QAccessible::queryAccessibleInterface(existing[i]));
            if (rust!(Slint_indexOfItems [data: Pin<&SlintAccessibleItemData> as "void*", item: &ItemWeak as "void*"] -> bool as "bool" {
                data.item == *item
            })) {
                return i;
            }
        }
        return -1;
    }

    QList<QObject *> updateItems(QList<QObject *> &&current_children,
                                 Descendents &descendents,
                                 Slint_accessible *parent) {
        QList<QObject *> children = {};
        children.reserve(descendents.count());

        for (size_t i = 0; i < descendents.count(); ++i) {
            auto item = descendents.itemAt(i);
            auto index = indexOfItem(current_children, item);
            QObject *object = nullptr;
            Slint_accessible_item *ai = nullptr;

            if (index == -1) {
                // Create new item:
                object = new QObject();
                auto role = descendents.roleAt(i);
                ai = new Slint_accessible_item(item, object, role, parent);

                QAccessible::registerAccessibleInterface(ai);
            } else {
                // Reuse existing item:
                object = current_children[index];
                ai = dynamic_cast<Slint_accessible_item *>(QAccessible::queryAccessibleInterface(object));

                current_children.removeAt(index);
            }

            Q_ASSERT(ai);
            Q_ASSERT(object);

            ai->updateAccessibilityTree();

            children.append(object);
        }

        return children;
    }

    void Slint_accessible::updateAccessibilityTree() const {
        QList<QObject *> valid_objects = deleteStaleItems(std::move(m_children));
        auto descendents = Descendents(rustItem());

        m_children = updateItems(std::move(valid_objects), descendents,
                                 const_cast<Slint_accessible *>(this));
    }
}}
