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

/*!
 This module contains the intermediate representation of the code in the form of an object tree
*/

// cSpell: ignore qualname

use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
use crate::expression_tree::{self, BindingExpression, Callable, Expression, Unit};
use crate::langtype::{
    BuiltinElement, BuiltinPropertyDefault, Enumeration, EnumerationValue, Function, NativeClass,
    Struct, StructName, Type,
};
use crate::langtype::{ElementType, PropertyLookupResult};
use crate::layout::{LayoutConstraints, Orientation};
use crate::namedreference::NamedReference;
use crate::parser::{SyntaxKind, SyntaxNode, syntax_nodes};
use crate::typeloader::{ImportKind, ImportedTypes, LibraryInfo};
use crate::typeregister::TypeRegister;
use crate::{parser, reject_experimental_feature};
use itertools::Either;
use smol_str::{SmolStr, ToSmolStr, format_smolstr};
use std::cell::{Cell, OnceCell, Ref, RefCell, RefMut};
use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt::Display;
use std::path::PathBuf;
use std::rc::{Rc, Weak};
use std::sync::Arc;

mod interfaces;

macro_rules! unwrap_or_continue {
    ($e:expr ; $diag:expr) => {
        match $e {
            Some(x) => x,
            None => {
                debug_assert!($diag.has_errors()); // error should have been reported at parsing time
                continue;
            }
        }
    };
}

/// The full document (a complete file)
#[derive(Default)]
pub struct Document {
    pub node: Option<syntax_nodes::Document>,
    pub inner_components: Vec<Rc<Component>>,
    pub inner_types: Vec<Type>,
    pub local_registry: TypeRegister,
    /// A list of paths to .ttf/.ttc files that are supposed to be registered on
    /// startup for custom font use.
    pub custom_fonts: Vec<(SmolStr, crate::parser::SyntaxToken)>,
    pub exports: Exports,
    pub imports: Vec<ImportedTypes>,
    pub library_exports: HashMap<String, LibraryInfo>,

    /// Resources to embed in the generated code.
    ///
    /// The [`crate::embedded_resources::EmbeddedResourcesIdx`] is the identifier used by code generators.
    /// Each entry's `path` is the absolute path on disk, or `None` for in-memory data URI payloads.
    pub embedded_file_resources: RefCell<
        typed_index_collections::TiVec<
            crate::embedded_resources::EmbeddedResourcesIdx,
            crate::embedded_resources::EmbeddedResources,
        >,
    >,

    #[cfg(feature = "bundle-translations")]
    pub translation_builder: Option<crate::translations::TranslationsBuilder>,

    /// The list of used extra types used recursively.
    pub used_types: RefCell<UsedSubTypes>,

    /// The popup_menu_impl
    pub popup_menu_impl: Option<Rc<Component>>,
}

impl Document {
    pub fn from_node(
        node: syntax_nodes::Document,
        imports: Vec<ImportedTypes>,
        reexports: Exports,
        diag: &mut BuildDiagnostics,
        parent_registry: &Rc<RefCell<TypeRegister>>,
        ignore_missing_font_files: bool,
        symbol_counters: &Rc<crate::symbol_counters::SymbolCounters>,
    ) -> Self {
        debug_assert_eq!(node.kind(), SyntaxKind::Document);

        let mut local_registry = TypeRegister::new(parent_registry);
        let mut inner_components = Vec::new();
        let mut inner_types = Vec::new();

        // Named imports are part of the subset, the other two forms aren't.
        // The path rules are enforced in the type loader, which is where a
        // path is resolved.
        #[cfg(feature = "slint-sc")]
        for import in &imports {
            match import.import_kind {
                ImportKind::ImportList(_) => {}
                ImportKind::FileImport => {
                    diag.slint_sc_error("File imports are", &import.import_uri_token)
                }
                ImportKind::ModuleReexport(_) => {
                    diag.slint_sc_error("Re-exports are", &import.import_uri_token)
                }
            }
        }

        let mut process_component =
            |n: syntax_nodes::Component,
             diag: &mut BuildDiagnostics,
             local_registry: &mut TypeRegister| {
                let compo = Component::from_node(n, diag, local_registry);
                if !local_registry.add(compo.clone()) {
                    diag.push_warning(format!("Component '{}' is replacing a previously defined component with the same name", compo.id), &compo.node.clone().unwrap().DeclaredIdentifier());
                }
                inner_components.push(compo);
            };
        let process_struct = |n: syntax_nodes::StructDeclaration,
                              diag: &mut BuildDiagnostics,
                              local_registry: &mut TypeRegister,
                              inner_types: &mut Vec<Type>| {
            let ty = type_struct_from_node(
                n.ObjectType(),
                diag,
                local_registry,
                parser::identifier_text(&n.DeclaredIdentifier()),
                Some(symbol_counters),
            );
            assert!(matches!(ty, Type::Struct(_)));
            if !local_registry.insert_type(ty.clone()) {
                diag.push_warning(
                    format!(
                        "Struct '{ty}' is replacing a previously defined type with the same name"
                    ),
                    &n.DeclaredIdentifier(),
                );
            }
            inner_types.push(ty);
        };
        let process_enum = |n: syntax_nodes::EnumDeclaration,
                            diag: &mut BuildDiagnostics,
                            local_registry: &mut TypeRegister,
                            inner_types: &mut Vec<Type>| {
            let Some(name) = parser::identifier_text(&n.DeclaredIdentifier()) else {
                assert!(diag.has_errors());
                return;
            };
            let mut existing_names = HashSet::new();
            let values = n
                .EnumValue()
                .filter_map(|v| {
                    let value = parser::identifier_text(&v)?;
                    if value == name {
                        diag.push_error(
                            format!("Enum '{value}' can't have a value with the same name"),
                            &v,
                        );
                        None
                    } else if !existing_names.insert(crate::generator::to_pascal_case(&value)) {
                        diag.push_error(format!("Duplicated enum value '{value}'"), &v);
                        None
                    } else {
                        Some(value)
                    }
                })
                .collect();
            let en = Enumeration {
                name: name.clone(),
                values,
                default_value: 0,
                node: Some(crate::langtype::DeclNode::new(&n)),
                rust_attributes: n
                    .AtRustAttr()
                    .map(|a| SmolStr::from(a.text().to_string()))
                    .collect(),
            };
            if en.values.is_empty() {
                diag.push_error("Enums must have at least one value".into(), &n);
            }

            let ty = Type::Enumeration(Arc::new(en));
            if !local_registry.insert_type_with_name(ty.clone(), name.clone()) {
                diag.push_warning(
                    format!(
                        "Enum '{name}' is replacing a previously defined type with the same name"
                    ),
                    &n.DeclaredIdentifier(),
                );
            }
            inner_types.push(ty);
        };

        for n in node.children() {
            match n.kind() {
                SyntaxKind::Component => {
                    process_component(n.into(), diag, &mut local_registry);
                }
                SyntaxKind::StructDeclaration => {
                    process_struct(n.into(), diag, &mut local_registry, &mut inner_types)
                }
                SyntaxKind::EnumDeclaration => {
                    process_enum(n.into(), diag, &mut local_registry, &mut inner_types)
                }
                SyntaxKind::ExportsList => {
                    for n in n.children() {
                        match n.kind() {
                            SyntaxKind::Component => {
                                process_component(n.into(), diag, &mut local_registry)
                            }
                            SyntaxKind::StructDeclaration => process_struct(
                                n.into(),
                                diag,
                                &mut local_registry,
                                &mut inner_types,
                            ),
                            SyntaxKind::EnumDeclaration => {
                                process_enum(n.into(), diag, &mut local_registry, &mut inner_types)
                            }
                            _ => {}
                        }
                    }
                }
                _ => {}
            };
        }
        let mut exports = Exports::from_node(&node, &inner_components, &local_registry, diag);
        exports.add_reexports(reexports, diag);

        let custom_fonts = imports
            .iter()
            .filter(|import| matches!(import.import_kind, ImportKind::FileImport))
            .filter_map(|import| {
                if crate::pathutils::is_font_file(&import.file) {
                    let token_path = import.import_uri_token.source_file.path();
                    let import_file_path = PathBuf::from(import.file.clone());
                    let import_file_path = crate::pathutils::join(token_path, &import_file_path)
                        .unwrap_or(import_file_path);

                    // Assume remote urls are valid, we need to load them at run-time (which we currently don't). For
                    // local paths we should try to verify the existence and let the developer know ASAP.
                    // When the resource URL mapper is set (e.g. remote viewer), fonts are
                    // delivered out-of-band; skip the local existence check.
                    if ignore_missing_font_files
                        || crate::pathutils::is_url(&import_file_path)
                        || crate::fileaccess::load_file(std::path::Path::new(&import_file_path))
                            .is_some()
                    {
                        Some((import_file_path.to_string_lossy().into(), import.import_uri_token.clone()))
                    } else {
                        diag.push_error(
                            format!("File \"{}\" not found", import.file),
                            &import.import_uri_token,
                        );
                        None
                    }
                } else if import.file.ends_with(".slint") {
                    diag.push_error("Import names are missing. Please specify which types you would like to import".into(), &import.import_uri_token.parent());
                    None
                } else {
                    diag.push_error(
                        format!("Unsupported foreign import \"{}\"", import.file),
                        &import.import_uri_token,
                    );
                    None
                }
            })
            .collect();

        for local_compo in &inner_components {
            if exports
                .components_or_types
                .iter()
                .filter_map(|(_, exported_compo_or_type)| exported_compo_or_type.as_ref().left())
                .any(|exported_compo| Rc::ptr_eq(exported_compo, local_compo))
            {
                continue;
            }
            // Don't warn about these for now - detecting their use can only be done after the resolve_expressions
            // pass.
            if local_compo.is_global() {
                continue;
            }
            if !local_compo.used.get() {
                diag.push_warning(
                    "Component is neither used nor exported".into(),
                    &local_compo.node.as_ref().map(|n| n.to_source_location()),
                )
            }
        }

        Document {
            node: Some(node),
            inner_components,
            inner_types,
            local_registry,
            custom_fonts,
            imports,
            exports,
            library_exports: Default::default(),
            embedded_file_resources: Default::default(),
            #[cfg(feature = "bundle-translations")]
            translation_builder: None,
            used_types: Default::default(),
            popup_menu_impl: None,
        }
    }

    pub fn exported_roots(&self) -> impl DoubleEndedIterator<Item = Rc<Component>> + '_ {
        self.exports
            .iter()
            .filter_map(|e| e.1.as_ref().left())
            .filter(|c| !c.is_global() && !c.is_interface())
            .cloned()
    }

    /// This is the component that is going to be instantiated by the interpreter
    pub fn last_exported_component(&self) -> Option<Rc<Component>> {
        self.exports
            .iter()
            .filter_map(|e| Some((&e.0.name_ident, e.1.as_ref().left()?)))
            .filter(|(_, c)| !c.is_global())
            .max_by_key(|(n, _)| n.text_range().end())
            .map(|(_, c)| c.clone())
    }

    /// visit all root and used component (including globals)
    pub fn visit_all_used_components(&self, mut v: impl FnMut(&Rc<Component>)) {
        let used_types = self.used_types.borrow();
        for c in &used_types.sub_components {
            v(c);
        }
        for c in self.exported_roots() {
            v(&c);
        }
        for c in &used_types.globals {
            v(c);
        }
        if let Some(c) = &self.popup_menu_impl {
            v(c);
        }
    }
}

#[derive(Debug, Clone)]
pub struct PopupWindow {
    pub component: Rc<Component>,
    pub x: NamedReference,
    pub y: NamedReference,
    pub close_policy: EnumerationValue,
    pub parent_element: ElementRc,
    pub is_tooltip: bool,
    /// A reference to a synthesized property on the *parent* component that the runtime keeps in sync
    /// with the popup's visibility (`true` while shown, `false` once closed). This is `Some` only when
    /// the parent reads the PopupWindow's `is-open` property; see the `lower_popups` pass.
    pub is_open: Option<NamedReference>,
}

#[derive(Debug, Clone)]
pub struct Timer {
    pub interval: NamedReference,
    pub triggered: NamedReference,
    pub running: NamedReference,
    pub element: ElementWeak,
}

/// Key used for the default slot's insertion point and slot-target maps.
/// Not a valid Slint identifier, so it can never collide with a user-declared slot name.
pub const DEFAULT_SLOT_NAME: &str = "@children";

pub fn slot_error_subject(name: &str) -> String {
    if name == DEFAULT_SLOT_NAME {
        "The @children placeholder".into()
    } else {
        format!("The slot '{name}'")
    }
}

#[derive(Clone, Debug)]
pub enum ChildInsertionPointNode {
    DefaultChildrenPlaceHolder(SyntaxNode),
    ChildrenPlaceHolder(syntax_nodes::ChildrenPlaceholder),
    SlotPlaceholder(syntax_nodes::SubElement),
    SlotForwarding(syntax_nodes::Expression),
}

impl ChildInsertionPointNode {
    pub fn syntax_node(&self) -> &SyntaxNode {
        match self {
            Self::DefaultChildrenPlaceHolder(node) => node,
            Self::ChildrenPlaceHolder(node) => node,
            Self::SlotPlaceholder(node) => node,
            Self::SlotForwarding(node) => node,
        }
    }
}

impl Spanned for ChildInsertionPointNode {
    fn span(&self) -> crate::diagnostics::Span {
        self.syntax_node().span()
    }

    fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
        self.syntax_node().source_file()
    }
}

#[derive(Clone, Debug)]
pub struct ChildrenInsertionPoint {
    pub parent: ElementRc,
    pub insertion_index: usize,
    pub node: ChildInsertionPointNode,
}

#[derive(Clone, Debug)]
pub struct DeclaredSlot {
    pub name: SmolStr,
    pub name_node: syntax_nodes::DeclaredIdentifier,
    has_rejected_placeholder: bool,
}

#[derive(Clone, Debug)]
pub struct SlotForwarding {
    pub target: SmolStr,
    pub source: SmolStr,
    pub expression_node: syntax_nodes::Expression,
}

/// Used sub types for a root component
#[derive(Debug, Default)]
pub struct UsedSubTypes {
    /// All the globals used by the component and its children.
    pub globals: Vec<Rc<Component>>,
    /// All the structs and enums used by the component and its children.
    pub structs_and_enums: Vec<Type>,
    /// All the sub components use by this components and its children,
    /// and the amount of time it is used
    pub sub_components: Vec<Rc<Component>>,
    /// All types, structs, enums, that originates from an
    /// external library
    pub library_types_imports: Vec<(SmolStr, LibraryInfo)>,
    /// All global components that originates from an
    /// external library
    pub library_global_imports: Vec<(SmolStr, LibraryInfo)>,
}

#[derive(Debug, Default, Clone)]
pub struct InitCode {
    // Code from init callbacks collected from elements
    pub constructor_code: Vec<Expression>,
    /// Code to set the initial focus via forward-focus on the Window
    pub focus_setting_code: Vec<Expression>,
    /// Code to register embedded fonts.
    pub font_registration_code: Vec<Expression>,

    /// Code inserted from inlined components, ordered by offset of the place where it was inlined from. This way
    /// we can preserve the order across multiple inlining passes.
    pub inlined_init_code: BTreeMap<usize, Expression>,
}

impl InitCode {
    pub fn iter(&self) -> impl Iterator<Item = &Expression> {
        self.font_registration_code.iter().chain(self.iter_without_font_registration())
    }
    /// The init code without the font registration, which has to run before the property init.
    pub fn iter_without_font_registration(&self) -> impl Iterator<Item = &Expression> {
        self.focus_setting_code
            .iter()
            .chain(self.constructor_code.iter())
            .chain(self.inlined_init_code.values())
    }
    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Expression> {
        self.font_registration_code
            .iter_mut()
            .chain(self.focus_setting_code.iter_mut())
            .chain(self.constructor_code.iter_mut())
            .chain(self.inlined_init_code.values_mut())
    }
}

/// A component is a type in the language which can be instantiated,
/// Or is materialized for repeated expression.
#[derive(Default, Debug)]
pub struct Component {
    pub node: Option<syntax_nodes::Component>,
    pub id: SmolStr,
    pub root_element: ElementRc,

    /// The parent element within the parent component if this component represents a repeated element
    pub parent_element: RefCell<ElementWeak>,

    /// List of elements that are not attached to the root anymore because they have been
    /// optimized away, but their properties may still be in use
    pub optimized_elements: RefCell<Vec<ElementRc>>,

    /// The layout constraints of the root item
    pub root_constraints: RefCell<LayoutConstraints>,

    /// When creating this component and inserting "children", append them to the children of
    /// the element pointer to by this field.
    pub child_insertion_points: RefCell<BTreeMap<String, ChildrenInsertionPoint>>,

    /// Slots declared in this component, in source order.
    pub declared_slots: RefCell<Vec<DeclaredSlot>>,

    pub init_code: RefCell<InitCode>,

    pub popup_windows: RefCell<Vec<PopupWindow>>,
    pub timers: RefCell<Vec<Timer>>,
    pub menu_item_tree: RefCell<Vec<Rc<Component>>>,

    /// This component actually inherits PopupWindow (although that has been changed to a Window by the lower_popups pass)
    pub inherits_popup_window: Cell<bool>,

    /// The names under which this component should be accessible
    /// if it is a global singleton and exported.
    pub exported_global_names: RefCell<Vec<ExportedName>>,

    /// True if this component is used as a sub-component by at least one other component.
    pub used: Cell<bool>,

    /// The list of properties (name and type) declared as private in the component.
    /// This is used to issue better error in the generated code if the property is used.
    pub private_properties: RefCell<Vec<(SmolStr, Type)>>,

    /// True if this component is imported from an external library.
    pub from_library: Cell<bool>,
}

impl Component {
    pub fn from_node(
        node: syntax_nodes::Component,
        diag: &mut BuildDiagnostics,
        tr: &TypeRegister,
    ) -> Rc<Self> {
        let mut child_insertion_points = BTreeMap::new();
        let mut declared_slots = Vec::new();
        let is_legacy_syntax = node.child_token(SyntaxKind::ColonEqual).is_some();
        let c = Component {
            node: Some(node.clone()),
            id: parser::identifier_text(&node.DeclaredIdentifier()).unwrap_or_default(),
            root_element: Element::from_node(
                node.Element(),
                "root".into(),
                match node.child_text(SyntaxKind::Identifier) {
                    Some(t) if t == "global" => {
                        #[cfg(feature = "slint-sc")]
                        diag.slint_sc_error("Globals are", &node.DeclaredIdentifier());
                        ElementType::Global
                    }
                    Some(t) if t == "interface" => {
                        if reject_experimental_feature(diag, tr, "interface", &node) {
                            ElementType::Error
                        } else {
                            ElementType::Interface
                        }
                    }
                    _ => ElementType::Error,
                },
                &mut child_insertion_points,
                &mut declared_slots,
                is_legacy_syntax,
                diag,
                tr,
            ),
            child_insertion_points: RefCell::new(child_insertion_points),
            declared_slots: RefCell::new(declared_slots),
            ..Default::default()
        };
        c.check_slot_validity(diag);
        let c = Rc::new(c);
        // x and y on a Window are meaningless
        if c.root_element
            .borrow()
            .builtin_type()
            .is_some_and(|b| matches!(b.name.as_str(), "Window" | "Dialog"))
        {
            for prop in ["x", "y"] {
                if let Some(b) = c.root_element.borrow().binding_cell_including_synthetic(prop) {
                    #[cfg(feature = "slint-sc")]
                    if diag.slint_sc {
                        diag.slint_sc_error(&format!("The property '{prop}' is"), &*b.borrow());
                        continue;
                    }
                    diag.push_warning(
                        format!(
                            "Setting '{prop}' on a Window is deprecated, it doesn't affect the position of the window"
                        ),
                        &*b.borrow(),
                    );
                }
            }
        }
        let weak = Rc::downgrade(&c);
        recurse_elem(&c.root_element, &(), &mut |e, _| {
            e.borrow_mut().enclosing_component = weak.clone();
            if let Some(qualified_id) =
                e.borrow_mut().debug.first_mut().and_then(|x| x.qualified_id.as_mut())
            {
                *qualified_id = format_smolstr!("{}::{}", c.id, qualified_id);
            }
        });
        c
    }

