// 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

use crate::api::{CompilationResult, ComponentDefinition, Value};
use crate::global_component::CompiledGlobalCollection;
use crate::{dynamic_type, eval};
use core::ffi::c_void;
use core::ptr::NonNull;
use dynamic_type::{Instance, InstanceBox};
use i_slint_compiler::expression_tree::{Expression, NamedReference, TwoWayBinding};
use i_slint_compiler::langtype::{BuiltinStruct, StructName, Type};
use i_slint_compiler::object_tree::{ElementRc, ElementWeak, TransitionDirection};
use i_slint_compiler::{CompilerConfiguration, generator, object_tree, parser};
use i_slint_compiler::{diagnostics::BuildDiagnostics, object_tree::PropertyDeclaration};
use i_slint_core::accessibility::{
    AccessibilityAction, AccessibleStringProperty, SupportedAccessibilityAction,
};
use i_slint_core::api::LogicalPosition;
use i_slint_core::component_factory::ComponentFactory;
use i_slint_core::input::Keys;
use i_slint_core::item_tree::{
    IndexRange, ItemRc, ItemTree, ItemTreeNode, ItemTreeRef, ItemTreeRefPin, ItemTreeVTable,
    ItemTreeWeak, ItemVisitorRefMut, ItemVisitorVTable, ItemWeak, TraversalOrder,
    VisitChildrenResult,
};
use i_slint_core::items::{
    AccessibleRole, ItemRef, ItemVTable, PopupClosePolicy, PropertyAnimation,
};
use i_slint_core::layout::{LayoutInfo, LayoutItemInfo, Orientation};
use i_slint_core::lengths::{LogicalLength, LogicalRect};
use i_slint_core::menus::MenuFromItemTree;
use i_slint_core::model::{ModelRc, RepeatedItemTree, Repeater};
use i_slint_core::platform::PlatformError;
use i_slint_core::properties::{ChangeTracker, InterpolatedPropertyValue};
use i_slint_core::rtti::{self, AnimatedBindingKind, FieldOffset, PropertyInfo};
use i_slint_core::slice::Slice;
use i_slint_core::styled_text::StyledText;
use i_slint_core::timers::Timer;
use i_slint_core::window::{WindowAdapterRc, WindowInner, WindowKind};
use i_slint_core::{Brush, Color, DataTransfer, Property, SharedString, SharedVector};
#[cfg(feature = "internal")]
use itertools::Either;
use once_cell::unsync::{Lazy, OnceCell};
use smol_str::{SmolStr, ToSmolStr};
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::num::NonZeroU32;
use std::rc::Weak;
use std::{pin::Pin, rc::Rc};

pub const SPECIAL_PROPERTY_INDEX: &str = "$index";
pub const SPECIAL_PROPERTY_MODEL_DATA: &str = "$model_data";

pub(crate) type CallbackHandler = Box<dyn Fn(&[Value]) -> Value>;

pub struct ItemTreeBox<'id> {
    instance: InstanceBox<'id>,
    description: Rc<ItemTreeDescription<'id>>,
}

impl<'id> ItemTreeBox<'id> {
    /// Borrow this instance as a `Pin<ItemTreeRef>`
    pub fn borrow(&self) -> ItemTreeRefPin<'_> {
        self.borrow_instance().borrow()
    }

    /// Safety: the lifetime is not unique
    pub fn description(&self) -> Rc<ItemTreeDescription<'id>> {
        self.description.clone()
    }

    pub fn borrow_instance<'a>(&'a self) -> InstanceRef<'a, 'id> {
        InstanceRef { instance: self.instance.as_pin_ref(), description: &self.description }
    }

    pub fn window_adapter_ref(&self) -> Result<&WindowAdapterRc, PlatformError> {
        let root_weak = vtable::VWeak::into_dyn(self.borrow_instance().root_weak().clone());
        InstanceRef::get_or_init_window_adapter_ref(
            &self.description,
            root_weak,
            true,
            self.instance.as_pin_ref().get_ref(),
        )
    }
}

pub(crate) type ErasedItemTreeBoxWeak = vtable::VWeak<ItemTreeVTable, ErasedItemTreeBox>;

pub(crate) struct ItemWithinItemTree {
    offset: usize,
    pub(crate) rtti: Rc<ItemRTTI>,
    elem: ElementRc,
}

impl ItemWithinItemTree {
    /// Safety: the pointer must be a dynamic item tree which is coming from the same description as Self
    pub(crate) unsafe fn item_from_item_tree(
        &self,
        mem: *const u8,
    ) -> Pin<vtable::VRef<'_, ItemVTable>> {
        unsafe {
            Pin::new_unchecked(vtable::VRef::from_raw(
                NonNull::from(self.rtti.vtable),
                NonNull::new(mem.add(self.offset) as _).unwrap(),
            ))
        }
    }

    pub(crate) fn item_index(&self) -> u32 {
        *self.elem.borrow().item_index.get().unwrap()
    }
}

pub(crate) struct PropertiesWithinComponent {
    pub(crate) offset: usize,
    pub(crate) prop: Box<dyn PropertyInfo<u8, Value>>,
}

pub(crate) struct RepeaterWithinItemTree<'par_id, 'sub_id> {
    /// The description of the items to repeat
    pub(crate) item_tree_to_repeat: Rc<ItemTreeDescription<'sub_id>>,
    /// The model
    pub(crate) model: Expression,
    /// Offset of the `Repeater`
    offset: FieldOffset<Instance<'par_id>, Repeater<ErasedItemTreeBox>>,
    /// When true, it is representing a `if`, instead of a `for`.
    /// Based on [`i_slint_compiler::object_tree::RepeatedElementInfo::is_conditional_element`]
    is_conditional: bool,
}

impl RepeatedItemTree for ErasedItemTreeBox {
    type Data = Value;

    fn update(&self, index: usize, data: Self::Data) {
        generativity::make_guard!(guard);
        let s = self.unerase(guard);
        let is_repeated = s.description.original.parent_element().is_some_and(|p| {
            p.borrow().repeated.as_ref().is_some_and(|r| !r.is_conditional_element)
        });
        if is_repeated {
            s.description.set_property(s.borrow(), SPECIAL_PROPERTY_INDEX, index.into()).unwrap();
            s.description.set_property(s.borrow(), SPECIAL_PROPERTY_MODEL_DATA, data).unwrap();
        }
    }

    fn init(&self) {
        self.run_setup_code();
    }

    fn listview_layout(self: Pin<&Self>, offset_y: &mut LogicalLength) -> LogicalLength {
        generativity::make_guard!(guard);
        let s = self.unerase(guard);

        let geom = s.description.original.root_element.borrow().geometry_props.clone().unwrap();

        crate::eval::store_property(
            s.borrow_instance(),
            &geom.y.element(),
            geom.y.name(),
            Value::Number(offset_y.get() as f64),
        )
        .expect("cannot set y");

        let h: LogicalLength = crate::eval::load_property(
            s.borrow_instance(),
            &geom.height.element(),
            geom.height.name(),
        )
        .expect("missing height")
        .try_into()
        .expect("height not the right type");

        *offset_y += h;
        LogicalLength::new(self.borrow().as_ref().layout_info(Orientation::Horizontal).min)
    }

    fn layout_item_info(
        self: Pin<&Self>,
        o: Orientation,
        child_index: Option<usize>,
    ) -> LayoutItemInfo {
        generativity::make_guard!(guard);
        let s = self.unerase(guard);

        if let Some(index) = child_index {
            let instance_ref = s.borrow_instance();
            let root_element = &s.description.original.root_element;

            let children = root_element.borrow().children.clone();
            if let Some(child_elem) = children.get(index) {
                // Get the layout info for this child element
                let layout_info = crate::eval_layout::get_layout_info(
                    child_elem,
                    instance_ref,
                    &instance_ref.window_adapter(),
                    crate::eval_layout::from_runtime(o),
                );
                return LayoutItemInfo { constraint: layout_info };
            } else {
                panic!(
                    "child_index {} out of bounds for repeated item {}",
                    index,
                    s.description().id()
                );
            }
        }

        LayoutItemInfo { constraint: self.borrow().as_ref().layout_info(o) }
    }

    fn flexbox_layout_item_info(
        self: Pin<&Self>,
        o: Orientation,
        child_index: Option<usize>,
    ) -> i_slint_core::layout::FlexboxLayoutItemInfo {
        generativity::make_guard!(guard);
        let s = self.unerase(guard);
        let instance_ref = s.borrow_instance();
        let root_element = &s.description.original.root_element;

        let load_f32 = |name: &str| -> f32 {
            eval::load_property(instance_ref, root_element, name)
                .ok()
                .and_then(|v| v.try_into().ok())
                .unwrap_or(0.0)
        };

        let flex_grow = load_f32("flex-grow");
        let flex_shrink = load_f32("flex-shrink");
        let flex_basis =
            if root_element.borrow().binding_cell_including_synthetic("flex-basis").is_some() {
                load_f32("flex-basis")
            } else {
                -1.0
            };
        let cross_axis_self_alignment =
            eval::load_property(instance_ref, root_element, "cross-axis-self-alignment")
                .ok()
                .and_then(|v| v.try_into().ok())
                .unwrap_or(i_slint_core::items::CrossAxisSelfAlignment::Auto);
        let flex_order = load_f32("flex-order") as i32;

        i_slint_core::layout::FlexboxLayoutItemInfo {
            constraint: self.layout_item_info(o, child_index).constraint,
            props: i_slint_core::layout::FlexItemProps {
                flex_grow,
                flex_shrink,
                flex_basis,
                cross_axis_self_alignment,
                flex_order,
            },
        }
    }
}

impl ItemTree for ErasedItemTreeBox {
    fn visit_children_item(
        self: Pin<&Self>,
        index: isize,
        order: TraversalOrder,
        visitor: ItemVisitorRefMut,
    ) -> VisitChildrenResult {
        self.borrow().as_ref().visit_children_item(index, order, visitor)
    }

    fn layout_info(self: Pin<&Self>, orientation: Orientation) -> i_slint_core::layout::LayoutInfo {
        self.borrow().as_ref().layout_info(orientation)
    }

    fn ensure_instantiated(self: Pin<&Self>) -> bool {
        self.borrow().as_ref().ensure_instantiated()
    }

    fn get_item_tree(self: Pin<&Self>) -> Slice<'_, ItemTreeNode> {
        get_item_tree(self.get_ref().borrow())
    }

    fn get_item_ref(self: Pin<&Self>, index: u32) -> Pin<ItemRef<'_>> {
        // We're having difficulties transferring the lifetime to a pinned reference
        // to the other ItemTreeVTable with the same life time. So skip the vtable
        // indirection and call our implementation directly.
        unsafe { get_item_ref(self.get_ref().borrow(), index) }
    }

    fn get_subtree_range(self: Pin<&Self>, index: u32) -> IndexRange {
        self.borrow().as_ref().get_subtree_range(index)
    }

    fn get_subtree(self: Pin<&Self>, index: u32, subindex: usize, result: &mut ItemTreeWeak) {
        self.borrow().as_ref().get_subtree(index, subindex, result);
    }

    fn parent_node(self: Pin<&Self>, result: &mut ItemWeak) {
        self.borrow().as_ref().parent_node(result)
    }

    fn embed_component(
        self: core::pin::Pin<&Self>,
        parent_component: &ItemTreeWeak,
        item_tree_index: u32,
    ) -> bool {
        self.borrow().as_ref().embed_component(parent_component, item_tree_index)
    }

    fn subtree_index(self: Pin<&Self>) -> usize {
        self.borrow().as_ref().subtree_index()
    }

    fn item_geometry(self: Pin<&Self>, item_index: u32) -> i_slint_core::lengths::LogicalRect {
        self.borrow().as_ref().item_geometry(item_index)
    }

    fn accessible_role(self: Pin<&Self>, index: u32) -> AccessibleRole {
        self.borrow().as_ref().accessible_role(index)
    }

    fn accessible_string_property(
        self: Pin<&Self>,
        index: u32,
        what: AccessibleStringProperty,
        result: &mut SharedString,
    ) -> bool {
        self.borrow().as_ref().accessible_string_property(index, what, result)
    }

    fn window_adapter(self: Pin<&Self>, do_create: bool, result: &mut Option<WindowAdapterRc>) {
        self.borrow().as_ref().window_adapter(do_create, result);
    }

    fn accessibility_action(self: core::pin::Pin<&Self>, index: u32, action: &AccessibilityAction) {
        self.borrow().as_ref().accessibility_action(index, action)
    }

    fn supported_accessibility_actions(
        self: core::pin::Pin<&Self>,
        index: u32,
    ) -> SupportedAccessibilityAction {
        self.borrow().as_ref().supported_accessibility_actions(index)
    }

    fn item_element_infos(
        self: core::pin::Pin<&Self>,
        index: u32,
        result: &mut SharedString,
    ) -> bool {
        self.borrow().as_ref().item_element_infos(index, result)
    }
}

i_slint_core::ItemTreeVTable_static!(static COMPONENT_BOX_VT for ErasedItemTreeBox);

impl Drop for ErasedItemTreeBox {
    fn drop(&mut self) {
        generativity::make_guard!(guard);
        let unerase = self.unerase(guard);
        let instance_ref = unerase.borrow_instance();

        let maybe_window_adapter = instance_ref
            .description
            .extra_data_offset
            .apply(instance_ref.as_ref())
            .globals
            .get()
            .and_then(|globals| globals.window_adapter())
            .and_then(|wa| wa.get());
        if let Some(window_adapter) = maybe_window_adapter {
            i_slint_core::item_tree::unregister_item_tree(
                instance_ref.instance,
                vtable::VRef::new(self),
                instance_ref.description.item_array.as_slice(),
                window_adapter,
            );
        }
    }
}