    fn check_slot_validity(&self, diagnostics: &mut BuildDiagnostics) {
        if !diagnostics.enable_experimental {
            return;
        }
        if self.is_global() || self.is_interface() {
            return;
        }
        let mut declared_slot_nodes = BTreeMap::<SmolStr, syntax_nodes::DeclaredIdentifier>::new();
        for slot in self.declared_slots.borrow().iter() {
            if slot.name == "children" {
                diagnostics.push_error(
                    format!(
                        "The name '{}' is reserved for the default slot. Use @children instead",
                        slot.name
                    ),
                    &slot.name_node,
                );
                continue;
            }
            if declared_slot_nodes.insert(slot.name.clone(), slot.name_node.clone()).is_some() {
                diagnostics.push_error(
                    format!("Duplicate slot declaration '{}'", slot.name),
                    &slot.name_node,
                );
            }
        }
        for (name, cip) in self.child_insertion_points.borrow().iter() {
            if name == DEFAULT_SLOT_NAME {
                continue;
            }
            if !declared_slot_nodes.contains_key(name.as_str()) {
                diagnostics
                    .push_error(format!("The slot '{name}' is used but not declared"), &cip.node);
            }
        }
        for (name, node) in declared_slot_nodes.iter() {
            let has_rejected_placeholder = self
                .declared_slots
                .borrow()
                .iter()
                .any(|slot| slot.has_rejected_placeholder && &slot.name == name);
            if !self.child_insertion_points.borrow().contains_key(name.as_str())
                && !has_rejected_placeholder
            {
                diagnostics.push_error(format!("The slot '{name}' is declared but not used"), node);
            }
        }
    }

    /// This component is a global component introduced with the "global" keyword
    pub fn is_global(&self) -> bool {
        match &self.root_element.borrow().base_type {
            ElementType::Global => true,
            ElementType::Builtin(c) => c.is_global,
            _ => false,
        }
    }

    /// This is an interface introduced with the "interface" keyword
    pub fn is_interface(&self) -> bool {
        matches!(&self.root_element.borrow().base_type, ElementType::Interface)
    }

    /// True if this component's root resolves to the `SystemTrayIcon` native
    /// class. Uses `native_class()` rather than `builtin_type()` so the check
    /// still matches once the root has been resolved to `Native(SystemTrayIcon)`
    /// after `resolve_native_classes`.
    pub fn inherits_system_tray_icon(&self) -> bool {
        self.root_element
            .borrow()
            .native_class()
            .is_some_and(|n| n.class_name.as_str() == "SystemTrayIcon")
    }

    /// Returns the names of aliases to global singletons, exactly as
    /// specified in the .slint markup (not normalized).
    pub fn global_aliases(&self) -> Vec<SmolStr> {
        self.exported_global_names
            .borrow()
            .iter()
            .filter(|name| name.as_str() != self.root_element.borrow().id)
            .map(|name| name.original_name())
            .collect()
    }

    // Number of repeaters in this component, including sub-components
    pub fn repeater_count(&self) -> u32 {
        let mut count = 0;
        recurse_elem(&self.root_element, &(), &mut |element, _| {
            let element = element.borrow();
            if let Some(sub_component) = element.sub_component() {
                count += sub_component.repeater_count();
            } else if element.repeated.is_some() {
                count += 1;
            }
        });
        count
    }

    /// Convenience accessor to get the parent element if this component is a repeated component, or None otherwise.
    ///
    /// # Panics
    ///
    /// Panics if the Self::parent_element member is currently mutably borrowed
    pub fn parent_element(&self) -> Option<ElementRc> {
        self.parent_element.borrow().upgrade()
    }
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
pub enum PropertyVisibility {
    #[default]
    Private,
    Input,
    Output,
    InOut,
    /// for builtin properties that must be known at compile time and cannot be changed at runtime
    Constexpr,
    /// For builtin properties that are meant to just be bindings but cannot be read or written
    /// (eg, Path's `commands`)
    Fake,
    /// For functions, not properties
    Public,
    Protected,
}

impl Display for PropertyVisibility {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PropertyVisibility::Private => f.write_str("private"),
            PropertyVisibility::Input => f.write_str("in"),
            PropertyVisibility::Output => f.write_str("out"),
            PropertyVisibility::InOut => f.write_str("in-out"),
            PropertyVisibility::Constexpr => f.write_str("constexpr"),
            PropertyVisibility::Public => f.write_str("public"),
            PropertyVisibility::Protected => f.write_str("protected"),
            PropertyVisibility::Fake => f.write_str("fake"),
        }
    }
}

#[derive(Clone, Debug, Default)]
pub struct PropertyDeclaration {
    pub property_type: Type,
    pub node: Option<SyntaxNode>,
    /// Tells if getter and setter will be added to expose in the native language API
    pub expose_in_public_api: bool,
    /// Public API property exposed as an alias: it shouldn't be generated but instead forward to the alias.
    pub is_alias: Option<NamedReference>,
    pub visibility: PropertyVisibility,
    /// For function or callback: whether it is declared as `pure` (None for private function for which this has to be deduced)
    pub pure: Option<bool>,
    /// Whether the declaration shadows a builtin element member of the same name
    /// (diagnosed by the check_builtin_shadowing pass)
    pub shadows_builtin: bool,
    /// Some if the property was declared with `@deprecated`. The string is the hint shown after
    /// "The property 'xxx' has been deprecated." in the warning: either derived from the two-way
    /// binding target, or the custom message given as argument to `@deprecated("...")`.
    pub deprecated: Option<SmolStr>,
}

impl PropertyDeclaration {
    // For diagnostics: return a node pointing to the type
    pub fn type_node(&self) -> Option<SyntaxNode> {
        let node = self.node.as_ref()?;
        if let Some(x) = syntax_nodes::PropertyDeclaration::new(node.clone()) {
            Some(x.Type().map_or_else(|| x.into(), |x| x.into()))
        } else {
            node.clone().into()
        }
    }

    /// True when declared `@deprecated` without a custom message, so the hint in
    /// [`Self::deprecated`] is derived from the two-way binding target.
    pub fn has_derived_deprecation(&self) -> bool {
        self.deprecated.is_some()
            && self
                .node
                .as_ref()
                .and_then(|n| syntax_nodes::PropertyDeclaration::new(n.clone()))
                .and_then(|p| p.PropertyDeprecation())
                .is_some_and(|d| d.child_token(SyntaxKind::StringLiteral).is_none())
    }
}

impl From<Type> for PropertyDeclaration {
    fn from(ty: Type) -> Self {
        PropertyDeclaration { property_type: ty, ..Self::default() }
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TransitionDirection {
    In,
    Out,
    InOut,
}

#[derive(Debug, Clone)]
pub struct TransitionPropertyAnimation {
    /// The state id as computed in lower_state
    pub state_id: i32,
    /// The direction of the transition
    pub direction: TransitionDirection,
    /// The content of the `animation` object
    pub animation: ElementRc,
}

impl TransitionPropertyAnimation {
    /// Return an expression which returns a boolean which is true if the transition is active.
    /// The state argument is an expression referencing the state property of type StateInfo
    pub fn condition(&self, state: Expression) -> Expression {
        match self.direction {
            TransitionDirection::In => Expression::BinaryExpression {
                lhs: Box::new(Expression::StructFieldAccess {
                    base: Box::new(state),
                    name: "current-state".into(),
                }),
                rhs: Box::new(Expression::NumberLiteral(self.state_id as _, Unit::None)),
                op: '=',
            },
            TransitionDirection::Out => Expression::BinaryExpression {
                lhs: Box::new(Expression::StructFieldAccess {
                    base: Box::new(state),
                    name: "previous-state".into(),
                }),
                rhs: Box::new(Expression::NumberLiteral(self.state_id as _, Unit::None)),
                op: '=',
            },
            TransitionDirection::InOut => Expression::BinaryExpression {
                lhs: Box::new(Expression::BinaryExpression {
                    lhs: Box::new(Expression::StructFieldAccess {
                        base: Box::new(state.clone()),
                        name: "current-state".into(),
                    }),
                    rhs: Box::new(Expression::NumberLiteral(self.state_id as _, Unit::None)),
                    op: '=',
                }),
                rhs: Box::new(Expression::BinaryExpression {
                    lhs: Box::new(Expression::StructFieldAccess {
                        base: Box::new(state),
                        name: "previous-state".into(),
                    }),
                    rhs: Box::new(Expression::NumberLiteral(self.state_id as _, Unit::None)),
                    op: '=',
                }),
                op: '|',
            },
        }
    }
}

#[derive(Debug)]
pub enum PropertyAnimation {
    Static(ElementRc),
    Transition { state_ref: Expression, animations: Vec<TransitionPropertyAnimation> },
}

impl Clone for PropertyAnimation {
    fn clone(&self) -> Self {
        fn deep_clone(e: &ElementRc) -> ElementRc {
            let e = e.borrow();
            debug_assert!(e.children.is_empty());
            debug_assert!(e.property_declarations.is_empty());
            debug_assert!(e.states.is_empty() && e.transitions.is_empty());
            Rc::new(RefCell::new(Element {
                id: e.id.clone(),
                base_type: e.base_type.clone(),
                bindings: e.bindings.clone(),
                property_analysis: e.property_analysis.clone(),
                enclosing_component: e.enclosing_component.clone(),
                repeated: None,
                debug: e.debug.clone(),
                ..Default::default()
            }))
        }
        match self {
            PropertyAnimation::Static(e) => PropertyAnimation::Static(deep_clone(e)),
            PropertyAnimation::Transition { state_ref, animations } => {
                PropertyAnimation::Transition {
                    state_ref: state_ref.clone(),
                    animations: animations
                        .iter()
                        .map(|t| TransitionPropertyAnimation {
                            state_id: t.state_id,
                            direction: t.direction,
                            animation: deep_clone(&t.animation),
                        })
                        .collect(),
                }
            }
        }
    }
}

/// Map the accessibility property (eg "accessible-role", "accessible-label") to its named reference
#[derive(Default, Clone)]
pub struct AccessibilityProps(pub BTreeMap<String, NamedReference>);

#[derive(Clone, Debug)]
pub struct GeometryProps {
    pub x: NamedReference,
    pub y: NamedReference,
    pub width: NamedReference,
    pub height: NamedReference,
}

impl GeometryProps {
    pub fn new(element: &ElementRc) -> Self {
        Self {
            x: NamedReference::new(element, SmolStr::new_static("x")),
            y: NamedReference::new(element, SmolStr::new_static("y")),
            width: NamedReference::new(element, SmolStr::new_static("width")),
            height: NamedReference::new(element, SmolStr::new_static("height")),
        }
    }
}

pub type BindingsMap = BTreeMap<SmolStr, RefCell<BindingExpression>>;

/// A sealed wrapper around an element's binding map.
///
/// The inner map is private to the `object_tree` module, so other modules cannot read or mutate
/// it in a hook-unaware way (treating a synthetic debug hook as a real binding). All access from
/// outside goes through the hook-aware accessors on [`Element`]. The field itself can stay public
/// — `Element` struct literals keep compiling — because the seal is on this inner map.
#[derive(Clone, Default)]
pub struct Bindings(BindingsMap);

impl std::iter::FromIterator<(SmolStr, RefCell<BindingExpression>)> for Bindings {
    fn from_iter<T: IntoIterator<Item = (SmolStr, RefCell<BindingExpression>)>>(iter: T) -> Self {
        Bindings(iter.into_iter().collect())
    }
}

impl From<BindingsMap> for Bindings {
    fn from(map: BindingsMap) -> Self {
        Bindings(map)
    }
}

impl Bindings {
    /// The raw binding cell for `name`, including a synthetic debug hook.
    ///
    /// The counterpart of [`Element::binding_cell_including_synthetic`], for code that holds a
    /// `&Bindings` (e.g. an animation element's bindings) rather than a whole `Element`.
    pub fn binding_cell_including_synthetic(
        &self,
        name: &str,
    ) -> Option<&RefCell<BindingExpression>> {
        self.0.get(name)
    }
}

#[derive(Clone, Debug)]
pub struct ElementDebugInfo {
    // The id qualified with the enclosing component name. Given `foo := Bar {}` this is `EnclosingComponent::foo`
    pub qualified_id: Option<SmolStr>,
    pub type_name: String,
    // Hold an id for each element that is unique during this build, based on the source file and
    // the offset of the `LBrace` token.
    //
    // This helps to cross-reference the element in the different build stages the LSP has to deal with.
    pub element_hash: u64,
    pub node: syntax_nodes::Element,
    // Field to indicate whether this element was a layout that had
    // been lowered into a rectangle in the lower_layouts pass.
    pub layout: Option<crate::layout::Layout>,
    /// Set to true if the ElementDebugInfo following this one in the debug vector
    /// in Element::debug is the last one and the next entry belongs to an other element.
    /// This can happen as a result of rectangle optimization, for example.
    pub element_boundary: bool,
}

impl ElementDebugInfo {
    // Returns a comma separate string that encodes the element type name (`Rectangle`, `MyButton`, etc.),
    // the qualified id (`SurroundingComponent::my-id`), and optionally the layout kind
    // (`h-box`, `v-box`, `grid`, `flex-box`).
    fn encoded_element_info(&self) -> String {
        let mut info = self.type_name.clone();
        info.push(',');
        if let Some(id) = self.qualified_id.as_ref() {
            info.push_str(id);
        }
        info.push(',');
        if let Some(layout) = &self.layout {
            use crate::layout::{Layout, Orientation};
            match layout {
                Layout::BoxLayout(b) => match b.orientation {
                    Orientation::Horizontal => info.push_str("h-box"),
                    Orientation::Vertical => info.push_str("v-box"),
                },
                Layout::GridLayout(_) => info.push_str("grid"),
                Layout::FlexboxLayout(_) => info.push_str("flex-box"),
            }
        }
        info
    }
}

/// An Element is an instantiation of a Component
#[derive(Default)]
pub struct Element {
    /// The id as named in the original .slint file.
    ///
    /// Note that it can only be used for lookup before inlining.
    /// After inlining there can be duplicated id in the component.
    /// The id are then re-assigned unique id in the assign_id pass
    pub id: SmolStr,
    //pub base: QualifiedTypeName,
    pub base_type: ElementType,
    /// Currently contains also the callbacks. FIXME: should that be changed?
    pub bindings: Bindings,
    pub change_callbacks: BTreeMap<SmolStr, RefCell<Vec<Expression>>>,
    pub property_analysis: RefCell<BTreeMap<SmolStr, PropertyAnalysis>>,

    pub children: Vec<ElementRc>,
    /// The component which contains this element.
    pub enclosing_component: Weak<Component>,

    pub property_declarations: BTreeMap<SmolStr, PropertyDeclaration>,

    /// Main owner for a reference to a property.
    pub named_references: crate::namedreference::NamedReferenceContainer,

    /// This element is part of a `for <xxx> in <model>`:
    pub repeated: Option<RepeatedElementInfo>,
    /// This element is a placeholder to embed an Component at
    pub is_component_placeholder: bool,

    pub states: Vec<State>,
    pub transitions: Vec<Transition>,

    /// true when this item's geometry is handled by a layout
    pub child_of_layout: bool,
    /// true when this item is a direct cell of a `FlexboxLayout`. Narrower
    /// than `child_of_layout`: only flexbox cells need the per-repeater
    /// `flexbox_layout_item_info` accessor.
    pub child_of_flexbox: bool,
    /// The property pointing to the layout info. `(horizontal, vertical)`
    pub layout_info_prop: Option<(NamedReference, NamedReference)>,
    /// `pure function layoutinfo-v-with-constraint(width: length) -> LayoutInfo`
    /// synthesized for elements whose vertical layout info depends on
    /// their width — lets the parent supply the width and avoid the
    /// recursion that would happen via the descendants' width property.
    pub layout_info_v_with_constraint: Option<NamedReference>,
    /// Mirror of `layout_info_v_with_constraint` for the horizontal axis.
    /// Synthesized on flex elements whose horizontal layout info would
    /// otherwise read their own height (column-direction or unknown).
    pub layout_info_h_with_constraint: Option<NamedReference>,
    /// Whether we have `preferred-{width,height}: 100%`
    pub default_fill_parent: (bool, bool),

    pub accessibility_props: AccessibilityProps,

    /// Reference to the property.
    /// This is always initialized from the element constructor, but is Option because it references itself
    pub geometry_props: Option<GeometryProps>,

    /// true if this Element is the fake Flickable content element
    pub is_flickable_content: bool,

    /// true if this Element may have a popup as child meaning it cannot be optimized
    /// because the popup references it.
    pub has_popup_child: bool,

    /// True for compiler-generated tooltip `PopupWindow` instances (see `lower_tooltips`).
    pub is_tooltip: bool,

    /// This is the component-local index of this item in the item tree array.
    /// It is generated after the last pass and before the generators run.
    pub item_index: OnceCell<u32>,
    /// the index of the first children in the tree, set with item_index
    pub item_index_of_first_children: OnceCell<u32>,

    /// True when this element is in a component was declared with the `:=` symbol instead of the `component` keyword
    pub is_legacy_syntax: bool,

    /// How many times the element was inlined
    pub inline_depth: i32,

    /// If this element is assigned to a specific slot in its parent component (e.g., `name << ...`)
    pub slot_target: Option<SmolStr>,

    /// Slot forwarding mappings declared on this element: `target: source;`
    pub forwarded_slots: Vec<SlotForwarding>,

    /// Information about the grid cell containing this element, if applicable
    pub grid_layout_cell: Option<Rc<RefCell<crate::layout::GridLayoutCell>>>,

    /// Debug information about this element.
    ///
    /// There can be several in case of inlining or optimization (child merged into their parent).
    ///
    /// The order in the list is first the parent, and then the removed children.
    pub debug: Vec<ElementDebugInfo>,
}

impl Spanned for Element {
    fn span(&self) -> crate::diagnostics::Span {
        self.debug
            .first()
            .map(|n| {
                // If possible, only span the qualified name of the Element (i.e. the `MyElement`
                // part of `MyElement { ... }`, as otherwise the span can get very large, which
                // isn't useful for showing diagnostics.
                // Only use the full span as the fallback.
                n.node.QualifiedName().as_ref().map(Spanned::span).unwrap_or_else(|| n.node.span())
            })
            .unwrap_or_default()
    }

    fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
        self.debug.first().map(|n| &n.node.source_file)
    }
}

impl core::fmt::Debug for Element {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        pretty_print(f, self, 0)
    }
}