pub type DynamicComponentVRc = vtable::VRc<ItemTreeVTable, ErasedItemTreeBox>;

#[derive(Default)]
pub(crate) struct ComponentExtraData {
    pub(crate) globals: OnceCell<crate::global_component::GlobalStorage>,
    pub(crate) self_weak: OnceCell<ErasedItemTreeBoxWeak>,
    pub(crate) embedding_position: OnceCell<(ItemTreeWeak, u32)>,
}

struct ErasedRepeaterWithinComponent<'id>(RepeaterWithinItemTree<'id, 'static>);
impl<'id, 'sub_id> From<RepeaterWithinItemTree<'id, 'sub_id>>
    for ErasedRepeaterWithinComponent<'id>
{
    fn from(from: RepeaterWithinItemTree<'id, 'sub_id>) -> Self {
        // Safety: this is safe as we erase the sub_id lifetime.
        // As long as when we get it back we get an unique lifetime with ErasedRepeaterWithinComponent::unerase
        Self(unsafe {
            core::mem::transmute::<
                RepeaterWithinItemTree<'id, 'sub_id>,
                RepeaterWithinItemTree<'id, 'static>,
            >(from)
        })
    }
}
impl<'id> ErasedRepeaterWithinComponent<'id> {
    pub fn unerase<'a, 'sub_id>(
        &'a self,
        _guard: generativity::Guard<'sub_id>,
    ) -> &'a RepeaterWithinItemTree<'id, 'sub_id> {
        // Safety: we just go from 'static to an unique lifetime
        unsafe {
            core::mem::transmute::<
                &'a RepeaterWithinItemTree<'id, 'static>,
                &'a RepeaterWithinItemTree<'id, 'sub_id>,
            >(&self.0)
        }
    }

    /// Return a repeater with a ItemTree with a 'static lifetime
    ///
    /// Safety: one should ensure that the inner ItemTree is not mixed with other inner ItemTree
    unsafe fn get_untagged(&self) -> &RepeaterWithinItemTree<'id, 'static> {
        &self.0
    }
}

type Callback = i_slint_core::Callback<[Value], Value>;

#[derive(Clone)]
pub struct ErasedItemTreeDescription(Rc<ItemTreeDescription<'static>>);
impl ErasedItemTreeDescription {
    pub fn unerase<'a, 'id>(
        &'a self,
        _guard: generativity::Guard<'id>,
    ) -> &'a Rc<ItemTreeDescription<'id>> {
        // Safety: we just go from 'static to an unique lifetime
        unsafe {
            core::mem::transmute::<
                &'a Rc<ItemTreeDescription<'static>>,
                &'a Rc<ItemTreeDescription<'id>>,
            >(&self.0)
        }
    }
}
impl<'id> From<Rc<ItemTreeDescription<'id>>> for ErasedItemTreeDescription {
    fn from(from: Rc<ItemTreeDescription<'id>>) -> Self {
        // Safety: We never access the ItemTreeDescription with the static lifetime, only after we unerase it
        Self(unsafe {
            core::mem::transmute::<Rc<ItemTreeDescription<'id>>, Rc<ItemTreeDescription<'static>>>(
                from,
            )
        })
    }
}

/// ItemTreeDescription is a representation of a ItemTree suitable for interpretation
///
/// It contains information about how to create and destroy the Component.
/// Its first member is the ItemTreeVTable for generated instance, since it is a `#[repr(C)]`
/// structure, it is valid to cast a pointer to the ItemTreeVTable back to a
/// ItemTreeDescription to access the extra field that are needed at runtime
#[repr(C)]
pub struct ItemTreeDescription<'id> {
    pub(crate) ct: ItemTreeVTable,
    /// INVARIANT: both dynamic_type and item_tree have the same lifetime id. Here it is erased to 'static
    dynamic_type: Rc<dynamic_type::TypeInfo<'id>>,
    item_tree: Vec<ItemTreeNode>,
    item_array:
        Vec<vtable::VOffset<crate::dynamic_type::Instance<'id>, ItemVTable, vtable::AllowPin>>,
    pub(crate) items: HashMap<SmolStr, ItemWithinItemTree>,
    pub(crate) custom_properties: HashMap<SmolStr, PropertiesWithinComponent>,
    pub(crate) custom_callbacks: HashMap<SmolStr, FieldOffset<Instance<'id>, Callback>>,
    /// For each exported callback, a `Property<()>` that tracks when the handler changes.
    /// Calling `get()` before invoking a callback registers a dependency; calling `mark_dirty()`
    /// after setting a handler triggers re-evaluation of dependent bindings.
    pub(crate) callback_trackers: HashMap<SmolStr, FieldOffset<Instance<'id>, Property<()>>>,
    repeater: Vec<ErasedRepeaterWithinComponent<'id>>,
    /// Map the Element::id of the repeater to the index in the `repeater` vec
    pub repeater_names: HashMap<SmolStr, usize>,
    /// Offset to a Option<ComponentPinRef>
    pub(crate) parent_item_tree_offset:
        Option<FieldOffset<Instance<'id>, OnceCell<ErasedItemTreeBoxWeak>>>,
    pub(crate) root_offset: FieldOffset<Instance<'id>, OnceCell<ErasedItemTreeBoxWeak>>,
    /// Offset of a ComponentExtraData
    pub(crate) extra_data_offset: FieldOffset<Instance<'id>, ComponentExtraData>,
    /// Keep the Rc alive
    pub(crate) original: Rc<object_tree::Component>,
    /// Maps from an item_id to the original element it came from
    pub(crate) original_elements: Vec<ElementRc>,
    /// Copy of original.root_element.property_declarations, without a guarded refcell
    public_properties: BTreeMap<SmolStr, PropertyDeclaration>,
    change_trackers: Option<(
        FieldOffset<Instance<'id>, OnceCell<Vec<ChangeTracker>>>,
        Vec<(NamedReference, Expression)>,
    )>,
    timers: Vec<FieldOffset<Instance<'id>, Timer>>,
    /// Map of element IDs to their active popup's ID
    popup_ids: std::cell::RefCell<HashMap<SmolStr, NonZeroU32>>,

    pub(crate) popup_menu_description: PopupMenuDescription,

    /// The collection of compiled globals
    compiled_globals: Option<Rc<CompiledGlobalCollection>>,

    /// The type loader, which will be available only on the top-most `ItemTreeDescription`.
    /// All other `ItemTreeDescription`s have `None` here.
    #[cfg(feature = "internal-highlight")]
    pub(crate) type_loader:
        std::cell::OnceCell<std::rc::Rc<i_slint_compiler::typeloader::TypeLoader>>,
    /// The type loader, which will be available only on the top-most `ItemTreeDescription`.
    /// All other `ItemTreeDescription`s have `None` here.
    #[cfg(feature = "internal-highlight")]
    pub(crate) raw_type_loader:
        std::cell::OnceCell<Option<std::rc::Rc<i_slint_compiler::typeloader::TypeLoader>>>,
}

#[derive(Clone, derive_more::From)]
pub(crate) enum PopupMenuDescription {
    Rc(Rc<ErasedItemTreeDescription>),
    Weak(Weak<ErasedItemTreeDescription>),
}
impl PopupMenuDescription {
    pub fn unerase<'id>(&self, guard: generativity::Guard<'id>) -> Rc<ItemTreeDescription<'id>> {
        match self {
            PopupMenuDescription::Rc(rc) => rc.unerase(guard).clone(),
            PopupMenuDescription::Weak(weak) => weak.upgrade().unwrap().unerase(guard).clone(),
        }
    }
}

fn internal_properties_to_public<'a>(
    prop_iter: impl Iterator<Item = (&'a SmolStr, &'a PropertyDeclaration)> + 'a,
) -> impl Iterator<
    Item = (
        SmolStr,
        i_slint_compiler::langtype::Type,
        i_slint_compiler::object_tree::PropertyVisibility,
    ),
> + 'a {
    prop_iter.filter(|(_, v)| v.expose_in_public_api).map(|(s, v)| {
        let name = v
            .node
            .as_ref()
            .and_then(|n| {
                n.child_node(parser::SyntaxKind::DeclaredIdentifier)
                    .and_then(|n| n.child_token(parser::SyntaxKind::Identifier))
            })
            .map(|n| n.to_smolstr())
            .unwrap_or_else(|| s.to_smolstr());
        (name, v.property_type.clone(), v.visibility)
    })
}

#[derive(Default)]
pub enum WindowOptions {
    #[default]
    CreateNewWindow,
    UseExistingWindow(WindowAdapterRc),
    Embed {
        parent_item_tree: ItemTreeWeak,
        parent_item_tree_index: u32,
    },
}

impl ItemTreeDescription<'_> {
    /// The name of this Component as written in the .slint file
    pub fn id(&self) -> &str {
        self.original.id.as_str()
    }

    #[cfg(feature = "internal")]
    pub(crate) fn compiled_globals(&self) -> Option<Rc<CompiledGlobalCollection>> {
        self.compiled_globals.clone()
    }

    /// List of publicly declared properties or callbacks
    ///
    /// We try to preserve the dashes and underscore as written in the property declaration
    pub fn properties(
        &self,
    ) -> impl Iterator<
        Item = (
            SmolStr,
            i_slint_compiler::langtype::Type,
            i_slint_compiler::object_tree::PropertyVisibility,
        ),
    > + '_ {
        internal_properties_to_public(self.public_properties.iter())
    }

    /// List names of exported global singletons
    pub fn global_names(&self) -> impl Iterator<Item = SmolStr> + '_ {
        self.compiled_globals
            .as_ref()
            .expect("Root component should have globals")
            .compiled_globals
            .iter()
            .filter(|g| g.visible_in_public_api())
            .flat_map(|g| g.names().into_iter())
    }

    pub fn global_properties(
        &self,
        name: &str,
    ) -> Option<
        impl Iterator<
            Item = (
                SmolStr,
                i_slint_compiler::langtype::Type,
                i_slint_compiler::object_tree::PropertyVisibility,
            ),
        > + '_,
    > {
        let g = self.compiled_globals.as_ref().expect("Root component should have globals");
        g.exported_globals_by_name
            .get(&crate::normalize_identifier(name))
            .and_then(|global_idx| g.compiled_globals.get(*global_idx))
            .map(|global| internal_properties_to_public(global.public_properties()))
    }

    /// Instantiate a runtime ItemTree from this ItemTreeDescription
    pub fn create(
        self: Rc<Self>,
        options: WindowOptions,
    ) -> Result<DynamicComponentVRc, PlatformError> {
        i_slint_backend_selector::with_platform(|_b| {
            // Nothing to do, just make sure a backend was created
            Ok(())
        })?;

        let instance = instantiate(self, None, None, Some(&options), Default::default());
        if let WindowOptions::UseExistingWindow(existing_adapter) = options {
            WindowInner::from_pub(existing_adapter.window())
                .set_component(&vtable::VRc::into_dyn(instance.clone()));
        }
        instance.run_setup_code();
        Ok(instance)
    }

    /// Set a value to property.
    ///
    /// Return an error if the property with this name does not exist,
    /// or if the value is the wrong type.
    /// Panics if the component is not an instance corresponding to this ItemTreeDescription,
    pub fn set_property(
        &self,
        component: ItemTreeRefPin,
        name: &str,
        value: Value,
    ) -> Result<(), crate::api::SetPropertyError> {
        if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
            panic!("mismatch instance and vtable");
        }
        generativity::make_guard!(guard);
        let c = unsafe { InstanceRef::from_pin_ref(component, guard) };
        if let Some(alias) = self
            .original
            .root_element
            .borrow()
            .property_declarations
            .get(name)
            .and_then(|d| d.is_alias.as_ref())
        {
            eval::store_property(c, &alias.element(), alias.name(), value)
        } else {
            eval::store_property(c, &self.original.root_element, name, value)
        }
    }

    /// Set a binding to a property
    ///
    /// Returns an error if the instance does not corresponds to this ItemTreeDescription,
    /// or if the property with this name does not exist in this component
    pub fn set_binding(
        &self,
        component: ItemTreeRefPin,
        name: &str,
        binding: Box<dyn Fn() -> Value>,
    ) -> Result<(), ()> {
        if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
            return Err(());
        }
        let x = self.custom_properties.get(name).ok_or(())?;
        unsafe {
            x.prop
                .set_binding(
                    Pin::new_unchecked(&*component.as_ptr().add(x.offset)),
                    binding,
                    i_slint_core::rtti::AnimatedBindingKind::NotAnimated,
                )
                .unwrap()
        };
        Ok(())
    }

    /// Return the value of a property
    ///
    /// Returns an error if the component is not an instance corresponding to this ItemTreeDescription,
    /// or if a callback with this name does not exist
    pub fn get_property(&self, component: ItemTreeRefPin, name: &str) -> Result<Value, ()> {
        if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
            return Err(());
        }
        generativity::make_guard!(guard);
        // Safety: we just verified that the component has the right vtable
        let c = unsafe { InstanceRef::from_pin_ref(component, guard) };
        if let Some(alias) = self
            .original
            .root_element
            .borrow()
            .property_declarations
            .get(name)
            .and_then(|d| d.is_alias.as_ref())
        {
            eval::load_property(c, &alias.element(), alias.name())
        } else {
            eval::load_property(c, &self.original.root_element, name)
        }
    }

    /// Sets an handler for a callback
    ///
    /// Returns an error if the component is not an instance corresponding to this ItemTreeDescription,
    /// or if the property with this name does not exist
    pub fn set_callback_handler(
        &self,
        component: Pin<ItemTreeRef>,
        name: &str,
        handler: CallbackHandler,
    ) -> Result<(), ()> {
        if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
            return Err(());
        }
        if let Some(alias) = self
            .original
            .root_element
            .borrow()
            .property_declarations
            .get(name)
            .and_then(|d| d.is_alias.as_ref())
        {
            generativity::make_guard!(guard);
            // Safety: we just verified that the component has the right vtable
            let c = unsafe { InstanceRef::from_pin_ref(component, guard) };
            let inst = eval::ComponentInstance::InstanceRef(c);
            eval::set_callback_handler(&inst, &alias.element(), alias.name(), handler)?
        } else {
            let x = self.custom_callbacks.get(name).ok_or(())?;
            let inst = unsafe { &*(component.as_ptr() as *const dynamic_type::Instance) };
            let sig = x.apply(inst);
            sig.set_handler(handler);
            if let Some(tracker_offset) = self.callback_trackers.get(name) {
                tracker_offset.apply_pin(unsafe { Pin::new_unchecked(inst) }).mark_dirty();
            }
        }
        Ok(())
    }

    /// Invoke the specified callback or function
    ///
    /// Returns an error if the component is not an instance corresponding to this ItemTreeDescription,
    /// or if the callback with this name does not exist in this component
    pub fn invoke(
        &self,
        component: ItemTreeRefPin,
        name: &SmolStr,
        args: &[Value],
    ) -> Result<Value, ()> {
        if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
            return Err(());
        }
        generativity::make_guard!(guard);
        // Safety: we just verified that the component has the right vtable
        let c = unsafe { InstanceRef::from_pin_ref(component, guard) };
        let borrow = self.original.root_element.borrow();
        let decl = borrow.property_declarations.get(name).ok_or(())?;

        let (elem, name) = if let Some(alias) = &decl.is_alias {
            (alias.element(), alias.name())
        } else {
            (self.original.root_element.clone(), name)
        };

        let inst = eval::ComponentInstance::InstanceRef(c);

        if matches!(&decl.property_type, Type::Function { .. }) {
            eval::call_function(&inst, &elem, name, args.to_vec()).ok_or(())
        } else {
            eval::invoke_callback(&inst, &elem, name, args).ok_or(())
        }
    }

    // Return the global with the given name
    pub fn get_global(
        &self,
        component: ItemTreeRefPin,
        global_name: &str,
    ) -> Result<Pin<Rc<dyn crate::global_component::GlobalComponent>>, ()> {
        if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
            return Err(());
        }
        generativity::make_guard!(guard);
        // Safety: we just verified that the component has the right vtable
        let c = unsafe { InstanceRef::from_pin_ref(component, guard) };
        let extra_data = c.description.extra_data_offset.apply(c.instance.get_ref());
        let g = extra_data.globals.get().unwrap().get(global_name).clone();
        g.ok_or(())
    }
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn visit_children_item(
    component: ItemTreeRefPin,
    index: isize,
    order: TraversalOrder,
    v: ItemVisitorRefMut,
) -> VisitChildrenResult {
    generativity::make_guard!(guard);
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
    let comp_rc = instance_ref.self_weak().get().unwrap().upgrade().unwrap();
    i_slint_core::item_tree::visit_item_tree(
        &vtable::VRc::into_dyn(comp_rc),
        get_item_tree(component).as_slice(),
        index,
        order,
        v,
        &mut |order, visitor, index| {
            if index as usize >= instance_ref.description.repeater.len() {
                // Do nothing: We are ComponentContainer and Our parent already did all the work!
                VisitChildrenResult::CONTINUE
            } else {
                generativity::make_guard!(guard);
                let rep_in_comp = instance_ref.description.repeater[index as usize].unerase(guard);
                let repeater = rep_in_comp.offset.apply_pin(instance_ref.instance);
                repeater.visit(order, visitor)
            }
        },
    )
}

/// Information attached to a builtin item
pub(crate) struct ItemRTTI {
    vtable: &'static ItemVTable,
    type_info: dynamic_type::StaticTypeInfo,
    pub(crate) properties: HashMap<&'static str, Box<dyn eval::ErasedPropertyInfo>>,
    pub(crate) callbacks: HashMap<&'static str, Box<dyn eval::ErasedCallbackInfo>>,
}

fn rtti_for<T: 'static + Default + rtti::BuiltinItem + vtable::HasStaticVTable<ItemVTable>>()
-> (&'static str, Rc<ItemRTTI>) {
    let rtti = ItemRTTI {
        vtable: T::STATIC_VTABLE,
        type_info: dynamic_type::StaticTypeInfo::new::<T>(),
        properties: T::properties()
            .into_iter()
            .map(|(k, v)| (k, Box::new(v) as Box<dyn eval::ErasedPropertyInfo>))
            .collect(),
        callbacks: T::callbacks()
            .into_iter()
            .map(|(k, v)| (k, Box::new(v) as Box<dyn eval::ErasedCallbackInfo>))
            .collect(),
    };
    (T::name(), Rc::new(rtti))
}

/// Create a ItemTreeDescription from a source.
/// The path corresponding to the source need to be passed as well (path is used for diagnostics
/// and loading relative assets)
pub async fn load(
    source: String,
    path: std::path::PathBuf,
    mut compiler_config: CompilerConfiguration,
) -> CompilationResult {
    // If the native style should be Qt, resolve it here as we know that we have it
    let is_native = compiler_config.style.as_deref() == Some("native");
    if is_native {
        // On wasm, look at the browser user agent
        #[cfg(target_arch = "wasm32")]
        let target = web_sys::window()
            .and_then(|window| window.navigator().platform().ok())
            .map_or("wasm", |platform| {
                let platform = platform.to_ascii_lowercase();
                if platform.contains("mac")
                    || platform.contains("iphone")
                    || platform.contains("ipad")
                {
                    "apple"
                } else if platform.contains("android") {
                    "android"
                } else if platform.contains("win") {
                    "windows"
                } else if platform.contains("linux") {
                    "linux"
                } else {
                    "wasm"
                }
            });
        #[cfg(not(target_arch = "wasm32"))]
        let target = "";
        compiler_config.style = Some(
            i_slint_common::get_native_style(i_slint_backend_selector::HAS_NATIVE_STYLE, target)
                .to_string(),
        );
    }

    let diag = BuildDiagnostics::default();
    #[cfg(feature = "internal-highlight")]
    let (path, mut diag, loader, raw_type_loader) =
        i_slint_compiler::load_root_file_with_raw_type_loader(
            &path,
            &path,
            source,
            diag,
            compiler_config,
        )
        .await;
    #[cfg(not(feature = "internal-highlight"))]
    let (path, mut diag, loader) =
        i_slint_compiler::load_root_file(&path, &path, source, diag, compiler_config).await;
    #[cfg(feature = "internal")]
    let watch_paths = loader.all_files_to_watch().into_iter().collect();
    if diag.has_errors() {
        return CompilationResult {
            components: HashMap::new(),
            diagnostics: diag.into_iter().collect(),
            #[cfg(feature = "internal")]
            watch_paths,
            #[cfg(feature = "internal")]
            structs_and_enums: Vec::new(),
            #[cfg(feature = "internal")]
            named_exports: Vec::new(),
        };
    }

    #[cfg(feature = "internal-highlight")]
    let loader = Rc::new(loader);
    #[cfg(feature = "internal-highlight")]
    let raw_type_loader = raw_type_loader.map(Rc::new);

    let doc = loader.get_document(&path).unwrap();

    let compiled_globals = Rc::new(CompiledGlobalCollection::compile(doc));
    let mut components = HashMap::new();

    let popup_menu_description = if let Some(popup_menu_impl) = &doc.popup_menu_impl {
        PopupMenuDescription::Rc(Rc::new_cyclic(|weak| {
            generativity::make_guard!(guard);
            ErasedItemTreeDescription::from(generate_item_tree(
                popup_menu_impl,
                Some(compiled_globals.clone()),
                PopupMenuDescription::Weak(weak.clone()),
                true,
                guard,
            ))
        }))
    } else {
        PopupMenuDescription::Weak(Default::default())
    };

    for c in doc.exported_roots() {
        generativity::make_guard!(guard);
        #[allow(unused_mut)]
        let mut it = generate_item_tree(
            &c,
            Some(compiled_globals.clone()),
            popup_menu_description.clone(),
            false,
            guard,
        );
        #[cfg(feature = "internal-highlight")]
        {
            let _ = it.type_loader.set(loader.clone());
            let _ = it.raw_type_loader.set(raw_type_loader.clone());
        }
        components.insert(c.id.to_string(), ComponentDefinition { inner: it.into() });
    }

    if components.is_empty() {
        diag.push_error_with_span("No component found".into(), Default::default());
    };

    #[cfg(feature = "internal")]
    let structs_and_enums = doc.used_types.borrow().structs_and_enums.clone();

    #[cfg(feature = "internal")]
    let named_exports = doc
        .exports
        .iter()
        .filter_map(|export| match &export.1 {
            Either::Left(component) if !component.is_global() => {
                Some((&export.0.name, &component.id))
            }
            Either::Right(ty) => match &ty {
                Type::Struct(s) if s.node().is_some() => {
                    if let StructName::User { name, .. } = &s.name {
                        Some((&export.0.name, name))
                    } else {
                        None
                    }
                }
                Type::Enumeration(en) => Some((&export.0.name, &en.name)),
                _ => None,
            },
            _ => None,
        })
        .filter(|(export_name, type_name)| *export_name != *type_name)
        .map(|(export_name, type_name)| (type_name.to_string(), export_name.to_string()))
        .collect::<Vec<_>>();

    CompilationResult {
        diagnostics: diag.into_iter().collect(),
        components,
        #[cfg(feature = "internal")]
        watch_paths,
        #[cfg(feature = "internal")]
        structs_and_enums,
        #[cfg(feature = "internal")]
        named_exports,
    }
}

fn generate_rtti() -> HashMap<&'static str, Rc<ItemRTTI>> {
    let mut rtti = HashMap::new();
    use i_slint_core::items::*;
    rtti.extend(
        [
            rtti_for::<ComponentContainer>(),
            rtti_for::<Empty>(),
            rtti_for::<ImageItem>(),
            rtti_for::<ClippedImage>(),
            rtti_for::<ComplexText>(),
            rtti_for::<StyledTextItem>(),
            rtti_for::<SimpleText>(),
            rtti_for::<Rectangle>(),
            rtti_for::<BasicBorderRectangle>(),
            rtti_for::<BorderRectangle>(),
            rtti_for::<TouchArea>(),
            rtti_for::<TooltipArea>(),
            rtti_for::<FocusScope>(),
            rtti_for::<KeyBinding>(),
            rtti_for::<SwipeGestureHandler>(),
            rtti_for::<ScaleRotateGestureHandler>(),
            rtti_for::<Path>(),
            rtti_for::<Flickable>(),
            rtti_for::<WindowItem>(),
            rtti_for::<TextInput>(),
            rtti_for::<Clip>(),
            rtti_for::<BoxShadow>(),
            rtti_for::<Transform>(),
            rtti_for::<Opacity>(),
            rtti_for::<Layer>(),
            rtti_for::<DragArea>(),
            rtti_for::<DropArea>(),
            rtti_for::<WindowMoveArea>(),
            rtti_for::<ContextMenu>(),
            rtti_for::<MenuItem>(),
            rtti_for::<SystemTrayIcon>(),
        ]
        .iter()
        .cloned(),
    );

    trait NativeHelper {
        fn push(rtti: &mut HashMap<&str, Rc<ItemRTTI>>);
    }
    impl NativeHelper for () {
        fn push(_rtti: &mut HashMap<&str, Rc<ItemRTTI>>) {}
    }
    impl<
        T: 'static + Default + rtti::BuiltinItem + vtable::HasStaticVTable<ItemVTable>,
        Next: NativeHelper,
    > NativeHelper for (T, Next)
    {
        fn push(rtti: &mut HashMap<&str, Rc<ItemRTTI>>) {
            let info = rtti_for::<T>();
            rtti.insert(info.0, info.1);
            Next::push(rtti);
        }
    }
    i_slint_backend_selector::NativeWidgets::push(&mut rtti);

    rtti
}