pub fn pretty_print(
    f: &mut impl std::fmt::Write,
    e: &Element,
    indentation: usize,
) -> std::fmt::Result {
    if let Some(repeated) = &e.repeated {
        write!(f, "for {}[{}] in ", repeated.model_data_id, repeated.index_id)?;
        expression_tree::pretty_print(f, &repeated.model)?;
        write!(f, ":")?;
        if let ElementType::Component(base) = &e.base_type {
            write!(f, "(base) ")?;
            if base.parent_element().is_some() {
                pretty_print(f, &base.root_element.borrow(), indentation)?;
                return Ok(());
            }
        }
    }
    if e.is_component_placeholder {
        write!(f, "/* Component Placeholder */ ")?;
    }
    writeln!(f, "{} := {} {{  /* {} */", e.id, e.base_type, e.element_infos())?;
    let mut indentation = indentation + 1;
    macro_rules! indent {
        () => {
            for _ in 0..indentation {
                write!(f, "   ")?
            }
        };
    }
    for (name, ty) in &e.property_declarations {
        indent!();
        if let Some(alias) = &ty.is_alias {
            writeln!(f, "alias<{}> {} <=> {:?};", ty.property_type, name, alias)?
        } else {
            writeln!(f, "property<{}> {};", ty.property_type, name)?
        }
    }
    for (name, expr) in &e.bindings.0 {
        indent!();
        write!(f, "{name}: ")?;
        let Ok(expr) = expr.try_borrow() else {
            writeln!(f, "<borrowed>")?;
            continue;
        };
        expression_tree::pretty_print(f, &expr.expression)?;
        if expr.analysis.as_ref().is_some_and(|a| a.is_const) {
            write!(f, "/*const*/")?;
        }
        writeln!(f, ";")?;
        //writeln!(f, "; /*{}*/", expr.priority)?;
        if let Some(anim) = &expr.animation {
            indent!();
            writeln!(f, "animate {name} {anim:?}")?;
        }
        for nr in &expr.two_way_bindings {
            indent!();
            writeln!(f, "{name} <=> {nr:?};")?;
        }
    }
    for (name, ch) in &e.change_callbacks {
        for ex in &*ch.borrow() {
            indent!();
            write!(f, "changed {name} => ")?;
            expression_tree::pretty_print(f, ex)?;
            writeln!(f)?;
        }
    }
    if !e.states.is_empty() {
        indent!();
        writeln!(f, "states {:?}", e.states)?;
    }
    if !e.transitions.is_empty() {
        indent!();
        writeln!(f, "transitions {:?} ", e.transitions)?;
    }
    for c in &e.children {
        indent!();
        pretty_print(f, &c.borrow(), indentation)?
    }
    if let Some(g) = &e.geometry_props {
        indent!();
        writeln!(f, "geometry {g:?} ")?;
    }

    /*if let Type::Component(base) = &e.base_type {
        pretty_print(f, &c.borrow(), indentation)?
    }*/
    indentation -= 1;
    indent!();
    writeln!(f, "}}")
}

#[derive(Clone, Default, Debug)]
pub struct PropertyAnalysis {
    /// true if somewhere in the code, there is an expression that changes this property with an assignment
    pub is_set: bool,

    /// True if this property might be set from a different component.
    pub is_set_externally: bool,

    /// true if somewhere in the code, an expression is reading this property
    /// Note: currently this is only set in the binding analysis pass
    pub is_read: bool,

    /// true if this property is read from another component
    pub is_read_externally: bool,

    /// True if the property is linked to another property that is read only. That property becomes read-only
    pub is_linked_to_read_only: bool,

    /// True if this property is linked to another property
    pub is_linked: bool,
}

impl PropertyAnalysis {
    /// Merge analysis from base element for inlining
    ///
    /// Contrary to `merge`, we don't keep the external uses because
    /// they should come from us
    pub fn merge_with_base(&mut self, other: &PropertyAnalysis) {
        self.is_set |= other.is_set;
        self.is_read |= other.is_read;
    }

    /// Merge the analysis
    pub fn merge(&mut self, other: &PropertyAnalysis) {
        self.is_set |= other.is_set;
        self.is_read |= other.is_read;
        self.is_read_externally |= other.is_read_externally;
        self.is_set_externally |= other.is_set_externally;
    }

    /// Return true if it is read or set or used in any way
    pub fn is_used(&self) -> bool {
        self.is_read || self.is_read_externally || self.is_set || self.is_set_externally
    }
}

#[derive(Debug, Clone)]
pub struct ListViewInfo {
    pub content_y: NamedReference,
    /// `None` when the user explicitly sets `content-height` on the ListView;
    /// `Some` when the ListView computes it from the content.
    pub content_height: Option<NamedReference>,
    /// `None` when the user explicitly sets `content-width` on the ListView;
    /// `Some` when the ListView computes it from the content.
    pub content_width: Option<NamedReference>,
    /// The ListView's inner visible height (not counting eventual scrollbar)
    pub listview_height: NamedReference,
    /// The ListView's inner visible width (not counting eventual scrollbar)
    pub listview_width: NamedReference,
}

#[derive(Debug, Clone)]
/// If the parent element is a repeated element, this has information about the models
pub struct RepeatedElementInfo {
    pub model: Expression,
    pub model_data_id: SmolStr,
    pub index_id: SmolStr,
    /// A conditional element is just a for whose model is a boolean expression
    ///
    /// When this is true, the model is of type boolean instead of Model
    pub is_conditional_element: bool,
    /// When the for is the delegate of a ListView
    pub is_listview: Option<ListViewInfo>,
}

pub type ElementRc = Rc<RefCell<Element>>;
pub type ElementWeak = Weak<RefCell<Element>>;

impl Element {
    pub fn make_rc(self) -> ElementRc {
        let r = ElementRc::new(RefCell::new(self));
        let g = GeometryProps::new(&r);
        r.borrow_mut().geometry_props = Some(g);
        r
    }

    pub fn from_node(
        node: syntax_nodes::Element,
        id: SmolStr,
        parent_type: ElementType,
        component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
        declared_slots: &mut Vec<DeclaredSlot>,
        is_legacy_syntax: bool,
        diag: &mut BuildDiagnostics,
        tr: &TypeRegister,
    ) -> ElementRc {
        // A child element's parent_type is the type of its parent; the root
        // gets a sentinel from Component::from_node
        #[cfg(feature = "slint-sc")]
        let is_component_root =
            !matches!(parent_type, ElementType::Builtin(_) | ElementType::Component(_));
        let base_type = if let Some(base_node) = node.QualifiedName() {
            let base = QualifiedTypeName::from_node(base_node.clone());
            let base_string = base.to_smolstr();
            match parent_type.lookup_type_for_child_element(&base_string, tr) {
                Ok(ElementType::Component(c)) if c.is_global() => {
                    diag.push_error(
                        "Cannot create an instance of a global component".into(),
                        &base_node,
                    );
                    ElementType::Error
                }
                Ok(ty) => {
                    // Window children, including through a component that
                    // inherits Window, error in warn_about_child_windows.
                    #[cfg(feature = "slint-sc")]
                    if let ElementType::Builtin(b) = &ty
                        && !b.slint_sc
                    {
                        diag.slint_sc_error(
                            &format!("The builtin element '{}' is", b.name),
                            &base_node,
                        );
                    }
                    ty
                }
                Err(err) => {
                    diag.push_error(err, &base_node);
                    ElementType::Error
                }
            }
        } else if parent_type == ElementType::Global || parent_type == ElementType::Interface {
            // This must be a global component or interface. It can only have properties and callbacks
            let mut error_on = |node: &dyn Spanned, what: &str| {
                let element_type = match parent_type {
                    ElementType::Global => "A global component",
                    ElementType::Interface => "An interface",
                    _ => "An unexpected type",
                };
                diag.push_error(format!("{element_type} cannot have {what}"), node);
            };
            node.SubElement().for_each(|n| error_on(&n, "sub elements"));
            node.RepeatedElement().for_each(|n| error_on(&n, "sub elements"));
            if let Some(n) = node.ChildrenPlaceholder() {
                error_on(&n, "sub elements");
            }
            node.PropertyAnimation().for_each(|n| error_on(&n, "animations"));
            node.States().for_each(|n| error_on(&n, "states"));
            node.Transitions().for_each(|n| error_on(&n, "transitions"));
            node.CallbackDeclaration().for_each(|cb| {
                if parser::identifier_text(&cb.DeclaredIdentifier()).is_some_and(|s| s == "init") {
                    error_on(&cb, "an 'init' callback")
                }
            });
            node.CallbackConnection().for_each(|cb| {
                if parser::identifier_text(&cb).is_some_and(|s| s == "init") {
                    error_on(&cb, "an 'init' callback")
                }
            });
            node.MatchElement().for_each(|n| error_on(&n, "match statements"));
            node.SlotDeclaration().for_each(|n| error_on(&n, "slots"));

            if parent_type == ElementType::Interface {
                node.Binding().for_each(|n| error_on(&n, "bindings"));
                node.TwoWayBinding().for_each(|n| error_on(&n, "two-way bindings"));

                node.ImplementStatement().for_each(|stmt| {
                    diag.push_error("Interfaces cannot implement another interface".into(), &stmt);
                });
            } else {
                node.ImplementStatement().for_each(|stmt| {
                    diag.push_error("Globals cannot implement an interface".into(), &stmt);
                });
            }

            parent_type
        } else if parent_type != ElementType::Error {
            // This should normally never happen because the parser does not allow for this
            assert!(diag.has_errors());
            return ElementRc::default();
        } else {
            tr.empty_type()
        };
        let is_interface = base_type == ElementType::Interface;
        // This isn't truly qualified yet, the enclosing component is added at the end of Component::from_node
        let qualified_id = (!id.is_empty()).then(|| id.clone());
        if let ElementType::Component(c) = &base_type {
            c.used.set(true);
        }
        let type_name = base_type
            .type_name()
            .filter(|_| base_type != tr.empty_type())
            .unwrap_or_default()
            .to_string();
        let mut r = Element {
            id,
            base_type: base_type.clone(),
            debug: vec![ElementDebugInfo {
                qualified_id,
                element_hash: 0,
                type_name,
                node: node.clone(),
                layout: None,
                element_boundary: false,
            }],
            is_legacy_syntax,
            ..Default::default()
        };

        let mut property_bindings: Vec<(
            SmolStr,
            syntax_nodes::BindingExpression,
            syntax_nodes::DeclaredIdentifier,
        )> = Vec::new();

        let mut two_way_bindings: Vec<(
            SmolStr,
            syntax_nodes::TwoWayBinding,
            syntax_nodes::DeclaredIdentifier,
        )> = Vec::new();

        for prop_decl in node.PropertyDeclaration() {
            // Only the root element's properties become part of the component's API
            #[cfg(feature = "slint-sc")]
            if !is_component_root {
                diag.slint_sc_error(
                    "Declaring a property on an element other than the root is",
                    &prop_decl,
                );
            }
            let prop_type = prop_decl
                .Type()
                .map(|type_node| type_from_node(type_node, diag, tr))
                // Type::Void is used for two way bindings without type specified
                .unwrap_or(Type::InferredProperty);

            let unresolved_prop_name =
                unwrap_or_continue!(parser::identifier_text(&prop_decl.DeclaredIdentifier()); diag);
            let PropertyLookupResult {
                resolved_name: prop_name,
                property_type: maybe_existing_prop_type,
                is_shadowable,
                ..
            } = r.lookup_property(&unresolved_prop_name);
            let shadows_builtin = maybe_existing_prop_type != Type::Invalid && is_shadowable;
            match maybe_existing_prop_type {
                Type::Invalid => {} // Ok to proceed with a new declaration
                // The declaration shadows a shadowable builtin member;
                // the check_builtin_shadowing pass emits the diagnostic
                _ if shadows_builtin => {}
                Type::Callback { .. } => {
                    diag.push_error(
                        format!("Cannot declare property '{prop_name}' when a callback with the same name exists"),
                        &prop_decl.DeclaredIdentifier().child_token(SyntaxKind::Identifier).unwrap(),
                    );
                    continue;
                }
                Type::Function { .. } => {
                    diag.push_error(
                        format!("Cannot declare property '{prop_name}' when a function with the same name exists"),
                        &prop_decl.DeclaredIdentifier().child_token(SyntaxKind::Identifier).unwrap(),
                    );
                    continue;
                }
                _ => {
                    diag.push_error(
                        format!("Cannot override property '{unresolved_prop_name}'"),
                        &prop_decl
                            .DeclaredIdentifier()
                            .child_token(SyntaxKind::Identifier)
                            .unwrap(),
                    );
                    continue;
                }
            }

            let mut visibility = None;
            for token in prop_decl.children_with_tokens() {
                if token.kind() != SyntaxKind::Identifier {
                    continue;
                }
                match (token.as_token().unwrap().text(), visibility) {
                    ("in", None) => visibility = Some(PropertyVisibility::Input),
                    ("in", Some(_)) => diag.push_error("Extra 'in' keyword".into(), &token),
                    ("out", None) => visibility = Some(PropertyVisibility::Output),
                    ("out", Some(_)) => diag.push_error("Extra 'out' keyword".into(), &token),
                    ("in-out" | "in_out", None) => visibility = Some(PropertyVisibility::InOut),
                    ("in-out" | "in_out", Some(_)) => {
                        diag.push_error("Extra 'in-out' keyword".into(), &token)
                    }
                    ("private", None) => visibility = Some(PropertyVisibility::Private),
                    ("private", Some(_)) => {
                        diag.push_error("Extra 'private' keyword".into(), &token)
                    }
                    _ => (),
                }
            }
            let visibility = visibility.unwrap_or({
                if is_legacy_syntax {
                    PropertyVisibility::InOut
                } else {
                    PropertyVisibility::Private
                }
            });

            if is_interface {
                if let Some(binding_expression) = &prop_decl.BindingExpression() {
                    diag.push_error(
                        "Interface properties cannot have default values".into(),
                        binding_expression,
                    )
                }
                if let Some(two_way) = &prop_decl.TwoWayBinding() {
                    diag.push_error(
                        "Interface properties cannot have default bindings".into(),
                        two_way,
                    )
                }
                if visibility == PropertyVisibility::Private {
                    diag.push_error(
                        "'private' properties are inaccessible in an interface".into(),
                        &prop_decl,
                    );
                }
            }

            let deprecated = prop_decl.PropertyDeprecation().and_then(|deprecation| {
                if reject_experimental_feature(diag, tr, "@deprecated", &deprecation) {
                    return None;
                }
                if let Some(message) = deprecation.child_token(SyntaxKind::StringLiteral) {
                    crate::literals::unescape_string(message.text())
                } else if let Some(qn) = prop_decl
                    .TwoWayBinding()
                    .and_then(|twb| twb.Expression().QualifiedName())
                {
                    // Keep the full target path (e.g. `a-struct.field`), dropping a leading
                    // `self`/`root`. The resolving pass checks the target is actually reachable.
                    let mut segments = qn
                        .children_with_tokens()
                        .filter(|t| t.kind() == SyntaxKind::Identifier)
                        .map(|t| parser::normalize_identifier(t.as_token().unwrap().text()))
                        .peekable();
                    if segments.peek().is_some_and(|s| matches!(s.as_str(), "self" | "root")) {
                        segments.next();
                    }
                    let path = segments.collect::<Vec<_>>().join(".");
                    (!path.is_empty()).then(|| format_smolstr!("Please use '{path}' instead"))
                } else {
                    diag.push_error(
                        "@deprecated without a message requires a two-way binding to derive the replacement from".into(),
                        &deprecation,
                    );
                    None
                }
            });

            // Use the name as declared, not the resolved name: when the declaration
            // shadows a builtin member, the resolved name may be a native alias.
            r.property_declarations.insert(
                unresolved_prop_name.clone(),
                PropertyDeclaration {
                    property_type: prop_type,
                    node: Some(prop_decl.clone().into()),
                    visibility,
                    shadows_builtin,
                    deprecated,
                    ..Default::default()
                },
            );

            if let Some(csn) = prop_decl.BindingExpression() {
                property_bindings.push((
                    unresolved_prop_name.clone(),
                    csn,
                    prop_decl.DeclaredIdentifier(),
                ));
            }

            if let Some(csn) = prop_decl.TwoWayBinding() {
                #[cfg(feature = "slint-sc")]
                diag.slint_sc_error("Two-way bindings are", &csn);
                two_way_bindings.push((unresolved_prop_name, csn, prop_decl.DeclaredIdentifier()));
            }
        }

        let (implemented_interfaces, child_implements) =
            if matches!(r.base_type, ElementType::Global | ElementType::Interface) {
                // Already rejected above with a more specific diagnostic.
                (Vec::new(), Vec::new())
            } else if r.id == "root" {
                interfaces::get_implemented_interfaces(&r, &node, tr, diag)
            } else {
                interfaces::disallow_implement_in_non_root(&node, tr, diag);
                (Vec::new(), Vec::new())
            };

        for (prop_name, csn, source) in property_bindings {
            match r.bindings.0.entry(prop_name.clone()) {
                Entry::Vacant(e) => {
                    e.insert(BindingExpression::new_uncompiled(csn.into()).into());
                }
                Entry::Occupied(_) => {
                    diag.push_error("Duplicated property binding".into(), &source);
                }
            }
        }

        for (prop_name, csn, source) in two_way_bindings {
            if r.bindings
                .0
                .insert(prop_name, BindingExpression::new_uncompiled(csn.into()).into())
                .is_some()
            {
                diag.push_error("Duplicated property binding".into(), &source);
            }
        }

        r.parse_bindings(
            node.Binding().filter_map(|b| {
                Some((b.child_token(SyntaxKind::Identifier)?, b.BindingExpression().into()))
            }),
            is_legacy_syntax,
            diag,
        );
        r.parse_bindings(
            node.TwoWayBinding()
                .filter_map(|b| Some((b.child_token(SyntaxKind::Identifier)?, b.into()))),
            is_legacy_syntax,
            diag,
        );

        apply_default_type_properties(&mut r);

        for sig_decl in node.CallbackDeclaration() {
            #[cfg(feature = "slint-sc")]
            diag.slint_sc_error("Callback declarations are", &sig_decl);
            let name =
                unwrap_or_continue!(parser::identifier_text(&sig_decl.DeclaredIdentifier()); diag);

            let pure = Some(
                sig_decl.child_token(SyntaxKind::Identifier).is_some_and(|t| t.text() == "pure"),
            );

            let PropertyLookupResult {
                resolved_name: existing_name,
                property_type: maybe_existing_prop_type,
                is_shadowable,
                ..
            } = r.lookup_property(&name);
            // When the declaration shadows a shadowable builtin member, proceed;
            // the check_builtin_shadowing pass emits the diagnostic
            let shadows_builtin = !matches!(maybe_existing_prop_type, Type::Invalid);
            if shadows_builtin && !is_shadowable {
                if matches!(maybe_existing_prop_type, Type::Callback { .. }) {
                    if r.property_declarations.contains_key(&name) {
                        diag.push_error(
                            "Duplicated callback declaration".into(),
                            &sig_decl.DeclaredIdentifier(),
                        );
                    } else {
                        diag.push_error(
                            format!("Cannot override callback '{existing_name}'"),
                            &sig_decl.DeclaredIdentifier(),
                        )
                    }
                } else {
                    diag.push_error(
                        format!(
                            "Cannot declare callback '{existing_name}' when a {} with the same name exists",
                            if matches!(maybe_existing_prop_type, Type::Function { .. }) { "function" } else { "property" }
                        ),
                        &sig_decl.DeclaredIdentifier(),
                    );
                }
                continue;
            }

            if let Some(csn) = sig_decl.TwoWayBinding() {
                r.bindings
                    .0
                    .insert(name.clone(), BindingExpression::new_uncompiled(csn.into()).into());
                r.property_declarations.insert(
                    name,
                    PropertyDeclaration {
                        property_type: Type::InferredCallback,
                        node: Some(sig_decl.into()),
                        visibility: PropertyVisibility::InOut,
                        pure,
                        shadows_builtin,
                        ..Default::default()
                    },
                );
                continue;
            }

            let args = sig_decl
                .CallbackDeclarationParameter()
                .map(|p| type_from_node(p.Type(), diag, tr))
                .collect();
            let return_type = sig_decl
                .ReturnType()
                .map(|ret_ty| type_from_node(ret_ty.Type(), diag, tr))
                .unwrap_or(Type::Void);
            let arg_names = sig_decl
                .CallbackDeclarationParameter()
                .map(|a| {
                    a.DeclaredIdentifier()
                        .and_then(|x| parser::identifier_text(&x))
                        .unwrap_or_default()
                })
                .collect();
            r.property_declarations.insert(
                name,
                PropertyDeclaration {
                    property_type: Type::Callback(Arc::new(Function {
                        return_type,
                        args,
                        arg_names,
                    })),
                    node: Some(sig_decl.into()),
                    visibility: PropertyVisibility::InOut,
                    pure,
                    shadows_builtin,
                    ..Default::default()
                },
            );
        }

        for func in node.Function() {
            #[cfg(feature = "slint-sc")]
            diag.slint_sc_error("Function declarations are", &func);
            let name =
                unwrap_or_continue!(parser::identifier_text(&func.DeclaredIdentifier()); diag);

            let PropertyLookupResult {
                resolved_name: existing_name,
                property_type: maybe_existing_prop_type,
                is_shadowable,
                ..
            } = r.lookup_property(&name);
            // When the declaration shadows a shadowable builtin member, proceed;
            // the check_builtin_shadowing pass emits the diagnostic
            let shadows_builtin = !matches!(maybe_existing_prop_type, Type::Invalid);
            if shadows_builtin && !is_shadowable {
                if matches!(maybe_existing_prop_type, Type::Callback { .. } | Type::Function { .. })
                {
                    diag.push_error(
                        format!("Cannot override '{existing_name}'"),
                        &func.DeclaredIdentifier(),
                    )
                } else {
                    diag.push_error(
                        format!("Cannot declare function '{existing_name}' when a property with the same name exists"),
                        &func.DeclaredIdentifier(),
                    );
                }
                continue;
            }

            let mut args = Vec::new();
            let mut arg_names = Vec::new();
            for a in func.ArgumentDeclaration() {
                args.push(type_from_node(a.Type(), diag, tr));
                let name =
                    unwrap_or_continue!(parser::identifier_text(&a.DeclaredIdentifier()); diag);
                if arg_names.contains(&name) {
                    diag.push_error(
                        format!("Duplicated argument name '{name}'"),
                        &a.DeclaredIdentifier(),
                    );
                }
                arg_names.push(name);
            }
            let return_type = func
                .ReturnType()
                .map_or(Type::Void, |ret_ty| type_from_node(ret_ty.Type(), diag, tr));

            let mut visibility = PropertyVisibility::Private;
            let mut pure = None;
            for token in func.children_with_tokens() {
                if token.kind() != SyntaxKind::Identifier {
                    continue;
                }
                match token.as_token().unwrap().text() {
                    "pure" => pure = Some(true),
                    "public" => {
                        visibility = PropertyVisibility::Public;
                        pure = pure.or(Some(false));
                    }
                    "protected" => {
                        visibility = PropertyVisibility::Protected;
                        pure = pure.or(Some(false));
                    }
                    _ => (),
                }
            }

            if is_interface && visibility != PropertyVisibility::Public {
                diag.push_error(
                    "Function declarations in an interface must be public".into(),
                    &func,
                );
            }

            let declaration = PropertyDeclaration {
                property_type: Type::Function(Arc::new(Function { return_type, args, arg_names })),
                node: Some(func.clone().into()),
                visibility,
                pure,
                shadows_builtin,
                ..Default::default()
            };

            match (base_type.clone(), func.CodeBlock()) {
                (ElementType::Interface, Some(code_block)) => {
                    diag.push_error(
                        "Function declarations in interfaces must not have a body".into(),
                        &code_block,
                    );
                    continue;
                }
                (ElementType::Interface, None) => {
                    // Do not create a binding for this function, as it is just a declaration without body. It will be
                    // implemented by the component that implements the interface.
                    r.property_declarations.insert(name, declaration);
                    continue;
                }
                (_, None) => {
                    diag.push_error("Functions must have a code block".into(), &func);
                }
                (_, Some(_)) => {}
            }

            if r.bindings
                .0
                .insert(name.clone(), BindingExpression::new_uncompiled(func.clone().into()).into())
                .is_some()
            {
                assert!(diag.has_errors());
            }

            r.property_declarations.insert(name, declaration);
        }

        for con_node in node.CallbackConnection() {
            #[cfg(feature = "slint-sc")]
            diag.slint_sc_error("Callback handlers are", &con_node);
            let unresolved_name = unwrap_or_continue!(parser::identifier_text(&con_node); diag);
            let PropertyLookupResult { resolved_name, property_type, .. } =
                r.lookup_property(&unresolved_name);
            if let Type::Callback(callback) = &property_type {
                let num_arg = con_node.DeclaredIdentifier().count();
                if num_arg > callback.args.len() {
                    diag.push_error(
                        format!(
                            "'{}' only has {} arguments, but {} were provided",
                            unresolved_name,
                            callback.args.len(),
                            num_arg
                        ),
                        &con_node.child_token(SyntaxKind::Identifier).unwrap(),
                    );
                }
            } else if property_type == Type::InferredCallback {
                // argument matching will happen later
            } else {
                if r.base_type != ElementType::Error {
                    diag.push_error(
                        format!("'{}' is not a callback in {}", unresolved_name, r.base_type),
                        &con_node.child_token(SyntaxKind::Identifier).unwrap(),
                    );
                }
                continue;
            }
            match r.bindings.0.entry(resolved_name.into()) {
                Entry::Vacant(e) => {
                    e.insert(BindingExpression::new_uncompiled(con_node.clone().into()).into());
                }
                Entry::Occupied(mut e) => {
                    // A global may implement a callback declared in another global: the
                    // callback is declared as a two-way alias (`callback foo <=> Other.foo;`)
                    // and also given a handler (`foo => { ... }`). The alias node stays on
                    // the declaration, and the handler takes the binding expression slot.
                    let is_global_alias = r.base_type == ElementType::Global
                        && matches!(
                            &e.get().borrow().expression,
                            Expression::Uncompiled(node) if node.kind() == SyntaxKind::TwoWayBinding
                        );
                    if is_global_alias {
                        // Keep the handler as the binding and point its span at the handler
                        // name, so a duplicate-implementation error refers to the
                        // implementation rather than the alias. The alias is recovered from
                        // the declaration node, so dropping it from the binding is fine.
                        let mut handler =
                            BindingExpression::new_uncompiled(con_node.clone().into());
                        if let Some(name) = con_node.child_token(SyntaxKind::Identifier) {
                            handler.span = Some(name.to_source_location());
                        }
                        e.insert(handler.into());
                    } else {
                        diag.push_error(
                            "Duplicated callback".into(),
                            &con_node.child_token(SyntaxKind::Identifier).unwrap(),
                        );
                    }
                }
            }
        }

        for anim in node.PropertyAnimation() {
            #[cfg(feature = "slint-sc")]
            diag.slint_sc_error("Animations are", &anim);
            if let Some(star) = anim.child_token(SyntaxKind::Star) {
                diag.push_error(
                    "catch-all property is only allowed within transitions".into(),
                    &star,
                )
            };
            for prop_name_token in anim.QualifiedName() {
                match QualifiedTypeName::from_node(prop_name_token.clone()).members.as_slice() {
                    [unresolved_prop_name] => {
                        if r.base_type == ElementType::Error {
                            continue;
                        };
                        let lookup_result = r.lookup_property(unresolved_prop_name);
                        let valid_assign = lookup_result.is_valid_for_assignment();
                        if let Some(anim_element) = animation_element_from_node(
                            &anim,
                            &prop_name_token,
                            lookup_result.property_type,
                            diag,
                            tr,
                        ) {
                            if !valid_assign {
                                diag.push_error(
                                    format!(
                                        "Cannot animate '{}' property '{}'",
                                        lookup_result.property_visibility, unresolved_prop_name
                                    ),
                                    &prop_name_token,
                                );
                            }

                            if unresolved_prop_name != lookup_result.resolved_name.as_ref() {
                                diag.push_property_deprecation_warning(
                                    unresolved_prop_name,
                                    &lookup_result.resolved_name,
                                    &prop_name_token,
                                );
                            } else if let Some(message) = lookup_result
                                .deprecated
                                .as_ref()
                                .filter(|_| !lookup_result.is_local_to_component)
                            {
                                diag.push_property_deprecation_warning_with_message(
                                    unresolved_prop_name,
                                    message,
                                    &prop_name_token,
                                );
                            }

                            let expr_binding = r
                                .bindings
                                .0
                                .entry(lookup_result.resolved_name.into())
                                .or_insert_with(|| {
                                    let mut r = BindingExpression::from(Expression::Invalid);
                                    r.priority = 1;
                                    r.span = Some(prop_name_token.to_source_location());
                                    r.into()
                                });
                            if expr_binding
                                .get_mut()
                                .animation
                                .replace(PropertyAnimation::Static(anim_element))
                                .is_some()
                            {
                                diag.push_error("Duplicated animation".into(), &prop_name_token)
                            }
                        }
                    }
                    _ => diag.push_error(
                        "Can only refer to property in the current element".into(),
                        &prop_name_token,
                    ),
                }
            }
        }

        for ch in node.PropertyChangedCallback() {
            #[cfg(feature = "slint-sc")]
            diag.slint_sc_error("Change callbacks are", &ch);
            let Some(prop) = parser::identifier_text(&ch.DeclaredIdentifier()) else { continue };
            let lookup_result = r.lookup_property(&prop);
            if !lookup_result.is_valid() {
                if r.base_type != ElementType::Error {
                    diag.push_error(
                        format!("Property '{prop}' does not exist"),
                        &ch.DeclaredIdentifier(),
                    );
                }
            } else if !lookup_result.property_type.is_property_type() {
                let what = match lookup_result.property_type {
                    Type::Function { .. } => "a function",
                    Type::Callback { .. } => "a callback",
                    _ => "not a property",
                };
                diag.push_error(
                    format!(
                        "Change callback can only be set on properties, and '{prop}' is {what}"
                    ),
                    &ch.DeclaredIdentifier(),
                );
            } else if lookup_result.property_visibility == PropertyVisibility::Private
                && !lookup_result.is_local_to_component
            {
                diag.push_error(
                    format!("Change callback on a private property '{prop}'"),
                    &ch.DeclaredIdentifier(),
                );
            }
            let handler = Expression::Uncompiled(ch.clone().into());
            match r.change_callbacks.entry(prop) {
                Entry::Vacant(e) => {
                    e.insert(vec![handler].into());
                }
                Entry::Occupied(mut e) => {
                    diag.push_error(
                        format!("Duplicated change callback on '{}'", e.key()),
                        &ch.DeclaredIdentifier(),
                    );
                    e.get_mut().get_mut().push(handler);
                }
            }
        }

        let r = r.make_rc();

        for se in node.children() {
            if se.kind() != SyntaxKind::SlotForwarding {
                continue;
            }
            if !Self::assert_experimental_slots(diag, &se, "slot forwarding") {
                continue;
            }

            let target_node = se.child_node(SyntaxKind::DeclaredIdentifier).unwrap();
            let target = parser::identifier_text(&target_node.clone()).unwrap_or_default();

            if target == "children" {
                diag.push_error(
                    format!(
                        "The name '{target}' is reserved for the default slot. Use @children instead"
                    ),
                    &target_node,
                );
                continue;
            }

            if r.borrow().forwarded_slots.iter().any(|f| f.target == target) {
                diag.push_error(format!("Duplicate assignment to slot '{target}'"), &target_node);
                continue;
            }

            match &r.borrow().base_type {
                ElementType::Component(component)
                    if !component
                        .declared_slots
                        .borrow()
                        .iter()
                        .any(|slot| slot.name == target) =>
                {
                    diag.push_error(
                        format!("Unknown slot '{target}' in '{}'", component.id),
                        &target_node,
                    );
                    continue;
                }
                ElementType::Component(_) => {}
                _ => {
                    diag.push_error("Slot forwarding can only be used on components".into(), &se);
                    continue;
                }
            }

            let Some(expression_node) = se.child_node(SyntaxKind::Expression) else {
                diag.push_error(
                    "Slot forwarding requires a slot identifier on the right-hand side".into(),
                    &se,
                );
                continue;
            };
            let Some(source) = Self::slot_forwarding_expr_identifier(&expression_node) else {
                diag.push_error(
                    "Slot forwarding requires a slot identifier on the right-hand side".into(),
                    &expression_node,
                );
                continue;
            };

            if source == "children" {
                diag.push_error(
                    format!(
                        "The name '{source}' is reserved for the default slot. Use @children instead"
                    ),
                    &expression_node,
                );
                continue;
            }

            r.borrow_mut().forwarded_slots.push(SlotForwarding {
                target,
                source,
                expression_node: expression_node.into(),
            });
        }

        for forwarding in r.borrow().forwarded_slots.clone() {
            let source = forwarding.source.clone();
            if let Some(existing_cip) = component_child_insertion_points.get(source.as_str()) {
                if matches!(existing_cip.node, ChildInsertionPointNode::SlotPlaceholder(_)) {
                    diag.push_error(
                        format!(
                            "The slot '{source}' cannot be forwarded and used as a placeholder in the same component"
                        ),
                        &forwarding.expression_node,
                    );
                } else {
                    diag.push_error(
                        format!(
                            "{} can only appear once in an element",
                            slot_error_subject(&source)
                        ),
                        &forwarding.expression_node,
                    );
                }
                continue;
            }
            component_child_insertion_points.insert(
                source.to_string(),
                ChildrenInsertionPoint {
                    parent: r.clone(),
                    insertion_index: 0,
                    node: ChildInsertionPointNode::SlotForwarding(forwarding.expression_node),
                },
            );
        }

        let mut assigned_slots = HashSet::new();

        for se in node.children() {
            if se.kind() == SyntaxKind::SubElement {
                if let Some(slot_name) =
                    Self::sub_element_slot_placeholder_name(&se, declared_slots)
                {
                    Self::register_slot_placeholder(
                        &se,
                        slot_name,
                        &r,
                        component_child_insertion_points,
                        diag,
                        tr,
                    );
                    continue;
                }
                let parent_type = r.borrow().base_type.clone();
                r.borrow_mut().children.push(Element::from_sub_element_node(
                    se.into(),
                    parent_type,
                    component_child_insertion_points,
                    declared_slots,
                    is_legacy_syntax,
                    diag,
                    tr,
                ));
            } else if se.kind() == SyntaxKind::RepeatedElement {
                let mut sub_child_insertion_points = BTreeMap::new();
                let rep = Element::from_repeated_node(
                    se.into(),
                    &r,
                    &mut sub_child_insertion_points,
                    declared_slots,
                    is_legacy_syntax,
                    diag,
                    tr,
                );
                Self::reject_slot_placeholders(
                    diag,
                    declared_slots,
                    sub_child_insertion_points,
                    "a repeated element",
                );
                r.borrow_mut().children.push(rep);
            } else if se.kind() == SyntaxKind::ConditionalElement {
                let mut sub_child_insertion_points = BTreeMap::new();
                let rep = Element::from_conditional_node(
                    se.into(),
                    r.borrow().base_type.clone(),
                    &mut sub_child_insertion_points,
                    declared_slots,
                    is_legacy_syntax,
                    diag,
                    tr,
                );
                Self::reject_slot_placeholders(
                    diag,
                    declared_slots,
                    sub_child_insertion_points,
                    "a conditional element",
                );
                r.borrow_mut().children.push(rep);
            } else if se.kind() == SyntaxKind::MatchElement {
                let mut sub_child_insertion_points = BTreeMap::new();
                let rep = Element::from_match_node(
                    se.into(),
                    r.borrow().base_type.clone(),
                    &mut sub_child_insertion_points,
                    declared_slots,
                    is_legacy_syntax,
                    diag,
                    tr,
                );
                Self::reject_slot_placeholders(
                    diag,
                    declared_slots,
                    sub_child_insertion_points,
                    "a match element",
                );
                r.borrow_mut().children.extend(rep);
            } else if se.kind() == SyntaxKind::ChildrenPlaceholder {
                #[cfg(feature = "slint-sc")]
                diag.slint_sc_error("The @children placeholder is", &se);
                if component_child_insertion_points.contains_key(DEFAULT_SLOT_NAME) {
                    diag.push_error(
                        format!(
                            "{} can only appear once in an element",
                            slot_error_subject(DEFAULT_SLOT_NAME)
                        ),
                        &se,
                    );
                } else {
                    component_child_insertion_points.insert(
                        DEFAULT_SLOT_NAME.into(),
                        ChildrenInsertionPoint {
                            parent: r.clone(),
                            insertion_index: r.borrow().children.len(),
                            node: ChildInsertionPointNode::ChildrenPlaceHolder(se.into()),
                        },
                    );
                }
            } else if se.kind() == SyntaxKind::SlotDeclaration {
                Self::assert_experimental_slots(diag, &se, "named slots");
                let decl: syntax_nodes::SlotDeclaration = se.into();
                let name_node = decl.DeclaredIdentifier();
                let name = parser::identifier_text(&name_node).unwrap_or_default();
                declared_slots.push(DeclaredSlot {
                    name,
                    name_node,
                    has_rejected_placeholder: false,
                });
            } else if se.kind() == SyntaxKind::SlotAssignment {
                if !Self::assert_experimental_slots(diag, &se, "named slots") {
                    continue;
                }
                let name_node = se.child_node(SyntaxKind::DeclaredIdentifier).unwrap();
                let name = parser::identifier_text(&name_node).unwrap_or_default();
                if name == "children" {
                    diag.push_error(
                        format!(
                            "The name '{name}' is reserved for the default slot. Use @children instead"
                        ),
                        &name_node,
                    );
                }
                if !assigned_slots.insert(name.clone()) {
                    diag.push_error(format!("Duplicate assignment to slot '{name}'"), &name_node);
                }
                if r.borrow().forwarded_slots.iter().any(|f| f.target == name) {
                    diag.push_error(format!("Duplicate assignment to slot '{name}'"), &name_node);
                }
                let sub_element_node = se.child_node(SyntaxKind::SubElement).unwrap();
                let parent_type = r.borrow().base_type.clone();
                match &parent_type {
                    ElementType::Component(component)
                        if !component
                            .declared_slots
                            .borrow()
                            .iter()
                            .any(|slot| slot.name == name) =>
                    {
                        diag.push_error(
                            format!("Unknown slot '{name}' in '{}'", component.id),
                            &name_node,
                        );
                    }
                    ElementType::Component(_) => {}
                    _ => {
                        diag.push_error(
                            "Slot assignments can only be used on components".to_string(),
                            &se,
                        );
                    }
                }
                let element = Element::from_sub_element_node(
                    sub_element_node.into(),
                    parent_type,
                    component_child_insertion_points,
                    declared_slots,
                    is_legacy_syntax,
                    diag,
                    tr,
                );
                element.borrow_mut().slot_target = Some(name);
                r.borrow_mut().children.push(element);
            }
        }

        #[cfg(feature = "slint-sc")]
        for s_node in node.States() {
            diag.slint_sc_error("States are", &s_node);
        }
        for state in node.States().flat_map(|s| s.State()) {
            let s = State {
                id: parser::identifier_text(&state.DeclaredIdentifier()).unwrap_or_default(),
                condition: state.Expression().map(|e| Expression::Uncompiled(e.into())),
                property_changes: state
                    .StatePropertyChange()
                    .filter_map(|s| {
                        lookup_property_from_qualified_name_for_state(s.QualifiedName(), &r, diag)
                            .map(|(ne, ty)| {
                                if !ty.is_property_type() && !matches!(ty, Type::Invalid) {
                                    diag.push_error(
                                        format!("'{}' is not a property", **s.QualifiedName()),
                                        &s,
                                    );
                                }
                                (ne, Expression::Uncompiled(s.BindingExpression().into()), s)
                            })
                    })
                    .collect(),
            };
            for trs in state.Transition() {
                let mut t = Transition::from_node(trs, &r, tr, diag);
                t.state_id.clone_from(&s.id);
                r.borrow_mut().transitions.push(t);
            }
            r.borrow_mut().states.push(s);
        }

        for ts in node.Transitions() {
            #[cfg(feature = "slint-sc")]
            diag.slint_sc_error("Transitions are", &ts);
            if !is_legacy_syntax {
                diag.push_error("'transitions' block are no longer supported. Use 'in {...}' and 'out {...}' directly in the state definition".into(), &ts);
            }
            for trs in ts.Transition() {
                let trans = Transition::from_node(trs, &r, tr, diag);
                r.borrow_mut().transitions.push(trans);
            }
        }

        if r.borrow().base_type.to_smolstr() == "ListView" {
            let mut seen_for = false;
            for se in node.children() {
                if se.kind() == SyntaxKind::RepeatedElement && !seen_for {
                    seen_for = true;
                } else if matches!(
                    se.kind(),
                    SyntaxKind::SubElement
                        | SyntaxKind::ConditionalElement
                        | SyntaxKind::RepeatedElement
                        | SyntaxKind::ChildrenPlaceholder
                ) {
                    diag.push_error("A ListView can just have a single 'for' as children. Anything else is not supported".into(), &se)
                }
            }
        }

        interfaces::validate_self_implement_statements(&r.borrow(), &implemented_interfaces, diag);
        interfaces::apply_child_implement_statements(&r, child_implements, diag);

        r
    }