pub(crate) fn generate_item_tree<'id>(
    component: &Rc<object_tree::Component>,
    compiled_globals: Option<Rc<CompiledGlobalCollection>>,
    popup_menu_description: PopupMenuDescription,
    is_popup_menu_impl: bool,
    guard: generativity::Guard<'id>,
) -> Rc<ItemTreeDescription<'id>> {
    thread_local! {
        static RTTI: Lazy<HashMap<&'static str, Rc<ItemRTTI>>> = Lazy::new(generate_rtti);
    }

    struct TreeBuilder<'id> {
        tree_array: Vec<ItemTreeNode>,
        item_array:
            Vec<vtable::VOffset<crate::dynamic_type::Instance<'id>, ItemVTable, vtable::AllowPin>>,
        original_elements: Vec<ElementRc>,
        items_types: HashMap<SmolStr, ItemWithinItemTree>,
        type_builder: dynamic_type::TypeBuilder<'id>,
        repeater: Vec<ErasedRepeaterWithinComponent<'id>>,
        repeater_names: HashMap<SmolStr, usize>,
        change_callbacks: Vec<(NamedReference, Expression)>,
        popup_menu_description: PopupMenuDescription,
        compiled_globals: Option<Rc<CompiledGlobalCollection>>,
    }
    impl generator::ItemTreeBuilder for TreeBuilder<'_> {
        type SubComponentState = ();

        fn push_repeated_item(
            &mut self,
            item_rc: &ElementRc,
            repeater_count: u32,
            parent_index: u32,
            _component_state: &Self::SubComponentState,
        ) {
            self.tree_array.push(ItemTreeNode::DynamicTree { index: repeater_count, parent_index });
            self.original_elements.push(item_rc.clone());
            let item = item_rc.borrow();
            let base_component = item.base_type.as_component();
            self.repeater_names.insert(item.id.clone(), self.repeater.len());
            generativity::make_guard!(guard);
            let repeated_element_info = item.repeated.as_ref().unwrap();
            self.repeater.push(
                RepeaterWithinItemTree {
                    item_tree_to_repeat: generate_item_tree(
                        base_component,
                        self.compiled_globals.clone(),
                        self.popup_menu_description.clone(),
                        false,
                        guard,
                    ),
                    offset: self.type_builder.add_field_type::<Repeater<ErasedItemTreeBox>>(),
                    model: repeated_element_info.model.clone(),
                    is_conditional: repeated_element_info.is_conditional_element,
                }
                .into(),
            );
        }

        fn push_native_item(
            &mut self,
            rc_item: &ElementRc,
            child_offset: u32,
            parent_index: u32,
            _component_state: &Self::SubComponentState,
        ) {
            let item = rc_item.borrow();
            let rt = RTTI.with(|rtti| {
                rtti.get(&*item.base_type.as_native().class_name)
                    .unwrap_or_else(|| {
                        panic!(
                            "Native type not registered: {}",
                            item.base_type.as_native().class_name
                        )
                    })
                    .clone()
            });

            let offset = self.type_builder.add_field(rt.type_info);

            self.tree_array.push(ItemTreeNode::Item {
                is_accessible: !item.accessibility_props.0.is_empty(),
                children_index: child_offset,
                children_count: item.children.len() as u32,
                parent_index,
                item_array_index: self.item_array.len() as u32,
            });
            self.item_array.push(unsafe { vtable::VOffset::from_raw(rt.vtable, offset) });
            self.original_elements.push(rc_item.clone());
            debug_assert_eq!(self.original_elements.len(), self.tree_array.len());
            self.items_types.insert(
                item.id.clone(),
                ItemWithinItemTree { offset, rtti: rt, elem: rc_item.clone() },
            );
            for (prop, expr) in &item.change_callbacks {
                self.change_callbacks.push((
                    NamedReference::new(rc_item, prop.clone()),
                    Expression::CodeBlock(expr.borrow().clone()),
                ));
            }
        }

        fn enter_component(
            &mut self,
            _item: &ElementRc,
            _sub_component: &Rc<object_tree::Component>,
            _children_offset: u32,
            _component_state: &Self::SubComponentState,
        ) -> Self::SubComponentState {
            /* nothing to do */
        }

        fn enter_component_children(
            &mut self,
            _item: &ElementRc,
            _repeater_count: u32,
            _component_state: &Self::SubComponentState,
            _sub_component_state: &Self::SubComponentState,
        ) {
            todo!()
        }
    }

    let mut builder = TreeBuilder {
        tree_array: Vec::new(),
        item_array: Vec::new(),
        original_elements: Vec::new(),
        items_types: HashMap::new(),
        type_builder: dynamic_type::TypeBuilder::new(guard),
        repeater: Vec::new(),
        repeater_names: HashMap::new(),
        change_callbacks: Vec::new(),
        popup_menu_description,
        compiled_globals: compiled_globals.clone(),
    };

    if !component.is_global() {
        generator::build_item_tree(component, &(), &mut builder);
    } else {
        for (prop, expr) in component.root_element.borrow().change_callbacks.iter() {
            builder.change_callbacks.push((
                NamedReference::new(&component.root_element, prop.clone()),
                Expression::CodeBlock(expr.borrow().clone()),
            ));
        }
    }

    let mut custom_properties = HashMap::new();
    let mut custom_callbacks = HashMap::new();
    let mut callback_trackers = HashMap::new();
    fn property_info<T>() -> (Box<dyn PropertyInfo<u8, Value>>, dynamic_type::StaticTypeInfo)
    where
        T: PartialEq + Clone + Default + std::convert::TryInto<Value> + 'static,
        Value: std::convert::TryInto<T>,
    {
        // Fixme: using u8 in PropertyInfo<> is not sound, we would need to materialize a type for out component
        (
            Box::new(unsafe {
                vtable::FieldOffset::<u8, Property<T>, _>::new_from_offset_pinned(0)
            }),
            dynamic_type::StaticTypeInfo::new::<Property<T>>(),
        )
    }
    fn animated_property_info<T>()
    -> (Box<dyn PropertyInfo<u8, Value>>, dynamic_type::StaticTypeInfo)
    where
        T: Clone + Default + InterpolatedPropertyValue + std::convert::TryInto<Value> + 'static,
        Value: std::convert::TryInto<T>,
    {
        // Fixme: using u8 in PropertyInfo<> is not sound, we would need to materialize a type for out component
        (
            Box::new(unsafe {
                rtti::MaybeAnimatedPropertyInfoWrapper(
                    vtable::FieldOffset::<u8, Property<T>, _>::new_from_offset_pinned(0),
                )
            }),
            dynamic_type::StaticTypeInfo::new::<Property<T>>(),
        )
    }

    fn property_info_for_type(
        ty: &Type,
        name: &str,
    ) -> Option<(Box<dyn PropertyInfo<u8, Value>>, dynamic_type::StaticTypeInfo)> {
        Some(match ty {
            Type::Float32 => animated_property_info::<f32>(),
            Type::Int32 => animated_property_info::<i32>(),
            Type::String => property_info::<SharedString>(),
            Type::Color => animated_property_info::<Color>(),
            Type::Brush => animated_property_info::<Brush>(),
            Type::Duration => animated_property_info::<i64>(),
            Type::Angle => animated_property_info::<f32>(),
            Type::PhysicalLength => animated_property_info::<f32>(),
            Type::LogicalLength => animated_property_info::<f32>(),
            Type::Rem => animated_property_info::<f32>(),
            Type::Image => property_info::<i_slint_core::graphics::Image>(),
            Type::Bool => property_info::<bool>(),
            Type::ComponentFactory => property_info::<ComponentFactory>(),
            Type::Struct(s) if matches!(s.name, StructName::Builtin(BuiltinStruct::StateInfo)) => {
                property_info::<i_slint_core::properties::StateInfo>()
            }
            Type::Struct(_) => property_info::<Value>(),
            Type::Array(_) => property_info::<Value>(),
            Type::Easing => property_info::<i_slint_core::animations::EasingCurve>(),
            Type::MouseCursor => property_info::<i_slint_core::cursor::MouseCursorInner>(),
            Type::Percent => animated_property_info::<f32>(),
            Type::Enumeration(e) => {
                macro_rules! match_enum_type {
                    ($( $(#[$enum_doc:meta])* $vis:vis enum $Name:ident { $($body:tt)* })*) => {
                        match e.name.as_str() {
                            $(
                                stringify!($Name) => property_info::<i_slint_core::items::$Name>(),
                            )*
                            x => unreachable!("Unknown non-builtin enum {x}"),
                        }
                    }
                }

                if e.node.is_some() {
                    property_info::<Value>()
                } else {
                    i_slint_common::for_each_enums!(match_enum_type)
                }
            }
            Type::Keys => property_info::<Keys>(),
            Type::DataTransfer => property_info::<DataTransfer>(),
            Type::LayoutCache => property_info::<SharedVector<f32>>(),
            Type::ArrayOfU16 => property_info::<SharedVector<u16>>(),
            Type::Function { .. } | Type::Callback { .. } => return None,
            Type::StyledText => property_info::<StyledText>(),
            // These can't be used in properties
            Type::Invalid
            | Type::Void
            | Type::InferredProperty
            | Type::InferredCallback
            | Type::Model
            | Type::PathData
            | Type::UnitProduct(_)
            | Type::ElementReference
            | Type::Closure => panic!("bad type {ty:?} for property {name}"),
        })
    }

    for (name, decl) in &component.root_element.borrow().property_declarations {
        if decl.is_alias.is_some() {
            continue;
        }
        if matches!(&decl.property_type, Type::Callback { .. }) {
            custom_callbacks
                .insert(name.clone(), builder.type_builder.add_field_type::<Callback>());
            if decl.expose_in_public_api {
                callback_trackers
                    .insert(name.clone(), builder.type_builder.add_field_type::<Property<()>>());
            }
            continue;
        }
        let Some((prop, type_info)) = property_info_for_type(&decl.property_type, name) else {
            continue;
        };
        custom_properties.insert(
            name.clone(),
            PropertiesWithinComponent { offset: builder.type_builder.add_field(type_info), prop },
        );
    }
    if let Some(parent_element) = component.parent_element()
        && let Some(r) = &parent_element.borrow().repeated
        && !r.is_conditional_element
    {
        let (prop, type_info) = property_info::<u32>();
        custom_properties.insert(
            SPECIAL_PROPERTY_INDEX.into(),
            PropertiesWithinComponent { offset: builder.type_builder.add_field(type_info), prop },
        );

        let model_ty = Expression::RepeaterModelReference {
            element: component.parent_element.borrow().clone(),
        }
        .ty();
        let (prop, type_info) =
            property_info_for_type(&model_ty, SPECIAL_PROPERTY_MODEL_DATA).unwrap();
        custom_properties.insert(
            SPECIAL_PROPERTY_MODEL_DATA.into(),
            PropertiesWithinComponent { offset: builder.type_builder.add_field(type_info), prop },
        );
    }

    let parent_item_tree_offset = if component.parent_element().is_some() || is_popup_menu_impl {
        Some(builder.type_builder.add_field_type::<OnceCell<ErasedItemTreeBoxWeak>>())
    } else {
        None
    };

    let root_offset = builder.type_builder.add_field_type::<OnceCell<ErasedItemTreeBoxWeak>>();
    let extra_data_offset = builder.type_builder.add_field_type::<ComponentExtraData>();

    let change_trackers = (!builder.change_callbacks.is_empty()).then(|| {
        (
            builder.type_builder.add_field_type::<OnceCell<Vec<ChangeTracker>>>(),
            builder.change_callbacks,
        )
    });
    let timers = component
        .timers
        .borrow()
        .iter()
        .map(|_| builder.type_builder.add_field_type::<Timer>())
        .collect();

    // only the public exported component needs the public property list
    let public_properties = if component.parent_element().is_none() {
        component.root_element.borrow().property_declarations.clone()
    } else {
        Default::default()
    };

    let t = ItemTreeVTable {
        visit_children_item,
        layout_info,
        ensure_instantiated,
        get_item_ref,
        get_item_tree,
        get_subtree_range,
        get_subtree,
        parent_node,
        embed_component,
        subtree_index,
        item_geometry,
        accessible_role,
        accessible_string_property,
        accessibility_action,
        supported_accessibility_actions,
        item_element_infos,
        window_adapter,
        drop_in_place,
        dealloc,
    };
    let t = ItemTreeDescription {
        ct: t,
        dynamic_type: builder.type_builder.build(),
        item_tree: builder.tree_array,
        item_array: builder.item_array,
        items: builder.items_types,
        custom_properties,
        custom_callbacks,
        callback_trackers,
        original: component.clone(),
        original_elements: builder.original_elements,
        repeater: builder.repeater,
        repeater_names: builder.repeater_names,
        parent_item_tree_offset,
        root_offset,
        extra_data_offset,
        public_properties,
        compiled_globals,
        change_trackers,
        timers,
        popup_ids: std::cell::RefCell::new(HashMap::new()),
        popup_menu_description: builder.popup_menu_description,
        #[cfg(feature = "internal-highlight")]
        type_loader: std::cell::OnceCell::new(),
        #[cfg(feature = "internal-highlight")]
        raw_type_loader: std::cell::OnceCell::new(),
    };

    Rc::new(t)
}

pub fn animation_for_property(
    component: InstanceRef,
    animation: &Option<i_slint_compiler::object_tree::PropertyAnimation>,
) -> AnimatedBindingKind {
    match animation {
        Some(i_slint_compiler::object_tree::PropertyAnimation::Static(anim_elem)) => {
            AnimatedBindingKind::Animation(Box::new({
                let component_ptr = component.as_ptr();
                let vtable = NonNull::from(&component.description.ct).cast();
                let anim_elem = Rc::clone(anim_elem);
                move || -> PropertyAnimation {
                    generativity::make_guard!(guard);
                    let component = unsafe {
                        InstanceRef::from_pin_ref(
                            Pin::new_unchecked(vtable::VRef::from_raw(
                                vtable,
                                NonNull::new_unchecked(component_ptr as *mut u8),
                            )),
                            guard,
                        )
                    };

                    eval::new_struct_with_bindings(
                        &anim_elem.borrow().bindings,
                        &mut eval::EvalLocalContext::from_component_instance(component),
                    )
                }
            }))
        }
        Some(i_slint_compiler::object_tree::PropertyAnimation::Transition {
            animations,
            state_ref,
        }) => {
            let component_ptr = component.as_ptr();
            let vtable = NonNull::from(&component.description.ct).cast();
            let animations = animations.clone();
            let state_ref = state_ref.clone();
            AnimatedBindingKind::Transition(Box::new(
                move || -> (PropertyAnimation, i_slint_core::animations::Instant) {
                    generativity::make_guard!(guard);
                    let component = unsafe {
                        InstanceRef::from_pin_ref(
                            Pin::new_unchecked(vtable::VRef::from_raw(
                                vtable,
                                NonNull::new_unchecked(component_ptr as *mut u8),
                            )),
                            guard,
                        )
                    };

                    let mut context = eval::EvalLocalContext::from_component_instance(component);
                    let state = eval::eval_expression(&state_ref, &mut context);
                    let state_info: i_slint_core::properties::StateInfo = state.try_into().unwrap();
                    for a in &animations {
                        let is_previous_state = a.state_id == state_info.previous_state;
                        let is_current_state = a.state_id == state_info.current_state;
                        match (a.direction, is_previous_state, is_current_state) {
                            (TransitionDirection::In, false, true)
                            | (TransitionDirection::Out, true, false)
                            | (TransitionDirection::InOut, false, true)
                            | (TransitionDirection::InOut, true, false) => {
                                return (
                                    eval::new_struct_with_bindings(
                                        &a.animation.borrow().bindings,
                                        &mut context,
                                    ),
                                    state_info.change_time,
                                );
                            }
                            _ => {}
                        }
                    }
                    Default::default()
                },
            ))
        }
        None => AnimatedBindingKind::NotAnimated,
    }
}

fn make_callback_eval_closure(
    expr: Expression,
    self_weak: ErasedItemTreeBoxWeak,
) -> impl Fn(&[Value]) -> Value {
    move |args| {
        let self_rc = self_weak.upgrade().unwrap();
        generativity::make_guard!(guard);
        let self_ = self_rc.unerase(guard);
        let instance_ref = self_.borrow_instance();
        let mut local_context =
            eval::EvalLocalContext::from_function_arguments(instance_ref, args.to_vec());
        eval::eval_expression(&expr, &mut local_context)
    }
}

fn make_binding_eval_closure(
    expr: Expression,
    self_weak: ErasedItemTreeBoxWeak,
) -> impl Fn() -> Value {
    move || {
        let self_rc = self_weak.upgrade().unwrap();
        generativity::make_guard!(guard);
        let self_ = self_rc.unerase(guard);
        let instance_ref = self_.borrow_instance();
        eval::eval_expression(
            &expr,
            &mut eval::EvalLocalContext::from_component_instance(instance_ref),
        )
    }
}

pub fn instantiate(
    description: Rc<ItemTreeDescription>,
    parent_ctx: Option<ErasedItemTreeBoxWeak>,
    root: Option<ErasedItemTreeBoxWeak>,
    window_options: Option<&WindowOptions>,
    globals: crate::global_component::GlobalStorage,
) -> DynamicComponentVRc {
    let instance = description.dynamic_type.clone().create_instance();

    let component_box = ItemTreeBox { instance, description: description.clone() };

    let self_rc = vtable::VRc::new(ErasedItemTreeBox::from(component_box));
    let self_weak = vtable::VRc::downgrade(&self_rc);

    generativity::make_guard!(guard);
    let comp = self_rc.unerase(guard);
    let instance_ref = comp.borrow_instance();
    instance_ref.self_weak().set(self_weak.clone()).ok();
    let description = comp.description();

    if let Some(WindowOptions::UseExistingWindow(existing_adapter)) = &window_options
        && let Err((a, b)) = globals.window_adapter().unwrap().try_insert(existing_adapter.clone())
    {
        assert!(Rc::ptr_eq(a, &b), "window not the same as parent window");
    }

    let has_parent = parent_ctx.is_some();
    if let Some(parent) = parent_ctx {
        description
            .parent_item_tree_offset
            .unwrap()
            .apply(instance_ref.as_ref())
            .set(parent)
            .ok()
            .unwrap();
    }
    let extra_data = description.extra_data_offset.apply(instance_ref.as_ref());
    extra_data.globals.set(globals.clone()).ok().unwrap();

    let resolved_root = if let Some(WindowOptions::Embed { .. }) = window_options {
        self_weak.clone()
    } else {
        generativity::make_guard!(guard);
        root.or_else(|| {
            instance_ref.parent_instance(guard).map(|parent| parent.root_weak().clone())
        })
        .unwrap_or_else(|| self_weak.clone())
    };
    description.root_offset.apply(instance_ref.as_ref()).set(resolved_root).ok().unwrap();

    if !has_parent && let Some(g) = description.compiled_globals.as_ref() {
        for g in g.compiled_globals.iter() {
            crate::global_component::instantiate(g, &globals, self_weak.clone());
        }
    }

    if let Some(WindowOptions::Embed { parent_item_tree, parent_item_tree_index }) = window_options
    {
        vtable::VRc::borrow_pin(&self_rc)
            .as_ref()
            .embed_component(parent_item_tree, *parent_item_tree_index);
    }

    if !description.original.is_global() {
        let maybe_window_adapter =
            if let Some(WindowOptions::UseExistingWindow(adapter)) = window_options.as_ref() {
                Some(adapter.clone())
            } else {
                extra_data.globals.get().unwrap().window_adapter().and_then(|wa| wa.get().cloned())
            };

        let component_rc = vtable::VRc::into_dyn(self_rc.clone());
        i_slint_core::item_tree::register_item_tree(&component_rc, maybe_window_adapter);
    }

    // Some properties are generated as Value, but for which the default constructed Value must be initialized
    for (prop_name, decl) in &description.original.root_element.borrow().property_declarations {
        if !matches!(
            decl.property_type,
            Type::Struct { .. } | Type::Array(_) | Type::Enumeration(_)
        ) || decl.is_alias.is_some()
        {
            continue;
        }
        let p = description.custom_properties.get(prop_name).unwrap();
        unsafe {
            let item = Pin::new_unchecked(&*instance_ref.as_ptr().add(p.offset));
            p.prop.set(item, eval::default_value_for_type(&decl.property_type), None).unwrap();
        }
    }

    #[cfg(slint_debug_property)]
    {
        let component_id = description.original.id.as_str();

        // Set debug names on custom (root element) properties
        for (prop_name, prop_info) in &description.custom_properties {
            let name = format!("{}.{}", component_id, prop_name);
            unsafe {
                let item = Pin::new_unchecked(&*instance_ref.as_ptr().add(prop_info.offset));
                prop_info.prop.set_debug_name(item, name);
            }
        }

        // Set debug names on built-in item properties
        for (item_name, item_within_component) in &description.items {
            let item = unsafe { item_within_component.item_from_item_tree(instance_ref.as_ptr()) };
            for (prop_name, prop_rtti) in &item_within_component.rtti.properties {
                let name = format!("{}::{}.{}", component_id, item_name, prop_name);
                prop_rtti.set_debug_name(item, name);
            }
        }
    }

    // Register the fonts before the property bindings, so a property that needs them
    // (image decoding, text sizing) finds them.
    for code in description.original.init_code.borrow().font_registration_code.iter() {
        eval::eval_expression(
            code,
            &mut eval::EvalLocalContext::from_component_instance(instance_ref),
        );
    }

    generator::handle_property_bindings_init(
        &description.original,
        |elem, prop_name, binding| unsafe {
            let is_root = Rc::ptr_eq(
                elem,
                &elem.borrow().enclosing_component.upgrade().unwrap().root_element,
            );
            let elem = elem.borrow();
            let is_const = binding.analysis.as_ref().is_some_and(|a| a.is_const);

            let property_type = elem.lookup_property(prop_name).property_type;
            if let Type::Function { .. } = property_type {
                // function don't need initialization
            } else if let Type::Callback { .. } = property_type {
                if !matches!(binding.expression, Expression::Invalid) {
                    let expr = binding.expression.clone();
                    let description = description.clone();
                    if let Some(callback_offset) =
                        description.custom_callbacks.get(prop_name).filter(|_| is_root)
                    {
                        let callback = callback_offset.apply(instance_ref.as_ref());
                        callback.set_handler(make_callback_eval_closure(expr, self_weak.clone()));
                    } else {
                        let item_within_component = &description.items[&elem.id];
                        let item = item_within_component.item_from_item_tree(instance_ref.as_ptr());
                        if let Some(callback) =
                            item_within_component.rtti.callbacks.get(prop_name.as_str())
                        {
                            callback.set_handler(
                                item,
                                Box::new(make_callback_eval_closure(expr, self_weak.clone())),
                            );
                        } else {
                            panic!("unknown callback {prop_name}")
                        }
                    }
                }
            } else if let Some(PropertiesWithinComponent { offset, prop: prop_info, .. }) =
                description.custom_properties.get(prop_name).filter(|_| is_root)
            {
                let is_state_info = matches!(&property_type, Type::Struct (s) if matches!(s.name, StructName::Builtin(BuiltinStruct::StateInfo)));
                if is_state_info {
                    let prop = Pin::new_unchecked(
                        &*(instance_ref.as_ptr().add(*offset)
                            as *const Property<i_slint_core::properties::StateInfo>),
                    );
                    let e = binding.expression.clone();
                    let state_binding = make_binding_eval_closure(e, self_weak.clone());
                    i_slint_core::properties::set_state_binding(prop, move || {
                        state_binding().try_into().unwrap()
                    });
                    return;
                }

                let maybe_animation = animation_for_property(instance_ref, &binding.animation);
                let item = Pin::new_unchecked(&*instance_ref.as_ptr().add(*offset));

                if !matches!(binding.expression, Expression::Invalid) {
                    if is_const {
                        let v = eval::eval_expression(
                            &binding.expression,
                            &mut eval::EvalLocalContext::from_component_instance(instance_ref),
                        );
                        prop_info.set(item, v, None).unwrap();
                    } else {
                        let e = binding.expression.clone();
                        prop_info
                            .set_binding(
                                item,
                                Box::new(make_binding_eval_closure(e, self_weak.clone())),
                                maybe_animation,
                            )
                            .unwrap();
                    }
                }
                for twb in &binding.two_way_bindings {
                    match twb {
                        TwoWayBinding::Property { property, field_access }
                            if field_access.is_empty()
                                && !matches!(
                                    &property_type,
                                    Type::Struct(..) | Type::Array(..)
                                ) =>
                        {
                            // Safety: The compiler ensured that the properties exist and have
                            // the same type (except for struct/array, which may map to a Value).
                            prop_info.link_two_ways(item, get_property_ptr(property, instance_ref));
                        }
                        TwoWayBinding::Property { property, field_access } => {
                            let (common, map) =
                                prepare_for_two_way_binding(instance_ref, property, field_access);
                            prop_info.link_two_way_with_map(item, common, map);
                        }
                        TwoWayBinding::ModelData { repeated_element, field_access } => {
                            let (getter, setter) = prepare_model_two_way_binding(
                                instance_ref,
                                repeated_element,
                                field_access,
                            );
                            prop_info.link_two_way_to_model_data(item, getter, setter);
                        }
                    }
                }
            } else {
                let item_within_component = &description.items[&elem.id];
                let item = item_within_component.item_from_item_tree(instance_ref.as_ptr());
                if let Some(prop_rtti) =
                    item_within_component.rtti.properties.get(prop_name.as_str())
                {
                    let maybe_animation = animation_for_property(instance_ref, &binding.animation);

                    for twb in &binding.two_way_bindings {
                        match twb {
                            TwoWayBinding::Property { property, field_access }
                                if field_access.is_empty()
                                    && !matches!(
                                        &property_type,
                                        Type::Struct(..) | Type::Array(..)
                                    ) =>
                            {
                                // Safety: The compiler ensured that the properties exist and
                                // have the same type.
                                prop_rtti
                                    .link_two_ways(item, get_property_ptr(property, instance_ref));
                            }
                            TwoWayBinding::Property { property, field_access } => {
                                let (common, map) = prepare_for_two_way_binding(
                                    instance_ref,
                                    property,
                                    field_access,
                                );
                                prop_rtti.link_two_way_with_map(item, common, map);
                            }
                            TwoWayBinding::ModelData { repeated_element, field_access } => {
                                let (getter, setter) = prepare_model_two_way_binding(
                                    instance_ref,
                                    repeated_element,
                                    field_access,
                                );
                                prop_rtti.link_two_way_to_model_data(item, getter, setter);
                            }
                        }
                    }
                    if !matches!(binding.expression, Expression::Invalid) {
                        if is_const {
                            prop_rtti
                                .set(
                                    item,
                                    eval::eval_expression(
                                        &binding.expression,
                                        &mut eval::EvalLocalContext::from_component_instance(
                                            instance_ref,
                                        ),
                                    ),
                                    maybe_animation.as_animation(),
                                )
                                .unwrap();
                        } else {
                            let e = binding.expression.clone();
                            prop_rtti.set_binding(
                                item,
                                Box::new(make_binding_eval_closure(e, self_weak.clone())),
                                maybe_animation,
                            );
                        }
                    }
                } else {
                    panic!("unknown property {} in {}", prop_name, elem.id);
                }
            }
        },
    );

    for rep_in_comp in &description.repeater {
        generativity::make_guard!(guard);
        let rep_in_comp = rep_in_comp.unerase(guard);

        let repeater = rep_in_comp.offset.apply_pin(instance_ref.instance);
        let expr = rep_in_comp.model.clone();
        let model_binding_closure = make_binding_eval_closure(expr, self_weak.clone());
        if rep_in_comp.is_conditional {
            let bool_model = Rc::new(crate::value_model::BoolModel::default());
            repeater.set_model_binding(move || {
                let v = model_binding_closure();
                bool_model.set_value(v.try_into().expect("condition model is bool"));
                ModelRc::from(bool_model.clone())
            });
        } else {
            repeater.set_model_binding(move || {
                let m = model_binding_closure();
                if let Value::Model(m) = m {
                    m
                } else {
                    ModelRc::new(crate::value_model::ValueModel::new(m))
                }
            });
        }
    }
    self_rc
}

fn prepare_for_two_way_binding(
    instance_ref: InstanceRef,
    property: &NamedReference,
    field_access: &[SmolStr],
) -> (Pin<Rc<Property<Value>>>, Option<Rc<dyn rtti::TwoWayBindingMapping<Value>>>) {
    let element = property.element();
    let name = property.name().as_str();

    generativity::make_guard!(guard);
    let enclosing_component = eval::enclosing_component_instance_for_element(
        &element,
        &eval::ComponentInstance::InstanceRef(instance_ref),
        guard,
    );
    let map: Option<Rc<dyn rtti::TwoWayBindingMapping<Value>>> = if field_access.is_empty() {
        None
    } else {
        struct FieldAccess(Vec<SmolStr>);
        impl rtti::TwoWayBindingMapping<Value> for FieldAccess {
            fn map_to(&self, value: &Value) -> Value {
                walk_struct_field_path(value.clone(), &self.0).unwrap_or_default()
            }
            fn map_from(&self, root: &mut Value, from: &Value) {
                if let Some(leaf) = walk_struct_field_path_mut(root, &self.0) {
                    *leaf = from.clone();
                }
            }
        }
        Some(Rc::new(FieldAccess(field_access.to_vec())))
    };
    let common = match enclosing_component {
        eval::ComponentInstance::InstanceRef(enclosing_component) => {
            let element = element.borrow();
            if element.id == element.enclosing_component.upgrade().unwrap().root_element.borrow().id
                && let Some(x) = enclosing_component.description.custom_properties.get(name)
            {
                let item =
                    unsafe { Pin::new_unchecked(&*enclosing_component.as_ptr().add(x.offset)) };
                let common = x.prop.prepare_for_two_way_binding(item);
                return (common, map);
            }
            let item_info = enclosing_component
                .description
                .items
                .get(element.id.as_str())
                .unwrap_or_else(|| panic!("Unknown element for {}.{}", element.id, name));
            let prop_info = item_info
                .rtti
                .properties
                .get(name)
                .unwrap_or_else(|| panic!("Property {} not in {}", name, element.id));
            core::mem::drop(element);
            let item = unsafe { item_info.item_from_item_tree(enclosing_component.as_ptr()) };
            prop_info.prepare_for_two_way_binding(item)
        }
        eval::ComponentInstance::GlobalComponent(glob) => {
            glob.as_ref().prepare_for_two_way_binding(name).unwrap()
        }
    };
    (common, map)
}

/// Build a (getter, setter) pair for a `TwoWayBinding::ModelData`. The
/// setter writes the whole row back through the field-access path, and
/// skips the write if the leaf value is unchanged.
fn prepare_model_two_way_binding(
    instance_ref: InstanceRef,
    repeated_element: &i_slint_compiler::object_tree::ElementWeak,
    field_access: &[SmolStr],
) -> (Box<dyn Fn() -> Option<Value>>, Box<dyn Fn(&Value)>) {
    let self_weak = instance_ref.self_weak().get().unwrap().clone();
    let repeated_element = repeated_element.clone();
    let field_access: Vec<SmolStr> = field_access.to_vec();

    let getter = {
        let self_weak = self_weak.clone();
        let repeated_element = repeated_element.clone();
        let field_access = field_access.clone();
        Box::new(move || -> Option<Value> {
            with_repeater_row(&self_weak, &repeated_element, |repeater, row| {
                walk_struct_field_path(repeater.model_row_data(row)?, &field_access)
            })
        })
    };

    let setter = Box::new(move |new_value: &Value| {
        with_repeater_row(&self_weak, &repeated_element, |repeater, row| {
            let mut data = repeater.model_row_data(row)?;
            // Short-circuit identical writes to avoid spurious change notifications.
            let leaf = walk_struct_field_path_mut(&mut data, &field_access)?;
            if &*leaf == new_value {
                return Some(());
            }
            *leaf = new_value.clone();
            repeater.model_set_row_data(row, data);
            Some(())
        });
    });

    (getter, setter)
}

/// Resolve the repeater that backs `repeated_element` and its current row
/// index, then run `f`. Returns `None` if any link is unavailable.
fn with_repeater_row<R>(
    self_weak: &ErasedItemTreeBoxWeak,
    repeated_element: &i_slint_compiler::object_tree::ElementWeak,
    f: impl FnOnce(Pin<&Repeater<ErasedItemTreeBox>>, usize) -> Option<R>,
) -> Option<R> {
    let self_rc = self_weak.upgrade()?;
    generativity::make_guard!(guard);
    let s = self_rc.unerase(guard);
    let instance = s.borrow_instance();
    let element = repeated_element.upgrade()?;
    let index = crate::eval::load_property(
        instance,
        &element.borrow().base_type.as_component().root_element,
        crate::dynamic_item_tree::SPECIAL_PROPERTY_INDEX,
    )
    .ok()?;
    let row = usize::try_from(i32::try_from(index).ok()?).ok()?;
    generativity::make_guard!(guard);
    let enclosing = crate::eval::enclosing_component_for_element(&element, instance, guard);
    generativity::make_guard!(guard);
    let (repeater, _) = get_repeater_by_name(enclosing, element.borrow().id.as_str(), guard);
    f(repeater, row)
}

/// Follow a chain of struct field accesses on `value`.
fn walk_struct_field_path(mut value: Value, fields: &[SmolStr]) -> Option<Value> {
    for f in fields {
        match value {
            Value::Struct(o) => value = o.get_field(f).cloned().unwrap_or_default(),
            Value::Void => return None,
            _ => return None,
        }
    }
    Some(value)
}

/// Mutable counterpart of [`walk_struct_field_path`].
fn walk_struct_field_path_mut<'a>(
    mut value: &'a mut Value,
    fields: &[SmolStr],
) -> Option<&'a mut Value> {
    for f in fields {
        match value {
            Value::Struct(o) => value = o.0.get_mut(f)?,
            _ => return None,
        }
    }
    Some(value)
}