    fn from_sub_element_node(
        node: syntax_nodes::SubElement,
        parent_type: ElementType,
        component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
        declared_slots: &mut Vec<DeclaredSlot>,
        is_in_legacy_component: bool,
        diag: &mut BuildDiagnostics,
        tr: &TypeRegister,
    ) -> ElementRc {
        let mut id = parser::identifier_text(&node).unwrap_or_default();
        if matches!(id.as_ref(), "parent" | "self" | "root") {
            diag.push_error(
                format!("'{id}' is a reserved id"),
                &node.child_token(SyntaxKind::Identifier).unwrap(),
            );
            id = SmolStr::default();
        }
        Element::from_node(
            node.Element(),
            id,
            parent_type,
            component_child_insertion_points,
            declared_slots,
            is_in_legacy_component,
            diag,
            tr,
        )
    }

    fn assert_experimental_slots(
        diagnostics: &mut BuildDiagnostics,
        node: &SyntaxNode,
        what: &str,
    ) -> bool {
        if diagnostics.enable_experimental {
            return true;
        }
        diagnostics.push_error(format!("'{what}' is an experimental feature"), node);
        false
    }

    fn sub_element_slot_placeholder_name(
        node: &SyntaxNode,
        declared_slots: &[DeclaredSlot],
    ) -> Option<SmolStr> {
        if node.child_token(SyntaxKind::ColonEqual).is_some() {
            return None;
        }
        let element = node.child_node(SyntaxKind::Element)?;
        if element.children().any(|c| c.kind() != SyntaxKind::QualifiedName) {
            return None;
        }
        let qualified_name = element.child_node(SyntaxKind::QualifiedName)?;
        if qualified_name.child_token(SyntaxKind::Dot).is_some() {
            return None;
        }
        let name = parser::identifier_text(&qualified_name)?;
        declared_slots.iter().any(|slot| slot.name == name).then_some(name)
    }

    fn mark_placeholder_rejected(declared_slots: &mut [DeclaredSlot], name: &str) {
        if let Some(slot) = declared_slots.iter_mut().find(|slot| slot.name.as_str() == name) {
            slot.has_rejected_placeholder = true;
        }
    }

    fn reject_slot_placeholders(
        diagnostics: &mut BuildDiagnostics,
        declared_slots: &mut [DeclaredSlot],
        insertion_points: BTreeMap<String, ChildrenInsertionPoint>,
        context: &str,
    ) {
        for (name, ChildrenInsertionPoint { node, .. }) in insertion_points {
            Self::mark_placeholder_rejected(declared_slots, &name);
            diagnostics.push_error(
                format!("{} cannot appear in {context}", slot_error_subject(&name)),
                &node,
            );
        }
    }

    fn register_slot_placeholder(
        node: &SyntaxNode,
        slot_name: SmolStr,
        parent: &ElementRc,
        component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
        diagnostics: &mut BuildDiagnostics,
        type_register: &TypeRegister,
    ) {
        Self::assert_experimental_slots(diagnostics, node, "named slots");
        if let Some(existing) = component_child_insertion_points.get(slot_name.as_str()) {
            if matches!(existing.node, ChildInsertionPointNode::SlotForwarding(_)) {
                diagnostics.push_error(
                    format!(
                        "The slot '{slot_name}' cannot be forwarded and used as a placeholder in the same component"
                    ),
                    node,
                );
            } else {
                diagnostics.push_error(
                    format!(
                        "{} can only appear once in an element",
                        slot_error_subject(&slot_name)
                    ),
                    node,
                );
            }
            return;
        }
        if type_register.lookup_element(slot_name.as_str()).is_ok() {
            diagnostics.push_warning(
                format!(
                    "{} shadows an element type of the same name. This element is a slot placeholder, not an instance of '{slot_name}'",
                    slot_error_subject(&slot_name)
                ),
                node,
            );
        }
        let insertion_index = parent.borrow().children.len();
        component_child_insertion_points.insert(
            slot_name.to_string(),
            ChildrenInsertionPoint {
                parent: parent.clone(),
                insertion_index,
                node: ChildInsertionPointNode::SlotPlaceholder(node.clone().into()),
            },
        );
    }

    fn from_repeated_node(
        node: syntax_nodes::RepeatedElement,
        parent: &ElementRc,
        component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
        declared_slots: &mut Vec<DeclaredSlot>,
        is_in_legacy_component: bool,
        diag: &mut BuildDiagnostics,
        tr: &TypeRegister,
    ) -> ElementRc {
        #[cfg(feature = "slint-sc")]
        diag.slint_sc_error("Repeated elements (for-in) are", &node);
        let e = Element::from_sub_element_node(
            node.SubElement(),
            parent.borrow().base_type.clone(),
            component_child_insertion_points,
            declared_slots,
            is_in_legacy_component,
            diag,
            tr,
        );
        let parent_is_listview = {
            let parent = parent.borrow();
            parent.base_type.to_string() == "ListView"
                // Custom "ListView" is OK, but it must have these properties
                && [
                    "content-y",
                    "content-height",
                    "content-width",
                    "visible-height",
                    "visible-width",
                ]
                .iter()
                .all(|p| parent.lookup_property(p).property_type == Type::LogicalLength)
        };
        let is_listview = if parent_is_listview
            && let Some(geometry_props) = e.borrow().geometry_props.as_ref()
        {
            let parent_elem = parent.borrow();
            // Check if content-width and content-height are explicitly set by the user,
            // either under their own name or through the deprecated viewport-* aliases.
            let (content_width_is_explicitly_set, content_height_is_explicitly_set) = {
                let has_binding = |name| parent_elem.binding(name).is_some_and(|b| b.has_binding());
                (
                    has_binding("content-width") || has_binding("viewport-width"),
                    has_binding("content-height") || has_binding("viewport-height"),
                )
            };
            drop(parent_elem); // Drop the borrow before creating NamedReference

            let lvi = ListViewInfo {
                content_y: NamedReference::new(parent, SmolStr::new_static("content-y")),
                content_height: (!content_height_is_explicitly_set)
                    .then(|| NamedReference::new(parent, SmolStr::new_static("content-height"))),
                content_width: (!content_width_is_explicitly_set)
                    .then(|| NamedReference::new(parent, SmolStr::new_static("content-width"))),
                listview_height: NamedReference::new(parent, SmolStr::new_static("visible-height")),
                listview_width: NamedReference::new(parent, SmolStr::new_static("visible-width")),
            };
            // these properties are set by the ListView layouting code
            if let Some(content_height) = &lvi.content_height {
                content_height.mark_as_set();
            }
            if let Some(content_width) = &lvi.content_width {
                content_width.mark_as_set();
            }
            geometry_props.y.mark_as_set();
            Some(lvi)
        } else {
            None
        };
        let rei = RepeatedElementInfo {
            model: Expression::Uncompiled(node.Expression().into()),
            model_data_id: node
                .DeclaredIdentifier()
                .and_then(|n| parser::identifier_text(&n))
                .unwrap_or_default(),
            index_id: node
                .RepeatedIndex()
                .and_then(|r| parser::identifier_text(&r))
                .unwrap_or_default(),
            is_conditional_element: false,
            is_listview,
        };
        e.borrow_mut().repeated = Some(rei);
        e
    }

    fn from_conditional_node(
        node: syntax_nodes::ConditionalElement,
        parent_type: ElementType,
        component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
        declared_slots: &mut Vec<DeclaredSlot>,
        is_in_legacy_component: bool,
        diag: &mut BuildDiagnostics,
        tr: &TypeRegister,
    ) -> ElementRc {
        #[cfg(feature = "slint-sc")]
        diag.slint_sc_error("Conditional elements (if) are", &node);
        let rei = RepeatedElementInfo {
            model: Expression::Uncompiled(node.Expression().into()),
            model_data_id: SmolStr::default(),
            index_id: SmolStr::default(),
            is_conditional_element: true,
            is_listview: None,
        };
        let e = Element::from_sub_element_node(
            node.SubElement(),
            parent_type,
            component_child_insertion_points,
            declared_slots,
            is_in_legacy_component,
            diag,
            tr,
        );
        e.borrow_mut().repeated = Some(rei);
        e
    }

    fn from_match_node(
        node: syntax_nodes::MatchElement,
        parent_type: ElementType,
        component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
        declared_slots: &mut Vec<DeclaredSlot>,
        is_in_legacy_component: bool,
        diag: &mut BuildDiagnostics,
        tr: &TypeRegister,
    ) -> Vec<ElementRc> {
        if !diag.enable_experimental {
            diag.push_error("match statements are an experimental feature".into(), &node);
        }
        let mut cases: Vec<ElementRc> = Vec::new();
        let expr = node.Expression();
        for case in node.MatchCase() {
            let Some(sub_element) = case.SubElement() else {
                continue;
            };
            let rei = RepeatedElementInfo {
                model: Expression::BinaryExpression {
                    lhs: (Box::new(Expression::Uncompiled(expr.clone().into()))),
                    rhs: Box::new(Expression::Uncompiled(case.Expression().into())),
                    op: '=',
                },
                model_data_id: SmolStr::default(),
                index_id: SmolStr::default(),
                is_conditional_element: true,
                is_listview: None,
            };
            let e: Rc<RefCell<Element>> = Element::from_sub_element_node(
                sub_element,
                parent_type.clone(),
                component_child_insertion_points,
                declared_slots,
                is_in_legacy_component,
                diag,
                tr,
            );
            e.borrow_mut().repeated = Some(rei);
            cases.push(e);
        }
        if let Some(wildcard) = node.WildcardMatchCase()
            && let Some(sub_element) = wildcard.SubElement()
        {
            let case_exprs: Vec<_> = node.MatchCase().collect();
            let mut condition = Expression::BinaryExpression {
                lhs: Box::new(Expression::Uncompiled(expr.clone().into())),
                rhs: Box::new(Expression::Uncompiled(case_exprs[0].Expression().into())),
                op: '!',
            };
            for case in &case_exprs[1..] {
                condition = Expression::BinaryExpression {
                    lhs: Box::new(condition),
                    rhs: Box::new(Expression::BinaryExpression {
                        lhs: Box::new(Expression::Uncompiled(expr.clone().into())),
                        rhs: Box::new(Expression::Uncompiled(case.Expression().into())),
                        op: '!',
                    }),
                    op: '&',
                };
            }
            let rei = RepeatedElementInfo {
                model: condition,
                model_data_id: SmolStr::default(),
                index_id: SmolStr::default(),
                is_conditional_element: true,
                is_listview: None,
            };
            let e = Element::from_sub_element_node(
                sub_element,
                parent_type.clone(),
                component_child_insertion_points,
                declared_slots,
                is_in_legacy_component,
                diag,
                tr,
            );
            e.borrow_mut().repeated = Some(rei);
            cases.push(e);
        }
        cases
    }