pub(crate) fn get_property_ptr(nr: &NamedReference, instance: InstanceRef) -> *const c_void {
    let element = nr.element();
    generativity::make_guard!(guard);
    let enclosing_component = eval::enclosing_component_instance_for_element(
        &element,
        &eval::ComponentInstance::InstanceRef(instance),
        guard,
    );
    match enclosing_component {
        eval::ComponentInstance::InstanceRef(enclosing_component) => {
            let element = element.borrow();
            if element.id == element.enclosing_component.upgrade().unwrap().root_element.borrow().id
                && let Some(x) = enclosing_component.description.custom_properties.get(nr.name())
            {
                return unsafe { enclosing_component.as_ptr().add(x.offset).cast() };
            };
            let item_info = enclosing_component
                .description
                .items
                .get(element.id.as_str())
                .unwrap_or_else(|| panic!("Unknown element for {}.{}", element.id, nr.name()));
            let prop_info = item_info
                .rtti
                .properties
                .get(nr.name().as_str())
                .unwrap_or_else(|| panic!("Property {} not in {}", nr.name(), element.id));
            core::mem::drop(element);
            let item = unsafe { item_info.item_from_item_tree(enclosing_component.as_ptr()) };
            unsafe { item.as_ptr().add(prop_info.offset()).cast() }
        }
        eval::ComponentInstance::GlobalComponent(glob) => glob.as_ref().get_property_ptr(nr.name()),
    }
}