    /// Return the type of a property in this element or its base, along with the final name, in case
    /// the provided name points towards a property alias. Type::Invalid is returned if the property does
    /// not exist.
    pub fn lookup_property<'a>(&self, name: &'a str) -> PropertyLookupResult<'a> {
        self.property_declarations.get(name).map_or_else(
            || {
                let mut r = self.base_type.lookup_property(name);
                r.is_in_direct_base = r.is_local_to_component;
                r.is_local_to_component = false;
                r
            },
            |p| PropertyLookupResult {
                resolved_name: name.into(),
                property_type: p.property_type.clone(),
                property_visibility: p.visibility,
                declared_pure: p.pure,
                is_local_to_component: true,
                is_in_direct_base: false,
                is_shadowable: false,
                builtin_function: None,
                #[cfg(feature = "slint-sc")]
                is_slint_sc: false,
                deprecated: p.deprecated.clone(),
            },
        )
    }

    fn parse_bindings(
        &mut self,
        bindings: impl Iterator<Item = (crate::parser::SyntaxToken, SyntaxNode)>,
        is_in_legacy_component: bool,
        diag: &mut BuildDiagnostics,
    ) {
        for (name_token, b) in bindings {
            let unresolved_name = crate::parser::normalize_identifier(name_token.text());
            let lookup_result = self.lookup_property(&unresolved_name);
            #[cfg(feature = "slint-sc")]
            if lookup_result.is_valid() && !lookup_result.is_slint_sc {
                diag.slint_sc_error(&format!("The property '{unresolved_name}' is"), &name_token);
            }
            if !lookup_result.property_type.is_property_type() {
                match lookup_result.property_type {
                        Type::Invalid => {
                            if self.base_type != ElementType::Error {
                                let msg = if let Some(suggestion) = css_property_suggestion(&unresolved_name, &self.base_type) {
                                    suggestion
                                } else if self.base_type.to_smolstr() == "Empty" {
                                    format!( "Unknown property {unresolved_name}")
                                } else {
                                    format!( "Unknown property {unresolved_name} in {}", self.base_type)
                                };
                                diag.push_error(msg, &name_token);
                            }
                        }
                        Type::Callback { .. } => {
                            diag.push_error(format!("'{unresolved_name}' is a callback. Use `=>` to connect"),
                            &name_token)
                        }
                        _ => diag.push_error(format!(
                            "Cannot assign to {} in {} because it does not have a valid property type",
                            unresolved_name, self.base_type,
                        ),
                        &name_token),
                    }
            } else if !lookup_result.is_local_to_component
                && (lookup_result.property_visibility == PropertyVisibility::Private
                    || lookup_result.property_visibility == PropertyVisibility::Output)
            {
                if is_in_legacy_component
                    && lookup_result.property_visibility == PropertyVisibility::Output
                {
                    diag.push_warning(
                        format!(
                            "Assigning to '{}' property '{unresolved_name}' is deprecated",
                            PropertyVisibility::Output
                        ),
                        &name_token,
                    );
                } else {
                    diag.push_error(
                        format!(
                            "Cannot assign to '{}' property '{}'",
                            lookup_result.property_visibility, unresolved_name
                        ),
                        &name_token,
                    );
                }
            }

            if *lookup_result.resolved_name != *unresolved_name {
                diag.push_property_deprecation_warning(
                    &unresolved_name,
                    &lookup_result.resolved_name,
                    &name_token,
                );
            } else if let Some(message) =
                lookup_result.deprecated.as_ref().filter(|_| !lookup_result.is_local_to_component)
            {
                diag.push_property_deprecation_warning_with_message(
                    &unresolved_name,
                    message,
                    &name_token,
                );
            }

            match self.bindings.0.entry(lookup_result.resolved_name.into()) {
                Entry::Occupied(_) => {
                    diag.push_error("Duplicated property binding".into(), &name_token);
                }
                Entry::Vacant(entry) => {
                    entry.insert(BindingExpression::new_uncompiled(b).into());
                }
            };
        }
    }

    fn slot_forwarding_expr_identifier(expression: &SyntaxNode) -> Option<SmolStr> {
        if expression.kind() != SyntaxKind::Expression {
            return None;
        }

        let mut expr_children = expression.children();
        let qualified_name = expr_children.find(|n| n.kind() == SyntaxKind::QualifiedName)?;
        if expr_children.next().is_some() {
            return None;
        }

        let mut identifiers = qualified_name
            .children_with_tokens()
            .filter(|n| n.kind() == SyntaxKind::Identifier)
            .filter_map(|n| n.into_token());
        let identifier = identifiers.next()?;
        if identifiers.next().is_some() {
            return None;
        }

        Some(crate::parser::normalize_identifier(identifier.text()))
    }

    /// Return the alias node of a `callback foo <=> ...;` declaration, if `name` is one.
    ///
    /// This lives on the callback declaration itself, which is where the alias of a
    /// global callback that also has a handler ends up (the handler takes the binding
    /// expression slot).
    pub fn callback_alias_declaration_node(
        &self,
        name: &str,
    ) -> Option<syntax_nodes::TwoWayBinding> {
        self.property_declarations
            .get(name)
            .and_then(|d| d.node.clone())
            .and_then(syntax_nodes::CallbackDeclaration::new)
            .and_then(|cb| cb.TwoWayBinding())
    }

    /// Return the two-way-binding syntax node of a `<=>` alias for the given property, if any.
    ///
    /// Usually the alias is the binding's own (uncompiled) expression. But a global
    /// callback may both alias another global's callback (`callback foo <=> Other.foo;`)
    /// and provide a handler (`foo => { ... }`): the handler then occupies the binding
    /// expression slot, so the alias node lives on the callback declaration instead.
    pub fn two_way_binding_node(&self, name: &str) -> Option<syntax_nodes::TwoWayBinding> {
        if let Some(binding) = self.bindings.0.get(name)
            && let Ok(b) = binding.try_borrow()
            && let Expression::Uncompiled(node) = b.value_expression()
            && let Some(twb) = syntax_nodes::TwoWayBinding::new(node.clone())
        {
            return Some(twb);
        }
        self.callback_alias_declaration_node(name)
    }

    pub fn native_class(&self) -> Option<Arc<NativeClass>> {
        let mut base_type = self.base_type.clone();
        loop {
            match &base_type {
                ElementType::Component(component) => {
                    base_type = component.root_element.clone().borrow().base_type.clone();
                }
                ElementType::Builtin(builtin) => break Some(builtin.native_class.clone()),
                ElementType::Native(native) => break Some(native.clone()),
                _ => break None,
            }
        }
    }

    pub fn builtin_type(&self) -> Option<Rc<BuiltinElement>> {
        let mut base_type = self.base_type.clone();
        loop {
            match &base_type {
                ElementType::Component(component) => {
                    base_type = component.root_element.clone().borrow().base_type.clone();
                }
                ElementType::Builtin(builtin) => break Some(builtin.clone()),
                _ => break None,
            }
        }
    }

    pub fn layout_info_prop(&self, orientation: Orientation) -> Option<&NamedReference> {
        self.layout_info_prop.as_ref().map(|prop| match orientation {
            Orientation::Horizontal => &prop.0,
            Orientation::Vertical => &prop.1,
        })
    }

    /// Whether this element is a *builtin* whose vertical layout info
    /// depends on its width. Returns `false` for user components — even
    /// ones whose own bindings derive height from width (e.g.
    /// `component Foo { height: self.width; }`); those don't carry the
    /// information needed to detect the dependency here. The synthesis
    /// pass catches user components by other means (descendant
    /// height-for-width + `layoutinfo-v-with-constraint` propagation).
    pub fn is_builtin_height_for_width(&self) -> bool {
        let Some(builtin) = self.builtin_type() else { return false };
        match builtin.name.as_str() {
            // Conservatively treat any wrap binding (including a literal
            // `no-wrap`) as height-for-width.
            "Text" | "TextInput" => self.is_binding_set("wrap", false),
            // An Image is height-for-width only while its height comes from its
            // width; with an explicit height it is fixed, so it is not.
            "Image" | "ClippedImage" => !self.is_binding_set("height", true),
            // Markdown text always wraps to fill the given width.
            "StyledText" => true,
            _ => false,
        }
    }

    /// Returns the `layoutinfo-v-with-constraint` NamedReference reachable
    /// from `self`, looking through the base-type chain. The NR points to
    /// the element actually carrying the binding.
    pub fn inherited_layout_info_v_with_constraint(&self) -> Option<NamedReference> {
        if let Some(nr) = &self.layout_info_v_with_constraint {
            return Some(nr.clone());
        }
        let mut base = self.base_type.clone();
        while let ElementType::Component(base_comp) = base {
            let root = base_comp.root_element.borrow();
            if let Some(nr) = &root.layout_info_v_with_constraint {
                return Some(nr.clone());
            }
            base = root.base_type.clone();
        }
        None
    }

    /// Whether [`Self::inherited_layout_info_v_with_constraint`] would return
    /// `Some`, without cloning the `NamedReference`.
    pub fn has_inherited_layout_info_v_with_constraint(&self) -> bool {
        if self.layout_info_v_with_constraint.is_some() {
            return true;
        }
        let mut base = self.base_type.clone();
        while let ElementType::Component(base_comp) = base {
            let root = base_comp.root_element.borrow();
            if root.layout_info_v_with_constraint.is_some() {
                return true;
            }
            base = root.base_type.clone();
        }
        false
    }

    /// Mirror of [`Self::inherited_layout_info_v_with_constraint`] for the
    /// horizontal axis.
    pub fn inherited_layout_info_h_with_constraint(&self) -> Option<NamedReference> {
        if let Some(nr) = &self.layout_info_h_with_constraint {
            return Some(nr.clone());
        }
        let mut base = self.base_type.clone();
        while let ElementType::Component(base_comp) = base {
            let root = base_comp.root_element.borrow();
            if let Some(nr) = &root.layout_info_h_with_constraint {
                return Some(nr.clone());
            }
            base = root.base_type.clone();
        }
        None
    }

    /// Whether this element's `layoutinfo-{orientation}` already incorporates its
    /// own explicit min/max/preferred/stretch constraints. True for elements with
    /// a `layoutinfo-*` property (layouts, sub-components) or an inherited
    /// `layoutinfo-*-with-constraint` function (a component forwarding a
    /// height-for-width layout).
    ///
    /// A parent layout must then NOT re-apply the cell's explicit constraints on
    /// top of the measured info — they are already included, and re-reading them
    /// unconstrained can reintroduce a height-for-width binding loop. Only native
    /// items (no `layoutinfo-*`) need their constraints applied separately.
    pub fn layout_info_includes_own_constraints(&self, orientation: Orientation) -> bool {
        self.layout_info_prop(orientation).is_some()
            || match orientation {
                Orientation::Vertical => self.inherited_layout_info_v_with_constraint().is_some(),
                Orientation::Horizontal => self.inherited_layout_info_h_with_constraint().is_some(),
            }
    }

    /// Returns the element's name as specified in the markup, not normalized.
    pub fn original_name(&self) -> SmolStr {
        self.debug
            .first()
            .and_then(|n| n.node.child_token(parser::SyntaxKind::Identifier))
            .map(|n| n.to_smolstr())
            .unwrap_or_else(|| self.id.clone())
    }

    /// Return true if the binding is set, either on this element or in a base
    ///
    /// If `need_explicit` is true, then only consider binding set in the code, not the ones set
    /// by the compiler later.
    ///
    /// Synthetic debug hooks (materialized for unbound properties) are never considered set
    /// (`has_binding` treats them as "no expression").
    pub fn is_binding_set(self: &Element, property_name: &str, need_explicit: bool) -> bool {
        if self.bindings.0.get(property_name).is_some_and(|b| {
            b.borrow().has_binding() && (!need_explicit || b.borrow().priority > 0)
        }) {
            true
        } else if let ElementType::Component(base) = &self.base_type {
            base.root_element.borrow().is_binding_set(property_name, need_explicit)
        } else {
            false
        }
    }

    /// Returns true if the property is set by a binding or an assignment expression
    ///
    /// Synthetic debug hooks (materialized for unbound properties) are not considered set.
    pub fn is_property_set(self: &Element, property_name: &str) -> bool {
        self.bindings
            .0
            .get(property_name)
            .is_some_and(|b| !b.borrow().expression.is_synthetic_debug_hook())
            || self
                .property_analysis
                .borrow()
                .get(property_name)
                .is_some_and(|a| a.is_set || a.is_linked)
            || matches!(&self.base_type, ElementType::Component(base) if base.root_element.borrow().is_property_set(property_name))
    }

    /// The binding for `property_name`, if one exists and is not a synthetic debug hook.
    ///
    /// This is the hook-aware replacement for `self.bindings.get(..)`: a synthetic debug hook
    /// is a materialized placeholder for an *unbound* property and must read as "no binding".
    /// Use this instead of the raw map whenever the question is "did anything bind this
    /// property" or "what is this property's binding".
    pub fn binding(&self, property_name: &str) -> Option<Ref<'_, BindingExpression>> {
        self.bindings
            .0
            .get(property_name)
            .filter(|binding| !binding.borrow().expression.is_synthetic_debug_hook())
            .map(|binding| binding.borrow())
    }

    /// Same as [`Self::binding`], but returns a mutable reference to the binding.
    pub fn binding_mut(&self, property_name: &str) -> Option<RefMut<'_, BindingExpression>> {
        self.bindings
            .0
            .get(property_name)
            .filter(|binding| !binding.borrow().expression.is_synthetic_debug_hook())
            .map(|binding| binding.borrow_mut())
    }

    /// Iterate over the bindings that are not synthetic debug hooks.
    ///
    /// The hook-aware replacement for iterating `self.bindings` directly when enumerating the
    /// properties that are actually set on this element.
    pub fn real_bindings(&self) -> impl Iterator<Item = (&SmolStr, &RefCell<BindingExpression>)> {
        self.bindings
            .0
            .iter()
            .filter(|(_, binding)| !binding.borrow().expression.is_synthetic_debug_hook())
    }

    /// Iterate over every binding entry, including synthetic debug hooks.
    ///
    /// The counterpart to [`Self::real_bindings`]. Use only where synthetic hooks must be lowered
    /// or emitted (codegen, LLR, native-class selection); prefer `real_bindings()` everywhere else.
    pub fn bindings_including_synthetic(
        &self,
    ) -> impl Iterator<Item = (&SmolStr, &RefCell<BindingExpression>)> {
        self.bindings.0.iter()
    }

    /// The raw binding cell for `property_name`, including a synthetic debug hook.
    ///
    /// Returns the `&RefCell` rather than a borrow guard, so callers that need to borrow, drop,
    /// and re-borrow within one scope (reentrant binding analysis) or use `try_borrow` can do so.
    /// Does not filter synthetic hooks — prefer [`Self::binding`] unless synthetic hooks matter.
    pub fn binding_cell_including_synthetic(
        &self,
        property_name: &str,
    ) -> Option<&RefCell<BindingExpression>> {
        self.bindings.0.get(property_name)
    }

    /// Set the property `property_name` of this Element only if it was not set.
    /// the `expression_fn` will only be called if it isn't set.
    ///
    /// If a synthetic debug hook exists for this property, the hook's inner expression is
    /// replaced with the new value (keeping the wrapper and id) and the hook is marked
    /// non-synthetic — so the property becomes live-editable at its real computed value.
    ///
    /// Returns true if the binding was changed.
    pub fn set_binding_if_not_set(
        &mut self,
        property_name: SmolStr,
        expression_fn: impl FnOnce() -> Expression,
    ) -> bool {
        if self.is_binding_set(&property_name, false) {
            return false;
        }

        match self.bindings.0.entry(property_name) {
            Entry::Vacant(vacant_entry) => {
                let mut binding: BindingExpression = expression_fn().into();
                binding.priority = i32::MAX;
                vacant_entry.insert(binding.into());
            }
            Entry::Occupied(mut existing_entry) => {
                let inner = existing_entry.get_mut().get_mut();
                let mut binding: BindingExpression = expression_fn().into();
                binding.priority = i32::MAX;
                // merge_with takes care of overwriting synthetic debug hooks.
                inner.merge_with(&binding);
            }
        };
        true
    }

    /// Unconditionally set the property `property_name` to `new_binding`, but handle a synthetic
    /// debug hook that already occupies the slot specially: instead of replacing it, upgrade it
    /// in-place (replace its inner expression, clear `synthetic`, keep the wrapper+id so the
    /// property stays live-editable).
    ///
    /// Returns the old `BindingExpression` if a *real* (non-synthetic) binding was displaced so
    /// the caller can report a conflict.  Returns `None` if the slot was empty or held only a
    /// synthetic hook (no conflict).
    ///
    /// This is intended for passes like `lower_layout` that must force-set a property and need to
    /// distinguish a genuine conflict from a synthetic placeholder.
    pub fn set_binding(
        &mut self,
        property_name: SmolStr,
        mut new_binding: BindingExpression,
    ) -> Option<BindingExpression> {
        match self.bindings.0.entry(property_name) {
            Entry::Vacant(v) => {
                v.insert(RefCell::new(new_binding));
                None
            }
            Entry::Occupied(mut e) => {
                let existing = e.get_mut().get_mut();
                if let expression_tree::Expression::DebugHook { expression: _, synthetic, id } =
                    &mut existing.expression
                    && *synthetic
                {
                    // Adopt the previous synthetic debug hook into the new binding, which is now no
                    // longer synthetic.
                    let new_debug_hook = expression_tree::Expression::DebugHook {
                        expression: Box::new(new_binding.expression),
                        id: id.clone(),
                        synthetic: false,
                    };
                    new_binding.expression = new_debug_hook;
                    *existing = new_binding;
                    // previously the binding was only a synthetic debug hook - don't report a
                    // conflict
                    return None;
                }
                // Real (non-synthetic) binding exists: replace it and report the conflict.
                Some(std::mem::replace(e.get_mut().get_mut(), new_binding))
            }
        }
    }

    /// Remove the binding for `property_name` and return it.
    ///
    /// The map entry is removed whether it held a real binding or only a synthetic debug hook,
    /// but the result is `Some` only for a real binding — a synthetic-only slot reads as `None`,
    /// matching "nothing was ever bound here". Dropping a leftover synthetic hook is safe for the
    /// lowering passes that consume, rename, or delete a property this way.
    pub fn take_binding(&mut self, property_name: &str) -> Option<BindingExpression> {
        self.take_binding_including_synthetic(property_name)
            .filter(|binding| !binding.expression.is_synthetic_debug_hook())
    }

    /// Remove the binding for `property_name` and return it (including synthetic debug hooks).
    pub fn take_binding_including_synthetic(
        &mut self,
        property_name: &str,
    ) -> Option<BindingExpression> {
        self.bindings.0.remove(property_name).map(RefCell::into_inner)
    }

    /// Remove and return the whole binding map, including synthetic debug hooks.
    ///
    /// For bulk transfers that move an element's bindings wholesale.
    pub(crate) fn take_bindings_including_synthetic(&mut self) -> BindingsMap {
        std::mem::take(&mut self.bindings.0)
    }

    /// Add the given binding entries, including synthetic debug hooks, to this element.
    ///
    /// For bulk transfers; like `BTreeMap::extend`, an entry with the same name is overwritten
    /// (no merge or priority adjustment).
    pub(crate) fn extend_bindings_including_synthetic(
        &mut self,
        bindings: impl IntoIterator<Item = (SmolStr, RefCell<BindingExpression>)>,
    ) {
        self.bindings.0.extend(bindings);
    }

    pub fn sub_component(&self) -> Option<&Rc<Component>> {
        if self.repeated.is_some() {
            None
        } else if let ElementType::Component(sub_component) = &self.base_type {
            Some(sub_component)
        } else {
            None
        }
    }

    pub fn element_infos(&self) -> String {
        let mut debug_infos = self.debug.clone();
        let mut base = self.base_type.clone();
        while let ElementType::Component(b) = base {
            let elem = b.root_element.borrow();
            base = elem.base_type.clone();
            debug_infos.extend(elem.debug.iter().cloned());
        }

        let (infos, _, _) = debug_infos.into_iter().fold(
            (String::new(), false, true),
            |(mut infos, elem_boundary, first), debug_info| {
                if elem_boundary {
                    infos.push('/');
                } else if !first {
                    infos.push(';');
                }

                infos.push_str(&debug_info.encoded_element_info());
                (infos, debug_info.element_boundary, false)
            },
        );
        infos
    }
}

/// For FlexboxLayout, suggest Slint property names for CSS properties.
fn css_property_suggestion(property_name: &str, base_type: &ElementType) -> Option<String> {
    let base_name = base_type.to_smolstr();
    if base_name != "FlexboxLayout" {
        return None;
    }
    match property_name {
        "gap" => Some("Use spacing instead of gap".into()),
        "row-gap" => Some("Use spacing-vertical instead of row-gap".into()),
        "column-gap" => Some("Use spacing-horizontal instead of column-gap".into()),
        "justify-content" => Some("Use alignment instead of justify-content".into()),
        _ => None,
    }
}

/// Apply default property values defined in `builtins.slint` to the element.
pub(crate) fn apply_default_type_properties(element: &mut Element) {
    // Apply default property values on top:
    if let ElementType::Builtin(builtin_base) = &element.base_type {
        for (prop, info) in &builtin_base.properties {
            if let BuiltinPropertyDefault::Expr(expr) = &info.default_value {
                element.bindings.0.entry(prop.clone()).or_insert_with(|| {
                    let mut binding = BindingExpression::from(expr.clone());
                    binding.priority = i32::MAX;
                    RefCell::new(binding)
                });
            }
        }
    }
}

/// Create a Type for this node
pub fn type_from_node(
    node: syntax_nodes::Type,
    diag: &mut BuildDiagnostics,
    tr: &TypeRegister,
) -> Type {
    if let Some(qualified_type_node) = node.QualifiedName() {
        let qualified_type = QualifiedTypeName::from_node(qualified_type_node.clone());

        let prop_type = tr.lookup_qualified(&qualified_type.members);

        #[cfg(feature = "slint-sc")]
        if !prop_type.is_slint_sc() {
            diag.slint_sc_error(&format!("The type '{qualified_type}' is"), &qualified_type_node);
        }

        if prop_type == Type::Invalid && tr.lookup_element(&qualified_type.to_smolstr()).is_err() {
            diag.push_error(format!("Unknown type '{qualified_type}'"), &qualified_type_node);
        } else if !prop_type.is_property_type() {
            diag.push_error(
                format!("'{qualified_type}' is not a valid type"),
                &qualified_type_node,
            );
        }
        prop_type
    } else if let Some(object_node) = node.ObjectType() {
        #[cfg(feature = "slint-sc")]
        diag.slint_sc_error("Inline struct types are", &object_node);
        type_struct_from_node(object_node, diag, tr, None, None)
    } else if let Some(array_node) = node.ArrayType() {
        #[cfg(feature = "slint-sc")]
        diag.slint_sc_error("Array types are", &array_node);
        Type::Array(Arc::new(type_from_node(array_node.Type(), diag, tr)))
    } else {
        assert!(diag.has_errors());
        Type::Invalid
    }
}

/// Create a [`Type::Struct`] from a [`syntax_nodes::ObjectType`]
///
/// `symbol_counters` is only available for named struct declarations,
/// where field default values (`struct Foo { bar: int = 42 }`) are supported.
pub fn type_struct_from_node(
    object_node: syntax_nodes::ObjectType,
    diag: &mut BuildDiagnostics,
    tr: &TypeRegister,
    name: Option<SmolStr>,
    symbol_counters: Option<&Rc<crate::symbol_counters::SymbolCounters>>,
) -> Type {
    let mut field_defaults = BTreeMap::default();
    let mut field_order = Vec::new();
    let fields: BTreeMap<SmolStr, Type> = object_node
        .ObjectTypeMember()
        .map(|member| {
            let field_name = parser::identifier_text(&member).unwrap_or_default();
            field_order.push(field_name.clone());
            let field_ty = type_from_node(member.Type(), diag, tr);
            if let Some(default_value_node) = member.Expression() {
                if name.is_none() {
                    diag.push_error(
                        "Field default values are only supported in named struct declarations"
                            .into(),
                        &default_value_node,
                    );
                } else if let Some(expr) = resolve_struct_field_default_value(
                    default_value_node,
                    &field_ty,
                    diag,
                    tr,
                    symbol_counters.expect("named struct declarations have symbol counters"),
                ) {
                    field_defaults.insert(field_name.clone(), expr);
                }
            }
            (field_name, field_ty)
        })
        .collect();
    // The `@rust-attr` attributes and the declaration node live on the
    // enclosing `StructDeclaration` (the parent of the `ObjectType`).
    let struct_decl = object_node.parent();
    Type::Struct(Arc::new(Struct {
        fields,
        field_defaults,
        name: name.map_or(StructName::None, |name| {
            let rust_attributes = struct_decl
                .as_ref()
                .and_then(|p| syntax_nodes::StructDeclaration::new(p.clone()))
                .map(|d| d.AtRustAttr().map(|a| SmolStr::from(a.text().to_string())).collect())
                .unwrap_or_default();
            let node = crate::langtype::DeclNode::new(struct_decl.as_ref().unwrap_or(&object_node));
            StructName::User { name, node, rust_attributes, field_order }
        }),
    }))
}

/// Resolve, type-check, and constant-fold the default value expression of a struct field.
/// Returns `None` (with a diagnostic) if the expression is not a compile time constant.
fn resolve_struct_field_default_value(
    node: syntax_nodes::Expression,
    field_ty: &Type,
    diag: &mut BuildDiagnostics,
    tr: &TypeRegister,
    symbol_counters: &Rc<crate::symbol_counters::SymbolCounters>,
) -> Option<crate::langtype::ConstantExpression> {
    #[cfg(feature = "slint-sc")]
    diag.slint_sc_error("Struct field default values are", &node);
    let mut expr = {
        let mut ctx = crate::lookup::LookupCtx::empty_context(tr, diag, symbol_counters.clone());
        ctx.property_type = field_ty.clone();
        Expression::from_expression_node(node.clone(), &mut ctx).maybe_convert_to(
            field_ty.clone(),
            &node,
            ctx.diag,
            &ctx.symbol_counters,
        )
    };
    crate::passes::const_propagation::fold_const_expression(&mut expr);
    // An error was already reported when a part of the expression failed to resolve
    // or convert; don't report a confusing constant-ness error on top of it
    let mut has_invalid = false;
    expr.visit_recursive(&mut |e| has_invalid |= matches!(e, Expression::Invalid));
    if has_invalid {
        return None;
    }
    let constant = crate::langtype::ConstantExpression::from_expression(&expr);
    if constant.is_none() {
        let reason = non_constant_expression_reason(&expr)
            .map_or_else(Default::default, |reason| format!(": {reason}"));
        diag.push_error(
            format!("The default value of a struct field must be a constant expression{reason}"),
            &node,
        );
    }
    constant
}