pub struct ErasedItemTreeBox(ItemTreeBox<'static>);
impl ErasedItemTreeBox {
    pub fn unerase<'a, 'id>(
        &'a self,
        _guard: generativity::Guard<'id>,
    ) -> Pin<&'a ItemTreeBox<'id>> {
        Pin::new(
            //Safety: 'id is unique because of `_guard`
            unsafe { core::mem::transmute::<&ItemTreeBox<'static>, &ItemTreeBox<'id>>(&self.0) },
        )
    }

    pub fn borrow(&self) -> ItemTreeRefPin<'_> {
        // Safety: it is safe to access self.0 here because the 'id lifetime does not leak
        self.0.borrow()
    }

    pub fn window_adapter_ref(&self) -> Result<&WindowAdapterRc, PlatformError> {
        self.0.window_adapter_ref()
    }

    pub fn run_setup_code(&self) {
        generativity::make_guard!(guard);
        let compo_box = self.unerase(guard);
        let instance_ref = compo_box.borrow_instance();
        for extra_init_code in
            self.0.description.original.init_code.borrow().iter_without_font_registration()
        {
            eval::eval_expression(
                extra_init_code,
                &mut eval::EvalLocalContext::from_component_instance(instance_ref),
            );
        }
        if let Some(cts) = instance_ref.description.change_trackers.as_ref() {
            let self_weak = instance_ref.self_weak().get().unwrap();
            let v = cts
                .1
                .iter()
                .enumerate()
                .map(|(idx, _)| {
                    let ct = ChangeTracker::default();
                    ct.init(
                        self_weak.clone(),
                        move |self_weak| {
                            let s = self_weak.upgrade().unwrap();
                            generativity::make_guard!(guard);
                            let compo_box = s.unerase(guard);
                            let instance_ref = compo_box.borrow_instance();
                            let nr = &s.0.description.change_trackers.as_ref().unwrap().1[idx].0;
                            eval::load_property(instance_ref, &nr.element(), nr.name()).unwrap()
                        },
                        move |self_weak, _| {
                            let s = self_weak.upgrade().unwrap();
                            generativity::make_guard!(guard);
                            let compo_box = s.unerase(guard);
                            let instance_ref = compo_box.borrow_instance();
                            let e = &s.0.description.change_trackers.as_ref().unwrap().1[idx].1;
                            eval::eval_expression(
                                e,
                                &mut eval::EvalLocalContext::from_component_instance(instance_ref),
                            );
                        },
                    );
                    ct
                })
                .collect::<Vec<_>>();
            cts.0
                .apply_pin(instance_ref.instance)
                .set(v)
                .unwrap_or_else(|_| panic!("run_setup_code called twice?"));
        }
        update_timers(instance_ref);
    }
}
impl<'id> From<ItemTreeBox<'id>> for ErasedItemTreeBox {
    fn from(inner: ItemTreeBox<'id>) -> Self {
        // Safety: Nothing access the component directly, we only access it through unerased where
        // the lifetime is unique again
        unsafe {
            ErasedItemTreeBox(core::mem::transmute::<ItemTreeBox<'id>, ItemTreeBox<'static>>(inner))
        }
    }
}