/// A user-facing explanation of what makes the expression non-constant, if there is
/// a better one than "it is not in the supported subset"
fn non_constant_expression_reason(expr: &Expression) -> Option<String> {
    use crate::expression_tree::{BuiltinFunction, Callable};
    let mut reason = None;
    expr.visit_recursive(&mut |e| {
        if reason.is_some() {
            return;
        }
        reason = match e {
            Expression::PropertyReference(nr) => {
                Some(format!("it references the property '{}'", nr.name()))
            }
            Expression::FunctionCall { function, .. } => match function {
                Callable::Function(nr) => Some(format!("it calls the function '{}'", nr.name())),
                Callable::Callback(nr) => Some(format!("it calls the callback '{}'", nr.name())),
                Callable::Builtin(BuiltinFunction::GetWindowScaleFactor) => Some(
                    "the conversion to logical pixels depends on the window's scale factor".into(),
                ),
                Callable::Builtin(BuiltinFunction::GetWindowDefaultFontSize) => Some(
                    "the conversion from 'rem' depends on the window's default font size".into(),
                ),
                Callable::Builtin(BuiltinFunction::Translate) => {
                    Some("the translation is selected at run-time".into())
                }
                Callable::Builtin(_) => Some("functions are not evaluated at compile time".into()),
            },
            _ => None,
        };
    });
    reason
}

fn animation_element_from_node(
    anim: &syntax_nodes::PropertyAnimation,
    prop_name: &syntax_nodes::QualifiedName,
    prop_type: Type,
    diag: &mut BuildDiagnostics,
    tr: &TypeRegister,
) -> Option<ElementRc> {
    let anim_type = tr.property_animation_type_for_property(prop_type);
    if !matches!(anim_type, ElementType::Builtin(..)) {
        diag.push_error(
            format!(
                "'{}' is not a property that can be animated",
                prop_name.text().to_string().trim()
            ),
            prop_name,
        );
        None
    } else {
        let mut anim_element =
            Element { id: "".into(), base_type: anim_type, ..Default::default() };
        anim_element.parse_bindings(
            anim.Binding().filter_map(|b| {
                Some((b.child_token(SyntaxKind::Identifier)?, b.BindingExpression().into()))
            }),
            false,
            diag,
        );

        apply_default_type_properties(&mut anim_element);

        Some(Rc::new(RefCell::new(anim_element)))
    }
}

#[derive(Default, Debug, Clone)]
pub struct QualifiedTypeName {
    pub members: Vec<SmolStr>,
}

impl QualifiedTypeName {
    pub fn from_node(node: syntax_nodes::QualifiedName) -> Self {
        debug_assert_eq!(node.kind(), SyntaxKind::QualifiedName);
        let members = node
            .children_with_tokens()
            .filter(|n| n.kind() == SyntaxKind::Identifier)
            .filter_map(|x| x.as_token().map(|x| crate::parser::normalize_identifier(x.text())))
            .collect();
        Self { members }
    }

    pub fn to_smolstr(&self) -> SmolStr {
        self.members.join(".").into()
    }
}

impl Display for QualifiedTypeName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.members.join("."))
    }
}

/// Return a NamedReference for a qualified name used in a state (or transition),
/// if the reference is invalid, there will be a diagnostic
fn lookup_property_from_qualified_name_for_state(
    node: syntax_nodes::QualifiedName,
    r: &ElementRc,
    diag: &mut BuildDiagnostics,
) -> Option<(NamedReference, Type)> {
    let qualname = QualifiedTypeName::from_node(node.clone());
    match qualname.members.as_slice() {
        [unresolved_prop_name] => {
            let lookup_result = r.borrow().lookup_property(unresolved_prop_name.as_ref());
            if !lookup_result.property_type.is_property_type() {
                diag.push_error(format!("'{qualname}' is not a valid property"), &node);
            } else if !lookup_result.is_valid_for_assignment() {
                diag.push_error(
                    format!(
                        "'{}' cannot be set in a state because it is '{}'",
                        qualname, lookup_result.property_visibility
                    ),
                    &node,
                );
            }
            Some((
                NamedReference::new(r, lookup_result.resolved_name.to_smolstr()),
                lookup_result.property_type,
            ))
        }
        [elem_id, unresolved_prop_name] => {
            if let Some(element) = find_element_by_id(r, elem_id.as_ref()) {
                let lookup_result = element.borrow().lookup_property(unresolved_prop_name.as_ref());
                if !lookup_result.is_valid() {
                    diag.push_error(
                        format!("'{unresolved_prop_name}' not found in '{elem_id}'"),
                        &node,
                    );
                } else if !lookup_result.is_valid_for_assignment() {
                    diag.push_error(
                        format!(
                            "'{}' cannot be set in a state because it is '{}'",
                            qualname, lookup_result.property_visibility
                        ),
                        &node,
                    );
                }
                Some((
                    NamedReference::new(&element, lookup_result.resolved_name.to_smolstr()),
                    lookup_result.property_type,
                ))
            } else {
                diag.push_error(format!("'{elem_id}' is not a valid element id"), &node);
                None
            }
        }
        _ => {
            diag.push_error(format!("'{qualname}' is not a valid property"), &node);
            None
        }
    }
}

/// FIXME: this is duplicated the resolving pass. Also, we should use a hash table
fn find_element_by_id(e: &ElementRc, name: &str) -> Option<ElementRc> {
    if e.borrow().id == name {
        return Some(e.clone());
    }
    for x in &e.borrow().children {
        if x.borrow().repeated.is_some() {
            continue;
        }
        if let Some(x) = find_element_by_id(x, name) {
            return Some(x);
        }
    }

    None
}

/// Find the parent element to a given element.
/// (since there is no parent mapping we need to fo an exhaustive search)
pub fn find_parent_element(e: &ElementRc) -> Option<ElementRc> {
    fn recurse(base: &ElementRc, e: &ElementRc) -> Option<ElementRc> {
        for child in &base.borrow().children {
            if Rc::ptr_eq(child, e) {
                return Some(base.clone());
            }
            if let Some(x) = recurse(child, e) {
                return Some(x);
            }
        }
        None
    }

    let root = e.borrow().enclosing_component.upgrade().unwrap().root_element.clone();
    if Rc::ptr_eq(&root, e) {
        return None;
    }
    recurse(&root, e)
}

/// Call the visitor for each children of the element recursively, starting with the element itself
///
/// The state returned by the visitor is passed to the children
pub fn recurse_elem<State>(
    elem: &ElementRc,
    state: &State,
    vis: &mut impl FnMut(&ElementRc, &State) -> State,
) {
    let state = vis(elem, state);
    for sub in &elem.borrow().children {
        recurse_elem(sub, &state, vis);
    }
}

/// Same as [`recurse_elem`] but include the elements from sub_components
pub fn recurse_elem_including_sub_components<State>(
    component: &Component,
    state: &State,
    vis: &mut impl FnMut(&ElementRc, &State) -> State,
) {
    recurse_elem(&component.root_element, state, &mut |elem, state| {
        debug_assert!(std::ptr::eq(
            component as *const Component,
            (&*elem.borrow().enclosing_component.upgrade().unwrap()) as *const Component
        ));
        if elem.borrow().repeated.is_some()
            && let ElementType::Component(base) = &elem.borrow().base_type
            && base.parent_element().is_some()
        {
            recurse_elem_including_sub_components(base, state, vis);
        }
        vis(elem, state)
    });
    component
        .popup_windows
        .borrow()
        .iter()
        .for_each(|p| recurse_elem_including_sub_components(&p.component, state, vis));
    component
        .menu_item_tree
        .borrow()
        .iter()
        .for_each(|c| recurse_elem_including_sub_components(c, state, vis));
}

/// Same as recurse_elem, but will take the children from the element as to not keep the element borrow
pub fn recurse_elem_no_borrow<State>(
    elem: &ElementRc,
    state: &State,
    vis: &mut impl FnMut(&ElementRc, &State) -> State,
) {
    let state = vis(elem, state);
    let children = elem.borrow().children.clone();
    for sub in &children {
        recurse_elem_no_borrow(sub, &state, vis);
    }
}

/// Same as [`recurse_elem`] but include the elements form sub_components
pub fn recurse_elem_including_sub_components_no_borrow<State>(
    component: &Component,
    state: &State,
    vis: &mut impl FnMut(&ElementRc, &State) -> State,
) {
    recurse_elem_no_borrow(&component.root_element, state, &mut |elem, state| {
        let base = if elem.borrow().repeated.is_some() {
            if let ElementType::Component(base) = &elem.borrow().base_type {
                if base.parent_element().is_some() {
                    Some(base.clone())
                } else {
                    // The process_repeater_components pass was not run yet
                    None
                }
            } else {
                None
            }
        } else {
            None
        };
        if let Some(base) = base {
            recurse_elem_including_sub_components_no_borrow(&base, state, vis);
        }
        vis(elem, state)
    });
    component
        .popup_windows
        .borrow()
        .iter()
        .for_each(|p| recurse_elem_including_sub_components_no_borrow(&p.component, state, vis));
    component
        .menu_item_tree
        .borrow()
        .iter()
        .for_each(|c| recurse_elem_including_sub_components_no_borrow(c, state, vis));
}

/// Visit the model expression of `elem`, if `elem` is the body of a `for`.
///
/// The expression is temporarily moved out of `repeated.model` so the visitor
/// can mutate it without holding a borrow on `elem`.
pub fn visit_repeater_model_expression(
    elem: &ElementRc,
    mut vis: impl FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
) {
    let repeated = elem
        .borrow_mut()
        .repeated
        .as_mut()
        .map(|r| (std::mem::take(&mut r.model), r.is_conditional_element));
    if let Some((mut model, is_cond)) = repeated {
        vis(&mut model, None, &|| if is_cond { Type::Bool } else { Type::Model });
        elem.borrow_mut().repeated.as_mut().unwrap().model = model;
    }
}

/// Like [`visit_element_expressions`] but skips the repeater model
/// expression. Use [`visit_repeater_model_expression`] separately for that.
pub fn visit_element_expressions_excluding_repeater_model(
    elem: &ElementRc,
    mut vis: impl FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
) {
    fn visit_element_expressions_simple(
        elem: &ElementRc,
        vis: &mut impl FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
    ) {
        for (name, expr) in elem.borrow().bindings_including_synthetic() {
            vis(&mut expr.borrow_mut(), Some(name.as_str()), &|| {
                elem.borrow().lookup_property(name).property_type
            });

            for twb in &mut expr.borrow_mut().two_way_bindings {
                if let expression_tree::TwoWayBinding::ModelData { repeated_element, .. } = twb {
                    let mut e =
                        Expression::RepeaterModelReference { element: repeated_element.clone() };
                    vis(&mut e, None, &|| Type::Invalid);
                    if let Expression::RepeaterModelReference { element } = e {
                        *repeated_element = element;
                    }
                }
            }

            match &mut expr.borrow_mut().animation {
                Some(PropertyAnimation::Static(e)) => visit_element_expressions_simple(e, vis),
                Some(PropertyAnimation::Transition { animations, state_ref }) => {
                    vis(state_ref, None, &|| Type::Int32);
                    for a in animations {
                        visit_element_expressions_simple(&a.animation, vis)
                    }
                }
                None => (),
            }
        }
    }

    visit_element_expressions_simple(elem, &mut vis);

    for expr in elem.borrow().change_callbacks.values() {
        for expr in expr.borrow_mut().iter_mut() {
            vis(expr, Some("$change callback$"), &|| Type::Void);
        }
    }

    let mut states = std::mem::take(&mut elem.borrow_mut().states);
    for s in &mut states {
        if let Some(cond) = s.condition.as_mut() {
            vis(cond, None, &|| Type::Bool)
        }
        for (ne, e, _) in &mut s.property_changes {
            vis(e, Some(ne.name()), &|| {
                ne.element().borrow().lookup_property(ne.name()).property_type
            });
        }
    }
    elem.borrow_mut().states = states;

    let mut transitions = std::mem::take(&mut elem.borrow_mut().transitions);
    for t in &mut transitions {
        for (_, _, a) in &mut t.property_animations {
            visit_element_expressions_simple(a, &mut vis);
        }
    }
    elem.borrow_mut().transitions = transitions;

    let component = elem.borrow().enclosing_component.upgrade().unwrap();
    if Rc::ptr_eq(&component.root_element, elem) {
        for e in component.init_code.borrow_mut().iter_mut() {
            vis(e, None, &|| Type::Void);
        }
    }
}

pub fn visit_element_expressions(
    elem: &ElementRc,
    mut vis: impl FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
) {
    visit_repeater_model_expression(elem, &mut vis);
    visit_element_expressions_excluding_repeater_model(elem, &mut vis);
}

pub fn visit_named_references_in_expression(
    expr: &mut Expression,
    vis: &mut impl FnMut(&mut NamedReference),
) {
    expr.visit_mut(|sub| visit_named_references_in_expression(sub, vis));
    match expr {
        Expression::PropertyReference(r) => vis(r),
        Expression::FunctionCall {
            function: Callable::Callback(r) | Callable::Function(r),
            ..
        } => vis(r),
        Expression::LayoutCacheAccess { layout_cache_prop, .. } => vis(layout_cache_prop),
        Expression::GridRepeaterCacheAccess { layout_cache_prop, .. } => vis(layout_cache_prop),
        Expression::OrganizeGridLayout(l) => l.visit_named_references(vis),
        Expression::ComputeBoxLayoutInfo { layout, .. } => layout.visit_named_references(vis),
        Expression::ComputeFlexboxLayoutInfo { layout, .. } => layout.visit_named_references(vis),
        Expression::ComputeGridLayoutInfo { layout_organized_data_prop, layout, .. } => {
            vis(layout_organized_data_prop);
            layout.visit_named_references(vis);
        }
        Expression::SolveBoxLayout(l, _) => l.visit_named_references(vis),
        Expression::SolveFlexboxLayout(l) => l.visit_named_references(vis),
        Expression::SolveGridLayout { layout_organized_data_prop, layout, .. } => {
            vis(layout_organized_data_prop);
            layout.visit_named_references(vis);
        }
        // This is not really a named reference, but the result is the same, it need to be updated
        // FIXME: this should probably be lowered into a PropertyReference
        Expression::RepeaterModelReference { element }
        | Expression::RepeaterIndexReference { element } => {
            // FIXME: this is questionable
            let mut nc =
                NamedReference::new(&element.upgrade().unwrap(), SmolStr::new_static("$model"));
            vis(&mut nc);
            debug_assert!(nc.element().borrow().repeated.is_some());
            *element = Rc::downgrade(&nc.element());
        }
        _ => {}
    }
}

/// Visit all the named reference in an element
/// But does not recurse in sub-elements. (unlike [`visit_all_named_references`] which recurse)
pub fn visit_all_named_references_in_element(
    elem: &ElementRc,
    mut vis: impl FnMut(&mut NamedReference),
) {
    visit_element_expressions(elem, |expr, _, _| {
        visit_named_references_in_expression(expr, &mut vis)
    });
    let mut states = std::mem::take(&mut elem.borrow_mut().states);
    for s in &mut states {
        for (r, _, _) in &mut s.property_changes {
            vis(r);
        }
    }
    elem.borrow_mut().states = states;
    let mut transitions = std::mem::take(&mut elem.borrow_mut().transitions);
    for t in &mut transitions {
        for (r, _, _) in &mut t.property_animations {
            vis(r)
        }
    }
    elem.borrow_mut().transitions = transitions;
    let mut repeated = std::mem::take(&mut elem.borrow_mut().repeated);
    if let Some(r) = &mut repeated
        && let Some(lv) = &mut r.is_listview
    {
        vis(&mut lv.content_y);
        if let Some(content_height) = &mut lv.content_height {
            vis(content_height);
        }
        if let Some(content_width) = &mut lv.content_width {
            vis(content_width);
        }
        vis(&mut lv.listview_height);
        vis(&mut lv.listview_width);
    }
    elem.borrow_mut().repeated = repeated;
    let mut layout_info_prop = std::mem::take(&mut elem.borrow_mut().layout_info_prop);
    layout_info_prop.as_mut().map(|(h, b)| (vis(h), vis(b)));
    elem.borrow_mut().layout_info_prop = layout_info_prop;
    let mut constrained_v = std::mem::take(&mut elem.borrow_mut().layout_info_v_with_constraint);
    if let Some(nr) = constrained_v.as_mut() {
        vis(nr);
    }
    elem.borrow_mut().layout_info_v_with_constraint = constrained_v;
    let mut constrained_h = std::mem::take(&mut elem.borrow_mut().layout_info_h_with_constraint);
    if let Some(nr) = constrained_h.as_mut() {
        vis(nr);
    }
    elem.borrow_mut().layout_info_h_with_constraint = constrained_h;
    let mut debug = std::mem::take(&mut elem.borrow_mut().debug);
    for d in debug.iter_mut() {
        if let Some(l) = d.layout.as_mut() {
            l.visit_named_references(&mut vis)
        }
    }
    elem.borrow_mut().debug = debug;

    let mut accessibility_props = std::mem::take(&mut elem.borrow_mut().accessibility_props);
    accessibility_props.0.iter_mut().for_each(|(_, x)| vis(x));
    elem.borrow_mut().accessibility_props = accessibility_props;

    let geometry_props = elem.borrow_mut().geometry_props.take();
    if let Some(mut geometry_props) = geometry_props {
        vis(&mut geometry_props.x);
        vis(&mut geometry_props.y);
        vis(&mut geometry_props.width);
        vis(&mut geometry_props.height);
        elem.borrow_mut().geometry_props = Some(geometry_props);
    }

    // visit two way bindings
    for (_, expr) in elem.borrow().real_bindings() {
        for twb in &mut expr.borrow_mut().two_way_bindings {
            if let expression_tree::TwoWayBinding::Property { property, .. } = twb {
                vis(property);
            }
        }
    }

    let mut property_declarations = std::mem::take(&mut elem.borrow_mut().property_declarations);
    for pd in property_declarations.values_mut() {
        pd.is_alias.as_mut().map(&mut vis);
    }
    elem.borrow_mut().property_declarations = property_declarations;

    // Visit grid_layout_cell for repeated Row elements
    let grid_layout_cell = elem.borrow_mut().grid_layout_cell.take();
    if let Some(grid_layout_cell) = grid_layout_cell {
        grid_layout_cell.borrow_mut().visit_named_references(&mut vis);
        elem.borrow_mut().grid_layout_cell = Some(grid_layout_cell);
    }
}