pub fn get_repeater_by_name<'a, 'id>(
    instance_ref: InstanceRef<'a, '_>,
    name: &str,
    guard: generativity::Guard<'id>,
) -> (std::pin::Pin<&'a Repeater<ErasedItemTreeBox>>, Rc<ItemTreeDescription<'id>>) {
    let rep_index = instance_ref.description.repeater_names[name];
    let rep_in_comp = instance_ref.description.repeater[rep_index].unerase(guard);
    (rep_in_comp.offset.apply_pin(instance_ref.instance), rep_in_comp.item_tree_to_repeat.clone())
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn ensure_instantiated(component: ItemTreeRefPin) -> bool {
    generativity::make_guard!(guard);
    // Safety: called through the vtable of our own ItemTreeDescription.
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };

    let mut changed = false;
    for (tree_index, node) in instance_ref.description.item_tree.iter().enumerate() {
        if !matches!(node, ItemTreeNode::Item { .. }) {
            continue;
        }
        let item_ref = component.as_ref().get_item_ref(tree_index as u32);
        if let Some(container) = i_slint_core::items::ItemRef::downcast_pin::<
            i_slint_core::items::ComponentContainer,
        >(item_ref)
        {
            changed |= container.ensure_updated();
        }
    }

    for rep_in_comp in &instance_ref.description.repeater {
        // Safety: we do not mix the repeater with a different component id.
        let rep_in_comp = unsafe { rep_in_comp.get_untagged() };
        let repeater = rep_in_comp.offset.apply_pin(instance_ref.instance);
        let init = || {
            let extra_data =
                instance_ref.description.extra_data_offset.apply(instance_ref.as_ref());
            instantiate(
                rep_in_comp.item_tree_to_repeat.clone(),
                instance_ref.self_weak().get().cloned(),
                None,
                None,
                extra_data.globals.get().unwrap().clone(),
            )
        };
        if let Some(lv) = &rep_in_comp
            .item_tree_to_repeat
            .original
            .parent_element
            .borrow()
            .upgrade()
            .unwrap()
            .borrow()
            .repeated
            .as_ref()
            .unwrap()
            .is_listview
        {
            let assume_property_logical_length =
                |prop| unsafe { Pin::new_unchecked(&*(prop as *const Property<LogicalLength>)) };
            let content_width = lv.content_width.as_ref().map(|content_width| {
                assume_property_logical_length(get_property_ptr(content_width, instance_ref))
            });
            let content_height = lv.content_height.as_ref().map(|content_height| {
                assume_property_logical_length(get_property_ptr(content_height, instance_ref))
            });
            changed |= repeater.ensure_updated_listview(
                init,
                content_width,
                content_height,
                assume_property_logical_length(get_property_ptr(&lv.content_y, instance_ref)),
                eval::load_property(
                    instance_ref,
                    &lv.listview_width.element(),
                    lv.listview_width.name(),
                )
                .unwrap()
                .try_into()
                .unwrap(),
                assume_property_logical_length(get_property_ptr(&lv.listview_height, instance_ref)),
            );
        } else {
            changed |= repeater.ensure_updated(init);
        }
    }
    changed
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn layout_info(component: ItemTreeRefPin, orientation: Orientation) -> LayoutInfo {
    generativity::make_guard!(guard);
    // This is fine since we can only be called with a component that with our vtable which is a ItemTreeDescription
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
    let orientation = crate::eval_layout::from_runtime(orientation);

    // Vtable entry (repeater cells, window auto-size). Pass the cross-axis size
    // to the root's parameterized layout-info function explicitly, avoiding a
    // cycle on `self.{w,h}`: for the vertical query the preferred width, so a
    // height-for-width Image sizes its height to that and not to infinity; for
    // the horizontal query `f32::MAX`, i.e. "don't wrap".
    let root = &instance_ref.description.original.root_element;
    let window_adapter = instance_ref.window_adapter();
    let cross_axis_constraint = match orientation {
        i_slint_compiler::layout::Orientation::Vertical => {
            root.borrow().layout_info_v_with_constraint.is_some().then(|| {
                crate::eval_layout::get_layout_info(
                    root,
                    instance_ref,
                    &window_adapter,
                    i_slint_compiler::layout::Orientation::Horizontal,
                )
                .preferred_bounded()
            })
        }
        i_slint_compiler::layout::Orientation::Horizontal => {
            root.borrow().layout_info_h_with_constraint.is_some().then_some(f32::MAX)
        }
    };
    let mut result = crate::eval_layout::get_layout_info_with_constraint(
        root,
        instance_ref,
        &window_adapter,
        orientation,
        cross_axis_constraint,
    );

    let constraints = instance_ref.description.original.root_constraints.borrow();
    if constraints.has_explicit_restrictions(orientation) {
        crate::eval_layout::fill_layout_info_constraints(
            &mut result,
            &constraints,
            orientation,
            &|nr: &NamedReference| {
                eval::load_property(instance_ref, &nr.element(), nr.name())
                    .unwrap()
                    .try_into()
                    .unwrap()
            },
        );
    }
    result
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
unsafe extern "C" fn get_item_ref(component: ItemTreeRefPin, index: u32) -> Pin<ItemRef> {
    let tree = get_item_tree(component);
    match &tree[index as usize] {
        ItemTreeNode::Item { item_array_index, .. } => unsafe {
            generativity::make_guard!(guard);
            let instance_ref = InstanceRef::from_pin_ref(component, guard);
            core::mem::transmute::<Pin<ItemRef>, Pin<ItemRef>>(
                instance_ref.description.item_array[*item_array_index as usize]
                    .apply_pin(instance_ref.instance),
            )
        },
        ItemTreeNode::DynamicTree { .. } => panic!("get_item_ref called on dynamic tree"),
    }
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn get_subtree_range(component: ItemTreeRefPin, index: u32) -> IndexRange {
    generativity::make_guard!(guard);
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
    if index as usize >= instance_ref.description.repeater.len() {
        let container_index = {
            let tree_node = &component.as_ref().get_item_tree()[index as usize];
            if let ItemTreeNode::DynamicTree { parent_index, .. } = tree_node {
                *parent_index
            } else {
                u32::MAX
            }
        };
        let container = component.as_ref().get_item_ref(container_index);
        let container = i_slint_core::items::ItemRef::downcast_pin::<
            i_slint_core::items::ComponentContainer,
        >(container)
        .unwrap();
        container.subtree_range()
    } else {
        generativity::make_guard!(guard);
        let rep_in_comp = instance_ref.description.repeater[index as usize].unerase(guard);

        let repeater = rep_in_comp.offset.apply_pin(instance_ref.instance);
        repeater.track_instance_changes();
        repeater.range().into()
    }
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn get_subtree(
    component: ItemTreeRefPin,
    index: u32,
    subtree_index: usize,
    result: &mut ItemTreeWeak,
) {
    generativity::make_guard!(guard);
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
    if index as usize >= instance_ref.description.repeater.len() {
        let container_index = {
            let tree_node = &component.as_ref().get_item_tree()[index as usize];
            if let ItemTreeNode::DynamicTree { parent_index, .. } = tree_node {
                *parent_index
            } else {
                u32::MAX
            }
        };
        let container = component.as_ref().get_item_ref(container_index);
        let container = i_slint_core::items::ItemRef::downcast_pin::<
            i_slint_core::items::ComponentContainer,
        >(container)
        .unwrap();
        if subtree_index == 0 {
            *result = container.subtree_component();
        }
    } else {
        generativity::make_guard!(guard);
        let rep_in_comp = instance_ref.description.repeater[index as usize].unerase(guard);

        let repeater = rep_in_comp.offset.apply(&instance_ref.instance);
        if let Some(instance_at) = repeater.instance_at(subtree_index) {
            *result = vtable::VRc::downgrade(&vtable::VRc::into_dyn(instance_at))
        }
    }
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn get_item_tree(component: ItemTreeRefPin) -> Slice<ItemTreeNode> {
    generativity::make_guard!(guard);
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
    let tree = instance_ref.description.item_tree.as_slice();
    unsafe { core::mem::transmute::<&[ItemTreeNode], &[ItemTreeNode]>(tree) }.into()
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn subtree_index(component: ItemTreeRefPin) -> usize {
    generativity::make_guard!(guard);
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
    if let Ok(value) = instance_ref.description.get_property(component, SPECIAL_PROPERTY_INDEX) {
        value.try_into().unwrap()
    } else {
        usize::MAX
    }
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
unsafe extern "C" fn parent_node(component: ItemTreeRefPin, result: &mut ItemWeak) {
    generativity::make_guard!(guard);
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };

    let component_and_index = {
        // Normal inner-compilation unit case:
        if let Some(parent_offset) = instance_ref.description.parent_item_tree_offset {
            let parent_item_index = instance_ref
                .description
                .original
                .parent_element
                .borrow()
                .upgrade()
                .and_then(|e| e.borrow().item_index.get().cloned())
                .unwrap_or(u32::MAX);
            let parent_component = parent_offset
                .apply(instance_ref.as_ref())
                .get()
                .and_then(|p| p.upgrade())
                .map(vtable::VRc::into_dyn);

            (parent_component, parent_item_index)
        } else if let Some((parent_component, parent_index)) = instance_ref
            .description
            .extra_data_offset
            .apply(instance_ref.as_ref())
            .embedding_position
            .get()
        {
            (parent_component.upgrade(), *parent_index)
        } else {
            (None, u32::MAX)
        }
    };

    if let (Some(component), index) = component_and_index {
        *result = ItemRc::new(component, index).downgrade();
    }
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
unsafe extern "C" fn embed_component(
    component: ItemTreeRefPin,
    parent_component: &ItemTreeWeak,
    parent_item_tree_index: u32,
) -> bool {
    generativity::make_guard!(guard);
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };

    if instance_ref.description.parent_item_tree_offset.is_some() {
        // We are not the root of the compilation unit tree... Can not embed this!
        return false;
    }

    {
        // sanity check parent:
        let prc = parent_component.upgrade().unwrap();
        let pref = vtable::VRc::borrow_pin(&prc);
        let it = pref.as_ref().get_item_tree();
        if !matches!(
            it.get(parent_item_tree_index as usize),
            Some(ItemTreeNode::DynamicTree { .. })
        ) {
            panic!("Trying to embed into a non-dynamic index in the parents item tree")
        }
    }

    let extra_data = instance_ref.description.extra_data_offset.apply(instance_ref.as_ref());
    extra_data.embedding_position.set((parent_component.clone(), parent_item_tree_index)).is_ok()
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn item_geometry(component: ItemTreeRefPin, item_index: u32) -> LogicalRect {
    generativity::make_guard!(guard);
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };

    let e = instance_ref.description.original_elements[item_index as usize].borrow();
    let g = e.geometry_props.as_ref().unwrap();

    let load_f32 = |nr: &NamedReference| -> f32 {
        crate::eval::load_property(instance_ref, &nr.element(), nr.name())
            .unwrap()
            .try_into()
            .unwrap()
    };

    LogicalRect {
        origin: (load_f32(&g.x), load_f32(&g.y)).into(),
        size: (load_f32(&g.width), load_f32(&g.height)).into(),
    }
}

// silence the warning despite `AccessibleRole` is a `#[non_exhaustive]` enum from another crate.
#[allow(improper_ctypes_definitions)]
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn accessible_role(component: ItemTreeRefPin, item_index: u32) -> AccessibleRole {
    generativity::make_guard!(guard);
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
    let nr = instance_ref.description.original_elements[item_index as usize]
        .borrow()
        .accessibility_props
        .0
        .get("accessible-role")
        .cloned();
    match nr {
        Some(nr) => crate::eval::load_property(instance_ref, &nr.element(), nr.name())
            .unwrap()
            .try_into()
            .unwrap(),
        None => AccessibleRole::default(),
    }
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn accessible_string_property(
    component: ItemTreeRefPin,
    item_index: u32,
    what: AccessibleStringProperty,
    result: &mut SharedString,
) -> bool {
    generativity::make_guard!(guard);
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
    let prop_name = format!("accessible-{what}");
    let nr = instance_ref.description.original_elements[item_index as usize]
        .borrow()
        .accessibility_props
        .0
        .get(&prop_name)
        .cloned();
    if let Some(nr) = nr {
        let value = crate::eval::load_property(instance_ref, &nr.element(), nr.name()).unwrap();
        match value {
            Value::String(s) => *result = s,
            Value::Bool(b) => *result = if b { "true" } else { "false" }.into(),
            Value::Number(x) => *result = x.to_string().into(),
            Value::EnumerationValue(_, v) => *result = v.into(),
            _ => unimplemented!("invalid type for accessible_string_property"),
        };
        true
    } else {
        false
    }
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn accessibility_action(
    component: ItemTreeRefPin,
    item_index: u32,
    action: &AccessibilityAction,
) {
    let perform = |prop_name, args: &[Value]| {
        generativity::make_guard!(guard);
        let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
        let nr = instance_ref.description.original_elements[item_index as usize]
            .borrow()
            .accessibility_props
            .0
            .get(prop_name)
            .cloned();
        if let Some(nr) = nr {
            let instance_ref = eval::ComponentInstance::InstanceRef(instance_ref);
            crate::eval::invoke_callback(&instance_ref, &nr.element(), nr.name(), args).unwrap();
        }
    };

    match action {
        AccessibilityAction::Default => perform("accessible-action-default", &[]),
        AccessibilityAction::Decrement => perform("accessible-action-decrement", &[]),
        AccessibilityAction::Increment => perform("accessible-action-increment", &[]),
        AccessibilityAction::Expand => perform("accessible-action-expand", &[]),
        AccessibilityAction::ReplaceSelectedText(_a) => {
            //perform("accessible-action-replace-selected-text", &[Value::String(a.clone())])
            i_slint_core::debug_log!(
                "AccessibilityAction::ReplaceSelectedText not implemented in interpreter's accessibility_action"
            );
        }
        AccessibilityAction::SetValue(a) => {
            perform("accessible-action-set-value", &[Value::String(a.clone())])
        }
        AccessibilityAction::SetSelection(anchor, focus) => perform(
            "accessible-action-set-selection",
            &[Value::Number(*anchor as f64), Value::Number(*focus as f64)],
        ),
    };
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn supported_accessibility_actions(
    component: ItemTreeRefPin,
    item_index: u32,
) -> SupportedAccessibilityAction {
    generativity::make_guard!(guard);
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
    instance_ref.description.original_elements[item_index as usize]
        .borrow()
        .accessibility_props
        .0
        .keys()
        .filter_map(|x| x.strip_prefix("accessible-action-"))
        .fold(SupportedAccessibilityAction::default(), |acc, value| {
            SupportedAccessibilityAction::from_name(&i_slint_compiler::generator::to_pascal_case(
                value,
            ))
            .unwrap_or_else(|| panic!("Not an accessible action: {value:?}"))
                | acc
        })
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn item_element_infos(
    component: ItemTreeRefPin,
    item_index: u32,
    result: &mut SharedString,
) -> bool {
    generativity::make_guard!(guard);
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
    *result = instance_ref.description.original_elements[item_index as usize]
        .borrow()
        .element_infos()
        .into();
    true
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C" fn window_adapter(
    component: ItemTreeRefPin,
    do_create: bool,
    result: &mut Option<WindowAdapterRc>,
) {
    generativity::make_guard!(guard);
    let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
    if do_create {
        *result = Some(instance_ref.window_adapter());
    } else {
        *result = instance_ref.maybe_window_adapter();
    }
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
unsafe extern "C" fn drop_in_place(component: vtable::VRefMut<ItemTreeVTable>) -> vtable::Layout {
    unsafe {
        let instance_ptr = component.as_ptr() as *mut Instance<'static>;
        let layout = (*instance_ptr).type_info().layout();
        dynamic_type::TypeInfo::drop_in_place(instance_ptr);
        layout.into()
    }
}

#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
unsafe extern "C" fn dealloc(_vtable: &ItemTreeVTable, ptr: *mut u8, layout: vtable::Layout) {
    unsafe { std::alloc::dealloc(ptr, layout.try_into().unwrap()) };
}

#[derive(Copy, Clone)]
pub struct InstanceRef<'a, 'id> {
    pub instance: Pin<&'a Instance<'id>>,
    pub description: &'a ItemTreeDescription<'id>,
}

impl<'a, 'id> InstanceRef<'a, 'id> {
    pub unsafe fn from_pin_ref(
        component: ItemTreeRefPin<'a>,
        _guard: generativity::Guard<'id>,
    ) -> Self {
        unsafe {
            Self {
                instance: Pin::new_unchecked(
                    &*(component.as_ref().as_ptr() as *const Instance<'id>),
                ),
                description: &*(Pin::into_inner_unchecked(component).get_vtable()
                    as *const ItemTreeVTable
                    as *const ItemTreeDescription<'id>),
            }
        }
    }

    pub fn as_ptr(&self) -> *const u8 {
        (&*self.instance.as_ref()) as *const Instance as *const u8
    }

    pub fn as_ref(&self) -> &Instance<'id> {
        &self.instance
    }

    /// Borrow this component as a `Pin<ItemTreeRef>`
    pub fn borrow(self) -> ItemTreeRefPin<'a> {
        unsafe {
            Pin::new_unchecked(vtable::VRef::from_raw(
                NonNull::from(&self.description.ct).cast(),
                NonNull::from(self.instance.get_ref()).cast(),
            ))
        }
    }

    pub fn self_weak(&self) -> &OnceCell<ErasedItemTreeBoxWeak> {
        let extra_data = self.description.extra_data_offset.apply(self.as_ref());
        &extra_data.self_weak
    }

    pub fn root_weak(&self) -> &ErasedItemTreeBoxWeak {
        self.description.root_offset.apply(self.as_ref()).get().unwrap()
    }

    pub fn window_adapter(&self) -> WindowAdapterRc {
        self.try_window_adapter().unwrap()
    }

    pub fn try_window_adapter(&self) -> Result<WindowAdapterRc, PlatformError> {
        self.root_weak().upgrade().unwrap().window_adapter_ref().cloned()
    }

    pub fn get_or_init_window_adapter_ref<'b, 'id2>(
        description: &'b ItemTreeDescription<'id2>,
        root_weak: ItemTreeWeak,
        do_create: bool,
        instance: &'b Instance<'id2>,
    ) -> Result<&'b WindowAdapterRc, PlatformError> {
        // We are the actual root: Generate and store a window_adapter if necessary
        description
            .extra_data_offset
            .apply(instance)
            .globals
            .get()
            .unwrap()
            .window_adapter()
            .unwrap()
            .get_or_try_init(|| {
                let mut parent_node = ItemWeak::default();
                if let Some(rc) = vtable::VWeak::upgrade(&root_weak) {
                    vtable::VRc::borrow_pin(&rc).as_ref().parent_node(&mut parent_node);
                }

                if let Some(parent) = parent_node.upgrade() {
                    // We are embedded: Get window adapter from our parent
                    let mut result = None;
                    vtable::VRc::borrow_pin(parent.item_tree())
                        .as_ref()
                        .window_adapter(do_create, &mut result);
                    result.ok_or(PlatformError::NoPlatform)
                } else if do_create {
                    let extra_data = description.extra_data_offset.apply(instance);
                    let window_adapter = // We are the root: Create a window adapter
                    i_slint_backend_selector::with_platform(|_b| {
                        _b.create_window_adapter()
                    })?;

                    let comp_rc = extra_data.self_weak.get().unwrap().upgrade().unwrap();
                    WindowInner::from_pub(window_adapter.window())
                        .set_component(&vtable::VRc::into_dyn(comp_rc));
                    Ok(window_adapter)
                } else {
                    Err(PlatformError::NoPlatform)
                }
            })
    }

    pub fn maybe_window_adapter(&self) -> Option<WindowAdapterRc> {
        let root_weak = vtable::VWeak::into_dyn(self.root_weak().clone());
        let root = self.root_weak().upgrade()?;
        generativity::make_guard!(guard);
        let comp = root.unerase(guard);
        Self::get_or_init_window_adapter_ref(
            &comp.description,
            root_weak,
            false,
            comp.instance.as_pin_ref().get_ref(),
        )
        .ok()
        .cloned()
    }

    pub fn access_window<R>(
        self,
        callback: impl FnOnce(&'_ i_slint_core::window::WindowInner) -> R,
    ) -> R {
        callback(WindowInner::from_pub(self.window_adapter().window()))
    }

    pub fn parent_instance<'id2>(
        &self,
        _guard: generativity::Guard<'id2>,
    ) -> Option<InstanceRef<'a, 'id2>> {
        // we need a 'static guard in order to be able to re-borrow with lifetime 'a.
        // Safety: This is the only 'static Id in scope.
        if let Some(parent_offset) = self.description.parent_item_tree_offset
            && let Some(parent) =
                parent_offset.apply(self.as_ref()).get().and_then(vtable::VWeak::upgrade)
        {
            let parent_instance = parent.unerase(_guard);
            // And also assume that the parent lives for at least 'a.  FIXME: this may not be sound
            let parent_instance = unsafe {
                std::mem::transmute::<InstanceRef<'_, 'id2>, InstanceRef<'a, 'id2>>(
                    parent_instance.borrow_instance(),
                )
            };
            return Some(parent_instance);
        }
        None
    }
}

/// Show the popup with a lazily evaluated location.
pub fn show_popup(
    element: ElementRc,
    instance: InstanceRef,
    popup: &object_tree::PopupWindow,
    pos_getter: impl Fn(InstanceRef<'_, '_>) -> LogicalPosition + 'static,
    close_policy: PopupClosePolicy,
    parent_comp: ErasedItemTreeBoxWeak,
    parent_window_adapter: WindowAdapterRc,
    parent_item: &ItemRc,
) {
    generativity::make_guard!(guard);

    // FIXME: we should compile once and keep the cached compiled component
    let compiled = generate_item_tree(
        &popup.component,
        None,
        parent_comp.upgrade().unwrap().0.description().popup_menu_description.clone(),
        false,
        guard,
    );

    let extra_data = instance.description.extra_data_offset.apply(instance.as_ref());
    // Use the newly created window adapter if we are able to create one. Otherwise use the parent's one.
    // Tooltips skip this to share the parent's adapter, ensuring they use the ChildWindow path
    // and renderer caches stay consistent.
    let window_kind = if popup.is_tooltip { WindowKind::ToolTip } else { WindowKind::Popup };
    let globals = if let Some(window_adapter) =
        WindowInner::from_pub(parent_window_adapter.window())
            .create_child_window_adapter(window_kind)
    {
        extra_data.globals.get().unwrap().clone_with_window_adapter(window_adapter)
    } else {
        extra_data.globals.get().unwrap().clone()
    };

    let popup_window_adapter = globals
        .window_adapter()
        .and_then(|window_adapter| window_adapter.get().cloned())
        .unwrap_or_else(|| parent_window_adapter.clone());

    // Keep a weak handle to the parent before `parent_comp` is moved into `instantiate`, so the
    // is-open setter (built below) can re-derive the parent instance when the popup closes.
    let parent_comp_weak = popup.is_open.is_some().then(|| parent_comp.clone());
    let inst = instantiate(
        compiled,
        Some(parent_comp),
        None,
        Some(&WindowOptions::UseExistingWindow(popup_window_adapter)),
        globals,
    );
    let inst_for_position = inst.clone();
    let access_position = Box::new(move || {
        generativity::make_guard!(guard);
        let compo_box = inst_for_position.unerase(guard);
        let instance_ref = compo_box.borrow_instance();
        pos_getter(instance_ref)
    });
    close_popup(element.clone(), instance, parent_window_adapter.clone());
    let window_kind = if popup.is_tooltip { WindowKind::ToolTip } else { WindowKind::Popup };
    // Keep the parent's `is-open` property in sync: `show_popup` invokes this with `true` now and with
    // `false` from every close path. Passing it directly into `show_popup` avoids an extra registration
    // call and a second popup lookup. Popups without `is-open` get a no-op setter.
    let is_open_setter: Box<dyn Fn(bool)> =
        if let (Some(is_open), Some(parent_comp_weak)) = (&popup.is_open, parent_comp_weak) {
            let is_open_element = is_open.element();
            let is_open_name = is_open.name().to_string();
            Box::new(move |value: bool| {
                if let Some(parent) = parent_comp_weak.upgrade() {
                    generativity::make_guard!(guard);
                    let compo_box = parent.unerase(guard);
                    let instance_ref = compo_box.borrow_instance();
                    let _ = crate::eval::store_property(
                        instance_ref,
                        &is_open_element,
                        &is_open_name,
                        Value::Bool(value),
                    );
                }
            })
        } else {
            Box::new(|_| {})
        };
    let popup_id = WindowInner::from_pub(parent_window_adapter.window()).show_popup(
        &vtable::VRc::into_dyn(inst.clone()),
        access_position,
        close_policy,
        parent_item,
        window_kind,
        is_open_setter,
    );
    instance.description.popup_ids.borrow_mut().insert(element.borrow().id.clone(), popup_id);
    inst.run_setup_code();
}

pub fn close_popup(
    element: ElementRc,
    instance: InstanceRef,
    parent_window_adapter: WindowAdapterRc,
) {
    if let Some(current_id) =
        instance.description.popup_ids.borrow_mut().remove(&element.borrow().id)
    {
        WindowInner::from_pub(parent_window_adapter.window()).close_popup(current_id);
    }
}

pub fn make_menu_item_tree(
    menu_item_tree: &Rc<object_tree::Component>,
    enclosing_component: &InstanceRef,
    condition: Option<&Expression>,
    visible: Option<&Expression>,
) -> vtable::VRc<i_slint_core::menus::MenuVTable, MenuFromItemTree> {
    generativity::make_guard!(guard);
    let mit_compiled = generate_item_tree(
        menu_item_tree,
        None,
        enclosing_component.description.popup_menu_description.clone(),
        false,
        guard,
    );
    let enclosing_component_weak = enclosing_component.self_weak().get().unwrap();
    let extra_data =
        enclosing_component.description.extra_data_offset.apply(enclosing_component.as_ref());
    let mit_inst = instantiate(
        mit_compiled.clone(),
        Some(enclosing_component_weak.clone()),
        None,
        None,
        extra_data.globals.get().unwrap().clone(),
    );
    mit_inst.run_setup_code();
    let item_tree = vtable::VRc::into_dyn(mit_inst);
    let condition = condition.map(|condition| {
        let binding =
            make_binding_eval_closure(condition.clone(), enclosing_component_weak.clone());
        move || binding().try_into().unwrap()
    });
    let visible = visible.map(|visible| {
        let binding = make_binding_eval_closure(visible.clone(), enclosing_component_weak.clone());
        move || binding().try_into().unwrap()
    });
    let menu = match (condition, visible) {
        (None, None) => MenuFromItemTree::new(item_tree),
        (None, Some(visible)) => {
            MenuFromItemTree::new_with_condition_and_visible(item_tree, || true, visible)
        }
        (Some(condition), None) => {
            MenuFromItemTree::new_with_condition_and_visible(item_tree, condition, || true)
        }
        (Some(condition), Some(visible)) => {
            MenuFromItemTree::new_with_condition_and_visible(item_tree, condition, visible)
        }
    };
    vtable::VRc::new(menu)
}

pub fn update_timers(instance: InstanceRef) {
    let ts = instance.description.original.timers.borrow();
    for (desc, offset) in ts.iter().zip(&instance.description.timers) {
        let timer = offset.apply(instance.as_ref());
        let running =
            eval::load_property(instance, &desc.running.element(), desc.running.name()).unwrap();
        if matches!(running, Value::Bool(true)) {
            let millis: i64 =
                eval::load_property(instance, &desc.interval.element(), desc.interval.name())
                    .unwrap()
                    .try_into()
                    .expect("interval must be a duration");
            if millis < 0 {
                timer.stop();
                continue;
            }
            let interval = core::time::Duration::from_millis(millis as _);
            if !timer.running() || interval != timer.interval() {
                let callback = desc.triggered.clone();
                let self_weak = instance.self_weak().get().unwrap().clone();
                timer.start(i_slint_core::timers::TimerMode::Repeated, interval, move || {
                    if let Some(instance) = self_weak.upgrade() {
                        generativity::make_guard!(guard);
                        let c = instance.unerase(guard);
                        let c = c.borrow_instance();
                        let inst = eval::ComponentInstance::InstanceRef(c);
                        eval::invoke_callback(&inst, &callback.element(), callback.name(), &[])
                            .unwrap();
                    }
                });
            }
        } else {
            timer.stop();
        }
    }
}

pub fn restart_timer(element: ElementWeak, instance: InstanceRef) {
    // The calling expression can be in a repeated or conditional child of the
    // component that declares the timer.
    let element_rc = element.upgrade().unwrap();
    generativity::make_guard!(guard);
    let instance = eval::enclosing_component_for_element(&element_rc, instance, guard);
    let timers = instance.description.original.timers.borrow();
    if let Some((_, offset)) = timers
        .iter()
        .zip(&instance.description.timers)
        .find(|(desc, _)| Weak::ptr_eq(&desc.element, &element))
    {
        let timer = offset.apply(instance.as_ref());
        timer.restart();
    }
}