/// Visit all named reference in this component and sub component
pub fn visit_all_named_references(
    component: &Component,
    vis: &mut impl FnMut(&mut NamedReference),
) {
    recurse_elem_including_sub_components_no_borrow(
        component,
        &Weak::new(),
        &mut |elem, parent_compo| {
            visit_all_named_references_in_element(elem, |nr| vis(nr));
            let compo = elem.borrow().enclosing_component.clone();
            if !Weak::ptr_eq(parent_compo, &compo) {
                let compo = compo.upgrade().unwrap();
                compo.root_constraints.borrow_mut().visit_named_references(vis);
                compo.popup_windows.borrow_mut().iter_mut().for_each(|p| {
                    vis(&mut p.x);
                    vis(&mut p.y);
                    if let Some(is_open) = &mut p.is_open {
                        vis(is_open);
                    }
                });
                compo.timers.borrow_mut().iter_mut().for_each(|t| {
                    vis(&mut t.interval);
                    vis(&mut t.triggered);
                    vis(&mut t.running);
                });
                for o in compo.optimized_elements.borrow().iter() {
                    visit_element_expressions(o, |expr, _, _| {
                        visit_named_references_in_expression(expr, vis)
                    });
                }
            }
            compo
        },
    );
}

/// Visit all expression in this component and sub components
///
/// Does not recurse in the expression itself
pub fn visit_all_expressions(
    component: &Component,
    mut vis: impl FnMut(&mut Expression, &dyn Fn() -> Type),
) {
    recurse_elem_including_sub_components(component, &Weak::new(), &mut |elem, parent_compo| {
        visit_element_expressions(elem, |expr, _, ty| vis(expr, ty));
        let compo = elem.borrow().enclosing_component.clone();
        if !Weak::ptr_eq(parent_compo, &compo) {
            let compo = compo.upgrade().unwrap();
            for o in compo.optimized_elements.borrow().iter() {
                visit_element_expressions(o, |expr, _, ty| vis(expr, ty));
            }
        }
        compo
    })
}

#[derive(Debug, Clone)]
pub struct State {
    pub id: SmolStr,
    pub condition: Option<Expression>,
    pub property_changes: Vec<(NamedReference, Expression, syntax_nodes::StatePropertyChange)>,
}

#[derive(Debug, Clone)]
pub struct Transition {
    pub direction: TransitionDirection,
    pub state_id: SmolStr,
    pub property_animations: Vec<(NamedReference, SourceLocation, ElementRc)>,
    pub node: syntax_nodes::Transition,
}

impl Transition {
    fn from_node(
        trs: syntax_nodes::Transition,
        r: &ElementRc,
        tr: &TypeRegister,
        diag: &mut BuildDiagnostics,
    ) -> Transition {
        if let Some(star) = trs.child_token(SyntaxKind::Star) {
            diag.push_error("catch-all not yet implemented".into(), &star);
        };
        let direction_text = trs
            .first_child_or_token()
            .and_then(|t| t.as_token().map(|tok| tok.text().to_string()))
            .unwrap_or_default();

        Transition {
            direction: match direction_text.as_str() {
                "in" => TransitionDirection::In,
                "out" => TransitionDirection::Out,
                "in-out" => TransitionDirection::InOut,
                "in_out" => TransitionDirection::InOut,
                _ => {
                    unreachable!("Unknown transition direction: '{}'", direction_text);
                }
            },
            state_id: trs
                .DeclaredIdentifier()
                .and_then(|x| parser::identifier_text(&x))
                .unwrap_or_default(),
            property_animations: trs
                .PropertyAnimation()
                .flat_map(|pa| pa.QualifiedName().map(move |qn| (pa.clone(), qn)))
                .filter_map(|(pa, qn)| {
                    lookup_property_from_qualified_name_for_state(qn.clone(), r, diag).and_then(
                        |(ne, prop_type)| {
                            animation_element_from_node(&pa, &qn, prop_type, diag, tr)
                                .map(|anim_element| (ne, qn.to_source_location(), anim_element))
                        },
                    )
                })
                .collect(),
            node: trs.clone(),
        }
    }
}

#[derive(Clone, Debug, derive_more::Deref)]
pub struct ExportedName {
    #[deref]
    pub name: SmolStr, // normalized
    pub name_ident: SyntaxNode,
}

impl ExportedName {
    pub fn original_name(&self) -> SmolStr {
        self.name_ident
            .child_token(parser::SyntaxKind::Identifier)
            .map(|n| n.to_smolstr())
            .unwrap_or_else(|| self.name.clone())
    }

    pub fn from_export_specifier(
        export_specifier: &syntax_nodes::ExportSpecifier,
    ) -> (SmolStr, ExportedName) {
        let internal_name =
            parser::identifier_text(&export_specifier.ExportIdentifier()).unwrap_or_default();

        let (name, name_ident): (SmolStr, SyntaxNode) = export_specifier
            .ExportName()
            .and_then(|ident| {
                parser::identifier_text(&ident).map(|text| (text, ident.clone().into()))
            })
            .unwrap_or_else(|| (internal_name.clone(), export_specifier.ExportIdentifier().into()));
        (internal_name, ExportedName { name, name_ident })
    }
}

#[derive(Default, Debug, derive_more::Deref)]
pub struct Exports {
    #[deref]
    components_or_types: Vec<(ExportedName, Either<Rc<Component>, Type>)>,
}

impl Exports {
    pub fn from_node(
        doc: &syntax_nodes::Document,
        inner_components: &[Rc<Component>],
        type_registry: &TypeRegister,
        diag: &mut BuildDiagnostics,
    ) -> Self {
        let resolve_export_to_inner_component_or_import =
            |internal_name: &str, internal_name_node: &dyn Spanned, diag: &mut BuildDiagnostics| {
                if let Ok(ElementType::Component(c)) = type_registry.lookup_element(internal_name) {
                    Some(Either::Left(c))
                } else if let ty @ Type::Struct { .. } | ty @ Type::Enumeration(_) =
                    type_registry.lookup(internal_name)
                {
                    Some(Either::Right(ty))
                } else if type_registry.lookup_element(internal_name).is_ok()
                    || type_registry.lookup(internal_name) != Type::Invalid
                {
                    diag.push_error(
                        format!("Cannot export '{internal_name}' because it is not a component",),
                        internal_name_node,
                    );
                    None
                } else {
                    diag.push_error(format!("'{internal_name}' not found",), internal_name_node);
                    None
                }
            };

        // Collect all exports from the three sources, then sort once (O(n log n))
        // instead of insertion sort (O(n²))
        let mut exports_with_duplicates: Vec<(ExportedName, Either<Rc<Component>, Type>)> =
            Vec::new();

        // Source 1: ExportSpecifiers
        exports_with_duplicates.extend(
            doc.ExportsList()
                // re-export are handled in the TypeLoader::load_dependencies_recursively_impl
                .filter(|exports| exports.ExportModule().is_none())
                .flat_map(|exports| exports.ExportSpecifier())
                .filter_map(|export_specifier| {
                    let (internal_name, exported_name) =
                        ExportedName::from_export_specifier(&export_specifier);
                    Some((
                        exported_name,
                        resolve_export_to_inner_component_or_import(
                            &internal_name,
                            &export_specifier.ExportIdentifier(),
                            diag,
                        )?,
                    ))
                }),
        );

        // Source 2: Exported components
        exports_with_duplicates.extend(
            doc.ExportsList().flat_map(|exports| exports.Component()).filter_map(|component| {
                let name_ident: SyntaxNode = component.DeclaredIdentifier().into();
                let name =
                    parser::identifier_text(&component.DeclaredIdentifier()).unwrap_or_else(|| {
                        debug_assert!(diag.has_errors());
                        SmolStr::default()
                    });

                let compo_or_type =
                    resolve_export_to_inner_component_or_import(&name, &name_ident, diag)?;

                Some((ExportedName { name, name_ident }, compo_or_type))
            }),
        );

        // Source 3: Exported structs and enums
        exports_with_duplicates.extend(
            doc.ExportsList()
                .flat_map(|exports| {
                    exports
                        .StructDeclaration()
                        .map(|st| st.DeclaredIdentifier())
                        .chain(exports.EnumDeclaration().map(|en| en.DeclaredIdentifier()))
                })
                .filter_map(|name_ident| {
                    let name = parser::identifier_text(&name_ident).unwrap_or_else(|| {
                        debug_assert!(diag.has_errors());
                        SmolStr::default()
                    });

                    let name_ident = name_ident.into();

                    let compo_or_type =
                        resolve_export_to_inner_component_or_import(&name, &name_ident, diag)?;

                    Some((ExportedName { name, name_ident }, compo_or_type))
                }),
        );

        exports_with_duplicates.sort_by(|(a, _), (b, _)| a.name.cmp(&b.name));

        let mut sorted_deduped_exports = Vec::with_capacity(exports_with_duplicates.len());
        let mut it = exports_with_duplicates.into_iter().peekable();
        while let Some((exported_name, compo_or_type)) = it.next() {
            let mut warning_issued_on_first_occurrence = false;

            // Skip over duplicates and issue warnings
            while it.peek().map(|(name, _)| &name.name) == Some(&exported_name.name) {
                let message = format!("Duplicated export '{}'", exported_name.name);

                if !warning_issued_on_first_occurrence {
                    diag.push_error(message.clone(), &exported_name.name_ident);
                    warning_issued_on_first_occurrence = true;
                }

                let duplicate_loc = it.next().unwrap().0.name_ident;
                diag.push_error(message.clone(), &duplicate_loc);
            }

            sorted_deduped_exports.push((exported_name, compo_or_type));
        }

        if let Some(last_compo) = inner_components.last() {
            let name = last_compo.id.clone();
            if last_compo.is_global() {
                if sorted_deduped_exports.is_empty() {
                    diag.push_warning("Global singleton is implicitly marked for export. This is deprecated and it should be explicitly exported".into(), &last_compo.node.as_ref().map(|n| n.to_source_location()));
                    sorted_deduped_exports.push((
                        ExportedName { name, name_ident: doc.clone().into() },
                        Either::Left(last_compo.clone()),
                    ))
                }
            } else if !sorted_deduped_exports
                .iter()
                .any(|e| e.1.as_ref().left().is_some_and(|c| !c.is_global()))
            {
                diag.push_warning("Component is implicitly marked for export. This is deprecated and it should be explicitly exported".into(), &last_compo.node.as_ref().map(|n| n.to_source_location()));
                let insert_pos = sorted_deduped_exports
                    .partition_point(|(existing_export, _)| existing_export.name <= name);
                sorted_deduped_exports.insert(
                    insert_pos,
                    (
                        ExportedName { name, name_ident: doc.clone().into() },
                        Either::Left(last_compo.clone()),
                    ),
                )
            }
        }
        Self { components_or_types: sorted_deduped_exports }
    }

    pub fn add_reexports(
        &mut self,
        other_exports: impl IntoIterator<Item = (ExportedName, Either<Rc<Component>, Type>)>,
        diag: &mut BuildDiagnostics,
    ) {
        for export in other_exports {
            match self.components_or_types.binary_search_by(|entry| entry.0.cmp(&export.0)) {
                Ok(_) => {
                    diag.push_warning(
                        format!(
                            "'{}' is already exported in this file; it will not be re-exported",
                            *export.0
                        ),
                        &export.0.name_ident,
                    );
                }
                Err(insert_pos) => {
                    self.components_or_types.insert(insert_pos, export);
                }
            }
        }
    }

    pub fn find(&self, name: &str) -> Option<Either<Rc<Component>, Type>> {
        self.components_or_types
            .binary_search_by(|(exported_name, _)| exported_name.as_str().cmp(name))
            .ok()
            .map(|index| self.components_or_types[index].1.clone())
    }

    pub fn retain(
        &mut self,
        func: impl FnMut(&mut (ExportedName, Either<Rc<Component>, Type>)) -> bool,
    ) {
        self.components_or_types.retain_mut(func)
    }

    pub(crate) fn snapshot(&self, snapshotter: &mut crate::typeloader::Snapshotter) -> Self {
        let components_or_types = self
            .components_or_types
            .iter()
            .map(|(en, either)| {
                let en = en.clone();
                let either = match either {
                    itertools::Either::Left(l) => itertools::Either::Left({
                        Weak::upgrade(&snapshotter.use_component(l))
                            .expect("Component should cleanly upgrade here")
                    }),
                    itertools::Either::Right(r) => itertools::Either::Right(r.clone()),
                };
                (en, either)
            })
            .collect();

        Self { components_or_types }
    }
}

impl std::iter::IntoIterator for Exports {
    type Item = (ExportedName, Either<Rc<Component>, Type>);

    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.components_or_types.into_iter()
    }
}

/// Re-declare the constrained layout-info functions on an injected wrapper, forwarding
/// to the element that carries them.
///
/// `inherited_layout_info_*_with_constraint` walks the base-type chain, but the element
/// declaring those functions is now the wrapper's *child*, so without this the wrapper
/// reports "not height-for-width / width-for-height" and the layout falls back to reading
/// the cell's own width/height — which the parent layout is still computing (binding loop).
///
/// The function must be re-declared rather than the `NamedReference` copied: callers
/// assert it points at the component's root element, which the wrapper now is.
fn forward_layout_info_with_constraint(new_root: &ElementRc, old_root: &ElementRc) {
    let span = old_root.borrow().to_source_location();
    let forward_to = |nr: NamedReference| Expression::FunctionCall {
        function: Callable::Function(NamedReference::new(old_root, nr.name().clone())),
        arguments: vec![Expression::FunctionParameterReference {
            index: 0,
            ty: Type::LogicalLength,
        }],
        source_location: None,
    };
    if let Some(nr) = old_root.borrow().inherited_layout_info_v_with_constraint() {
        crate::passes::lower_layout::synthesize_layoutinfo_v_with_constraint_on(
            new_root,
            span.clone(),
            forward_to(nr),
        );
    }
    if let Some(nr) = old_root.borrow().inherited_layout_info_h_with_constraint() {
        crate::passes::lower_layout::synthesize_layoutinfo_h_with_constraint_on(
            new_root,
            span,
            forward_to(nr),
        );
    }
}

/// This function replace the root element of a repeated element. the previous root becomes the only
/// child of the new root element.
/// Note that no reference to the base component must exist outside of repeated_element.base_type
pub fn inject_element_as_repeated_element(repeated_element: &ElementRc, new_root: ElementRc) {
    let component = repeated_element.borrow().base_type.as_component().clone();
    // Since we're going to replace the repeated element's component, we need to assert that
    // outside this function no strong reference exists to it. Then we can unwrap and
    // replace the root element.
    debug_assert_eq!(Rc::strong_count(&component), 2);
    let old_root = &component.root_element;

    adjust_geometry_for_injected_parent(&new_root, old_root);

    // Any elements with a weak reference to the repeater's component will need fixing later.
    let mut elements_with_enclosing_component_reference = Vec::new();
    recurse_elem(old_root, &(), &mut |element: &ElementRc, _| {
        if let Some(enclosing_component) = element.borrow().enclosing_component.upgrade()
            && Rc::ptr_eq(&enclosing_component, &component)
        {
            elements_with_enclosing_component_reference.push(element.clone());
        }
    });
    elements_with_enclosing_component_reference
        .extend_from_slice(component.optimized_elements.borrow().as_slice());
    elements_with_enclosing_component_reference.push(new_root.clone());

    new_root.borrow_mut().child_of_layout =
        std::mem::replace(&mut old_root.borrow_mut().child_of_layout, false);
    // The injected parent becomes the repeated element, so it takes over the grid cell role.
    new_root.borrow_mut().grid_layout_cell = old_root.borrow_mut().grid_layout_cell.take();
    // Likewise it takes over the flexbox cell role, so the flex item-info accessor is
    // generated on the wrapper the layout actually calls it on.
    if old_root.borrow().child_of_flexbox {
        new_root.borrow_mut().child_of_flexbox = true;
        // That accessor reads the flex item properties from the repeated root (now the
        // wrapper). Link them to the inner element that still carries the bindings
        // (and the FlexboxLayout's captured references keeping them alive), rather
        // than moving them, which would leave those references dangling.
        for prop in
            ["flex-grow", "flex-shrink", "flex-basis", "flex-order", "cross-axis-self-alignment"]
                .iter()
        {
            if old_root.borrow().binding(prop).is_some() {
                new_root.borrow_mut().set_binding(
                    SmolStr::new_static(prop),
                    BindingExpression::new_two_way(
                        NamedReference::new(old_root, SmolStr::new_static(prop)).into(),
                    ),
                );
            }
        }
    }
    let layout_info_prop = old_root.borrow().layout_info_prop.clone().or_else(|| {
        // generate the layout_info_prop that forward to the implicit layout for that item
        let li_v = crate::layout::create_new_prop(
            &new_root,
            SmolStr::new_static("layoutinfo-v"),
            crate::typeregister::layout_info_type().into(),
        );
        let li_h = crate::layout::create_new_prop(
            &new_root,
            SmolStr::new_static("layoutinfo-h"),
            crate::typeregister::layout_info_type().into(),
        );
        let expr_h = crate::layout::implicit_layout_info_call(
            old_root,
            Orientation::Horizontal,
            crate::layout::BuiltinFilter::All,
            None,
        )
        .unwrap();
        let expr_v = crate::layout::implicit_layout_info_call(
            old_root,
            Orientation::Vertical,
            crate::layout::BuiltinFilter::All,
            None,
        )
        .unwrap();
        let expr_v =
            BindingExpression::new_with_span(expr_v, old_root.borrow().to_source_location());
        li_v.element().borrow_mut().set_binding(li_v.name().clone(), expr_v);
        let expr_h =
            BindingExpression::new_with_span(expr_h, old_root.borrow().to_source_location());
        li_h.element().borrow_mut().set_binding(li_h.name().clone(), expr_h);
        Some((li_h.clone(), li_v.clone()))
    });
    new_root.borrow_mut().layout_info_prop = layout_info_prop;
    forward_layout_info_with_constraint(&new_root, old_root);

    // Replace the repeated component's element with our shadow element. That requires a bit of reference counting
    // surgery and relies on nobody having a strong reference left to the component, which we take out of the Rc.
    drop(std::mem::take(&mut repeated_element.borrow_mut().base_type));

    debug_assert_eq!(Rc::strong_count(&component), 1);

    let mut component = Rc::try_unwrap(component).expect("internal compiler error: more than one strong reference left to repeated component when lowering shadow properties");

    let old_root = std::mem::replace(&mut component.root_element, new_root.clone());
    new_root.borrow_mut().children.push(old_root);

    let component = Rc::new(component);
    repeated_element.borrow_mut().base_type = ElementType::Component(component.clone());

    for elem in elements_with_enclosing_component_reference {
        elem.borrow_mut().enclosing_component = Rc::downgrade(&component);
    }
}

/// Make the geometry of the `injected_parent` that of the old_elem. And the old_elem
/// will cover the `injected_parent`
pub fn adjust_geometry_for_injected_parent(injected_parent: &ElementRc, old_elem: &ElementRc) {
    let mut injected_parent_mut = injected_parent.borrow_mut();
    injected_parent_mut.set_binding(
        "z".into(),
        BindingExpression::new_two_way(
            NamedReference::new(old_elem, SmolStr::new_static("z")).into(),
        ),
    );
    // (should be removed by const propagation in the llr)
    injected_parent_mut.property_declarations.insert(
        "dummy".into(),
        PropertyDeclaration { property_type: Type::LogicalLength, ..Default::default() },
    );
    let mut old_elem_mut = old_elem.borrow_mut();
    injected_parent_mut.default_fill_parent = std::mem::take(&mut old_elem_mut.default_fill_parent);
    injected_parent_mut.geometry_props.clone_from(&old_elem_mut.geometry_props);
    drop(injected_parent_mut);
    old_elem_mut.geometry_props.as_mut().unwrap().x =
        NamedReference::new(injected_parent, SmolStr::new_static("dummy"));
    old_elem_mut.geometry_props.as_mut().unwrap().y =
        NamedReference::new(injected_parent, SmolStr::new_static("dummy"));
}
