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

/*! module for the C++ code generator
*/

// cSpell:ignore cmath constexpr cstdlib decltype intptr itertools nullptr prepended struc subcomponent uintptr vals compl consteval constinit glyphset glyphsets reflexpr

use super::accessor_names::{self, AccessorKind};
use crate::fileaccess;
use std::collections::HashSet;
use std::fmt::{Formatter, Write};
use std::sync::OnceLock;

use smol_str::{SmolStr, StrExt, format_smolstr};

/// The configuration for the C++ code generator
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Config {
    pub namespace: Option<String>,
    pub cpp_files: Vec<std::path::PathBuf>,
    pub header_include: String,
}

// Check if word is one of C++ keywords
fn is_cpp_keyword(word: &str) -> bool {
    static CPP_KEYWORDS: OnceLock<HashSet<&'static str>> = OnceLock::new();
    let keywords = CPP_KEYWORDS.get_or_init(|| {
        #[rustfmt::skip]
        let keywords: HashSet<&str> = HashSet::from([
            "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit",
            "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch",
            "char", "char8_t", "char16_t", "char32_t", "class", "compl", "concept", "const",
            "consteval", "constexpr", "constinit", "const_cast", "continue", "co_await",
            "co_return", "co_yield", "decltype", "default", "delete", "do", "double",
            "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float",
            "for", "friend", "goto", "if", "inline", "int", "long", "mutable", "namespace",
            "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private",
            "protected", "public", "reflexpr", "register", "reinterpret_cast", "requires",
            "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast",
            "struct", "switch", "synchronized", "template", "this", "thread_local", "throw",
            "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using",
            "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq",
        ]);
        keywords
    });
    keywords.contains(word)
}

pub fn ident(ident: &str) -> SmolStr {
    let mut new_ident = SmolStr::from(ident);
    if ident.contains('-') {
        new_ident = ident.replace_smolstr("-", "_");
    }
    if is_cpp_keyword(new_ident.as_str()) {
        new_ident = format_smolstr!("{}_", new_ident);
    }
    new_ident
}

pub fn concatenate_ident(ident: &str) -> SmolStr {
    if ident.contains('-') { ident.replace_smolstr("-", "_") } else { ident.into() }
}

/// Given a property reference to a native item (eg, the property name is empty)
/// return tokens to the `ItemRc`
fn access_item_rc(pr: &llr::MemberReference, ctx: &EvaluationContext) -> String {
    let mut component_access = "self->".into();

    let llr::MemberReference::Relative { parent_level, local_reference } = pr else {
        unreachable!()
    };
    let llr::LocalMemberIndex::Native { item_index, .. } = &local_reference.reference else {
        unreachable!()
    };

    for _ in 0..*parent_level {
        component_access = format!("{component_access}parent.lock().value()->");
    }

    let (sub_compo_path, sub_component) = follow_sub_component_path(
        ctx.compilation_unit,
        ctx.parent_sub_component_idx(*parent_level).unwrap(),
        &local_reference.sub_component_path,
    );
    if !local_reference.sub_component_path.is_empty() {
        component_access += &sub_compo_path;
    }
    let component_rc = format!("{component_access}self_weak.lock()->into_dyn()");
    let item_index_in_tree = sub_component.items[*item_index].index_in_tree;
    let item_index = if item_index_in_tree == 0 {
        format!("{component_access}tree_index")
    } else {
        format!("{component_access}tree_index_of_first_child + {item_index_in_tree} - 1")
    };

    format!("{component_rc}, {item_index}")
}

/// This module contains some data structure that helps represent a C++ code.
/// It is then rendered into an actual C++ text using the Display trait
pub mod cpp_ast {

    use std::cell::Cell;
    use std::fmt::{Display, Error, Formatter};

    use smol_str::{SmolStr, format_smolstr};

    thread_local!(static INDENTATION : Cell<u32> = const { Cell::new(0) });
    fn indent(f: &mut Formatter<'_>) -> Result<(), Error> {
        INDENTATION.with(|i| {
            for _ in 0..(i.get()) {
                write!(f, "    ")?;
            }
            Ok(())
        })
    }

    ///A full C++ file
    #[derive(Default, Debug)]
    pub struct File {
        pub is_cpp_file: bool,
        pub includes: Vec<SmolStr>,
        pub after_includes: String,
        pub namespace: Option<String>,
        pub declarations: Vec<Declaration>,
        pub resources: Vec<Declaration>,
        pub definitions: Vec<Declaration>,
    }

    impl File {
        #[allow(clippy::manual_checked_ops)]
        pub fn split_off_cpp_files(&mut self, header_file_name: String, count: usize) -> Vec<File> {
            let mut cpp_files = Vec::with_capacity(count);
            if count > 0 {
                let mut definitions = Vec::new();

                let mut i = 0;
                while i < self.definitions.len() {
                    if matches!(
                        &self.definitions[i],
                        Declaration::Function(Function { template_parameters: Some(..), .. })
                            | Declaration::TypeAlias(..)
                    ) {
                        i += 1;
                        continue;
                    }

                    definitions.push(self.definitions.remove(i));
                }

                let mut cpp_resources = self
                    .resources
                    .iter_mut()
                    .filter_map(|header_resource| match header_resource {
                        Declaration::Var(var) => {
                            var.is_extern = true;
                            Some(Declaration::Var(Var {
                                ty: var.ty.clone(),
                                name: var.name.clone(),
                                array_size: var.array_size,
                                init: std::mem::take(&mut var.init),
                                is_extern: false,
                                ..Default::default()
                            }))
                        }
                        _ => None,
                    })
                    .collect::<Vec<_>>();

                let cpp_includes = vec![format_smolstr!("\"{header_file_name}\"")];

                let def_chunk_size = definitions.len() / count;
                let res_chunk_size = cpp_resources.len() / count;
                cpp_files.extend((0..count - 1).map(|_| File {
                    is_cpp_file: true,
                    includes: cpp_includes.clone(),
                    after_includes: String::new(),
                    namespace: self.namespace.clone(),
                    declarations: Default::default(),
                    resources: cpp_resources.drain(0..res_chunk_size).collect(),
                    definitions: definitions.drain(0..def_chunk_size).collect(),
                }));

                cpp_files.push(File {
                    is_cpp_file: true,
                    includes: cpp_includes,
                    after_includes: String::new(),
                    namespace: self.namespace.clone(),
                    declarations: Default::default(),
                    resources: cpp_resources,
                    definitions,
                });

                cpp_files.resize_with(count, Default::default);
            }

            // Any definition in the header file is inline.
            self.definitions.iter_mut().for_each(|def| match def {
                Declaration::Function(f) => f.is_inline = true,
                Declaration::Var(v) => v.is_inline = true,
                _ => {}
            });

            cpp_files
        }
    }

    impl Display for File {
        fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
            writeln!(f, "// This file is auto-generated")?;
            if !self.is_cpp_file {
                writeln!(f, "#pragma once")?;
            }
            for i in &self.includes {
                writeln!(f, "#include {i}")?;
            }
            if let Some(namespace) = &self.namespace {
                writeln!(f, "namespace {namespace} {{")?;
                INDENTATION.with(|x| x.set(x.get() + 1));
            }

            write!(f, "{}", self.after_includes)?;
            for d in self.declarations.iter().chain(self.resources.iter()) {
                write!(f, "\n{d}")?;
            }
            for d in &self.definitions {
                write!(f, "\n{d}")?;
            }
            if let Some(namespace) = &self.namespace {
                writeln!(f, "}} // namespace {namespace}")?;
                INDENTATION.with(|x| x.set(x.get() - 1));
            }

            Ok(())
        }
    }

    /// Declarations  (top level, or within a struct)
    #[derive(Debug, derive_more::Display)]
    pub enum Declaration {
        Struct(Struct),
        Function(Function),
        Var(Var),
        TypeAlias(TypeAlias),
        Enum(Enum),
    }

    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
    pub enum Access {
        Public,
        Private,
        /*Protected,*/
    }

    #[derive(Default, Debug)]
    pub struct Struct {
        pub name: SmolStr,
        pub members: Vec<(Access, Declaration)>,
        pub friends: Vec<SmolStr>,
    }

    impl Display for Struct {
        fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
            indent(f)?;
            if self.members.is_empty() && self.friends.is_empty() {
                writeln!(f, "class {};", self.name)
            } else {
                writeln!(f, "class {} {{", self.name)?;
                INDENTATION.with(|x| x.set(x.get() + 1));
                let mut access = Access::Private;
                for m in &self.members {
                    if m.0 != access {
                        access = m.0;
                        indent(f)?;
                        match access {
                            Access::Public => writeln!(f, "public:")?,
                            Access::Private => writeln!(f, "private:")?,
                        }
                    }
                    write!(f, "{}", m.1)?;
                }
                for friend in &self.friends {
                    indent(f)?;
                    writeln!(f, "friend class {friend};")?;
                }
                INDENTATION.with(|x| x.set(x.get() - 1));
                indent(f)?;
                writeln!(f, "}};")
            }
        }
    }

    impl Struct {
        pub fn extract_definitions(&mut self) -> impl Iterator<Item = Declaration> + '_ {
            let struct_name = self.name.clone();
            self.members.iter_mut().filter_map(move |x| match &mut x.1 {
                Declaration::Function(f) if f.statements.is_some() => {
                    Some(Declaration::Function(Function {
                        name: format_smolstr!("{}::{}", struct_name, f.name),
                        signature: f.signature.clone(),
                        is_constructor_or_destructor: f.is_constructor_or_destructor,
                        is_static: false,
                        is_friend: false,
                        statements: f.statements.take(),
                        template_parameters: f.template_parameters.clone(),
                        constructor_member_initializers: f.constructor_member_initializers.clone(),
                        ..Default::default()
                    }))
                }
                _ => None,
            })
        }
    }

    #[derive(Default, Debug)]
    pub struct Enum {
        pub name: SmolStr,
        pub values: Vec<SmolStr>,
    }

    impl Display for Enum {
        fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
            indent(f)?;
            writeln!(f, "enum class {} {{", self.name)?;
            INDENTATION.with(|x| x.set(x.get() + 1));
            for value in &self.values {
                write!(f, "{value},")?;
            }
            INDENTATION.with(|x| x.set(x.get() - 1));
            indent(f)?;
            writeln!(f, "}};")
        }
    }

    /// Function or method
    #[derive(Default, Debug)]
    pub struct Function {
        pub name: SmolStr,
        /// "(...) -> ..."
        pub signature: String,
        /// The function does not have return type
        pub is_constructor_or_destructor: bool,
        pub is_static: bool,
        pub is_friend: bool,
        pub is_inline: bool,
        /// The list of statement instead the function.  When None,  this is just a function
        /// declaration without the definition
        pub statements: Option<Vec<String>>,
        /// What's inside template<...> if any
        pub template_parameters: Option<String>,
        /// Explicit initializers, such as FooClass::FooClass() : someMember(42) {}
        pub constructor_member_initializers: Vec<String>,
    }

    impl Display for Function {
        fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
            indent(f)?;
            if let Some(tpl) = &self.template_parameters {
                write!(f, "template<{tpl}> ")?;
            }
            if self.is_static {
                write!(f, "static ")?;
            }
            if self.is_friend {
                write!(f, "friend ")?;
            }
            if self.is_inline {
                write!(f, "inline ")?;
            }
            if !self.is_constructor_or_destructor {
                write!(f, "auto ")?;
            }
            write!(f, "{} {}", self.name, self.signature)?;
            if let Some(st) = &self.statements {
                if !self.constructor_member_initializers.is_empty() {
                    writeln!(f, "\n : {}", self.constructor_member_initializers.join(","))?;
                }
                writeln!(f, "{{")?;
                for s in st {
                    indent(f)?;
                    writeln!(f, "    {s}")?;
                }
                indent(f)?;
                writeln!(f, "}}")
            } else {
                writeln!(f, ";")
            }
        }
    }

    /// A variable or a member declaration.
    #[derive(Default, Debug)]
    pub struct Var {
        pub is_inline: bool,
        pub is_extern: bool,
        pub ty: SmolStr,
        pub name: SmolStr,
        pub array_size: Option<usize>,
        pub init: Option<String>,
    }

    impl Display for Var {
        fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
            indent(f)?;
            if self.is_extern {
                write!(f, "extern ")?;
            }
            if self.is_inline {
                write!(f, "inline ")?;
            }
            write!(f, "{} {}", self.ty, self.name)?;
            if let Some(size) = self.array_size {
                write!(f, "[{size}]")?;
            }
            if let Some(i) = &self.init {
                write!(f, " = {i}")?;
            }
            writeln!(f, ";")
        }
    }

    #[derive(Default, Debug)]
    pub struct TypeAlias {
        pub new_name: SmolStr,
        pub old_name: SmolStr,
    }

    impl Display for TypeAlias {
        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
            indent(f)?;
            writeln!(f, "using {} = {};", self.new_name, self.old_name)
        }
    }

    pub trait CppType {
        fn cpp_type(&self) -> Option<SmolStr>;
    }

    pub fn escape_string(str: &str) -> String {
        let mut result = String::with_capacity(str.len());
        for x in str.chars() {
            match x {
                '\n' => result.push_str("\\n"),
                '\\' => result.push_str("\\\\"),
                '\"' => result.push_str("\\\""),
                '\t' => result.push_str("\\t"),
                '\r' => result.push_str("\\r"),
                _ if !x.is_ascii() || (x as u32) < 32 => {
                    use std::fmt::Write;
                    write!(result, "\\U{:0>8x}", x as u32).unwrap();
                }
                _ => result.push(x),
            }
        }
        result
    }
}

use crate::CompilerConfiguration;
use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp};
use crate::langtype::{
    BuiltinStruct, Enumeration, EnumerationValue, NativeClass, StructName, Type,
};
use crate::layout::Orientation;
use crate::llr::lower_expression::lower_constant_expression;
use crate::llr::{
    self, EvaluationContext as llr_EvaluationContext, EvaluationScope, ParentScope,
    TypeResolutionContext as _,
};
use crate::object_tree::Document;
use cpp_ast::*;
use itertools::{Either, Itertools};
use std::cell::Cell;
use std::collections::{BTreeMap, BTreeSet};

const SHARED_GLOBAL_CLASS: &str = "SharedGlobals";

#[derive(Default)]
struct ConditionalIncludes {
    iostream: Cell<bool>,
    cstdlib: Cell<bool>,
    cmath: Cell<bool>,
}

#[derive(Clone)]
struct CppGeneratorContext<'a> {
    global_access: String,
    conditional_includes: &'a ConditionalIncludes,
}

type EvaluationContext<'a> = llr_EvaluationContext<'a, CppGeneratorContext<'a>>;

impl CppType for StructName {
    fn cpp_type(&self) -> Option<SmolStr> {
        match self {
            StructName::None => None,
            StructName::User { name, .. } => Some(ident(name)),
            StructName::Builtin(builtin) => builtin.cpp_type(),
        }
    }
}

impl CppType for BuiltinStruct {
    fn cpp_type(&self) -> Option<SmolStr> {
        let name: &'static str = self.into();
        match self {
            Self::Color | Self::LogicalPosition | Self::LogicalSize => {
                Some(format_smolstr!("slint::{}", name))
            }
            Self::PathMoveTo
            | Self::PathLineTo
            | Self::PathArcTo
            | Self::PathCubicTo
            | Self::PathQuadraticTo
            | Self::PathClose => Some(format_smolstr!("slint::private_api::{}", name)),
            s if s.is_public() => Some(format_smolstr!("slint::language::{}", name)),
            _ => Some(format_smolstr!("slint::cbindgen_private::{}", name)),
        }
    }
}

impl CppType for Type {
    fn cpp_type(&self) -> Option<SmolStr> {
        match self {
            Type::Void => Some("void".into()),
            Type::Float32 => Some("float".into()),
            Type::Int32 => Some("int".into()),
            Type::String => Some("slint::SharedString".into()),
            Type::Keys => Some("slint::Keys".into()),
            Type::Color => Some("slint::Color".into()),
            Type::Duration => Some("std::int64_t".into()),
            Type::Angle => Some("float".into()),
            Type::PhysicalLength => Some("float".into()),
            Type::LogicalLength => Some("float".into()),
            Type::Rem => Some("float".into()),
            Type::Percent => Some("float".into()),
            Type::Bool => Some("bool".into()),
            Type::Struct(s) => s.name.cpp_type().or_else(|| {
                let elem = s.fields.values().map(|v| v.cpp_type()).collect::<Option<Vec<_>>>()?;

                Some(format_smolstr!("std::tuple<{}>", elem.join(", ")))
            }),
            Type::Array(i) => {
                Some(format_smolstr!("std::shared_ptr<slint::Model<{}>>", i.cpp_type()?))
            }
            Type::Image => Some("slint::Image".into()),
            Type::DataTransfer => Some("slint::DataTransfer".into()),
            Type::Enumeration(enumeration) => {
                if enumeration.node.is_some() {
                    Some(ident(&enumeration.name))
                } else {
                    Some(format_smolstr!("slint::cbindgen_private::{}", ident(&enumeration.name)))
                }
            }
            Type::Brush => Some("slint::Brush".into()),
            Type::LayoutCache => Some("slint::SharedVector<float>".into()),
            Type::ArrayOfU16 => Some("slint::SharedVector<uint16_t>".into()),
            Type::Easing => Some("slint::cbindgen_private::EasingCurve".into()),
            Type::StyledText => Some("slint::StyledText".into()),
            Type::MouseCursor => Some("slint::cbindgen_private::MouseCursorInner".into()),
            _ => None,
        }
    }
}

fn to_cpp_orientation(o: Orientation) -> &'static str {
    match o {
        Orientation::Horizontal => "slint::cbindgen_private::Orientation::Horizontal",
        Orientation::Vertical => "slint::cbindgen_private::Orientation::Vertical",
    }
}

/// If the expression is surrounded with parentheses, remove these parentheses
fn remove_parentheses(expr: &str) -> &str {
    if expr.starts_with('(') && expr.ends_with(')') {
        let mut level = 0;
        // check that the opening and closing parentheses are on the same level
        for byte in &expr.as_bytes()[1..expr.len() - 1] {
            match byte {
                b')' if level == 0 => return expr,
                b')' => level -= 1,
                b'(' => level += 1,
                _ => (),
            }
        }
        &expr[1..expr.len() - 1]
    } else {
        expr
    }
}

#[test]
fn remove_parentheses_test() {
    assert_eq!(remove_parentheses("(foo(bar))"), "foo(bar)");
    assert_eq!(remove_parentheses("(foo).bar"), "(foo).bar");
    assert_eq!(remove_parentheses("(foo(bar))"), "foo(bar)");
    assert_eq!(remove_parentheses("(foo)(bar)"), "(foo)(bar)");
    assert_eq!(remove_parentheses("(foo).get()"), "(foo).get()");
    assert_eq!(remove_parentheses("((foo).get())"), "(foo).get()");
    assert_eq!(remove_parentheses("(((()())()))"), "((()())())");
    assert_eq!(remove_parentheses("((()())())"), "(()())()");
    assert_eq!(remove_parentheses("(()())()"), "(()())()");
    assert_eq!(remove_parentheses("()())("), "()())(");
}

fn property_set_value_code(
    property: &llr::MemberReference,
    value_expr: &str,
    ctx: &EvaluationContext,
) -> String {
    let prop = access_member(property, ctx);
    if let Some((animation, map)) = &ctx.property_info(property).animation {
        let mut animation = (*animation).clone();
        map.map_expression(&mut animation);
        let animation_code = compile_expression(&animation, ctx);
        return prop
            .then(|prop| format!("{prop}.set_animated_value({value_expr}, {animation_code})"));
    }
    prop.then(|prop| format!("{prop}.set({value_expr})"))
}

/// Walk `field_access` on `root_ty`, prepending each access to `base` to
/// produce a C++ expression (e.g. `base.foo.bar`), and return the leaf type.
fn lower_field_access_chain(
    mut base: String,
    root_ty: &Type,
    field_access: &[SmolStr],
) -> (String, Type) {
    let mut ty = root_ty.clone();
    for f in field_access {
        let Type::Struct(s) = &ty else { panic!("Field of two way binding on a non-struct type") };
        base = struct_field_access(base, s, f);
        ty = s.fields.get(f).unwrap().clone();
    }
    (base, ty)
}

/// Emit a `link_two_way_to_model_data` call wiring `p1` to a row of the
/// model described by `info`, optionally through a struct `field_access`.
fn generate_model_two_way_binding(
    ctx: &EvaluationContext,
    info: &llr::ResolvedModelTwoWayBinding,
    p1: &str,
    field_access: &[SmolStr],
) -> String {
    let body_sc = &ctx.compilation_unit.sub_components[info.body_sub_component];
    let data_prop_name = field_name(&body_sc.properties[info.data_prop].name);
    let index_prop_name = field_name(&body_sc.properties[info.index_prop].name);
    let repeater_index = usize::from(info.repeater_index);

    // Determine the C++ class name of `self` so we can cast back from
    // the type-erased VRc obtained by locking the weak pointer.
    let self_type = ident(
        &ctx.current_sub_component()
            .expect("model two-way bindings only exist on sub-components")
            .name,
    );

    // Walk the parent chain in a single expression so the intermediate
    // `lock().value()` temporaries live until we assign to `body_rc`.
    let (body_setup, body) = if info.parent_level == 0 {
        (String::new(), "self")
    } else {
        let chain: String = (0..info.parent_level).map(|_| "->parent.lock().value()").collect();
        (format!("auto body_rc = self{chain}; "), "body_rc")
    };

    let (getter_expr, ty) = lower_field_access_chain(
        format!("{body}->{data_prop_name}.get()"),
        info.data_prop_ty,
        field_access,
    );
    let (setter_lvalue, _) =
        lower_field_access_chain("data".into(), info.data_prop_ty, field_access);
    let cpp_ty = ty.cpp_type().unwrap();

    // Capture a weak pointer instead of a raw `self` so the getter and
    // setter stay safe when the repeater instance is destroyed while a
    // forwarded binding on a shared common property still references it.
    format!(
        "slint::private_api::Property<{cpp_ty}>::link_two_way_to_model_data(&{p1}, \
         [weak = self->self_weak]() -> std::optional<{cpp_ty}> {{ \
            auto rc = weak.lock(); \
            if (!rc) return std::nullopt; \
            auto self = reinterpret_cast<const {self_type}*>((*rc).borrow().instance); \
            {body_setup}return {getter_expr}; \
         }}, \
         [weak = self->self_weak](const {cpp_ty} &value) {{ \
            auto rc = weak.lock(); \
            if (!rc) return; \
            auto self = reinterpret_cast<const {self_type}*>((*rc).borrow().instance); \
            {body_setup}\
            if (auto parent_opt = {body}->parent.lock()) {{ \
                auto data = {body}->{data_prop_name}.get(); \
                {setter_lvalue} = value; \
                (*parent_opt)->repeater_{repeater_index}.model_set_row_data(\
                    static_cast<size_t>({body}->{index_prop_name}.get()), data); \
            }} \
         }});"
    )
}

fn handle_property_init(
    prop: &llr::MemberReference,
    binding_expression: &llr::BindingExpression,
    init: &mut Vec<String>,
    ctx: &EvaluationContext,
) {
    let prop_access = access_member(prop, ctx).unwrap();
    let prop_type = ctx.property_ty(prop);
    if let Type::Callback(callback) = &prop_type {
        let mut ctx2 = ctx.clone();
        ctx2.argument_types = &callback.args;

        let mut params = callback.args.iter().enumerate().map(|(i, ty)| {
            format!("[[maybe_unused]] {} arg_{}", ty.cpp_type().unwrap_or_default(), i)
        });

        init.push(format!(
            "{prop_access}.set_handler(
                [this]({params}) {{
                    [[maybe_unused]] auto self = this;
                    {code};
                }});",
            prop_access = prop_access,
            params = params.join(", "),
            code = return_compile_expression(
                &binding_expression.expression.borrow(),
                &ctx2,
                Some(&callback.return_type)
            )
        ));
    } else {
        let init_expr = compile_expression(&binding_expression.expression.borrow(), ctx);

        init.push(match binding_expression.kind {
            llr::BindingKind::Constant => format!("{prop_access}.set({init_expr});"),
            llr::BindingKind::State => {
                let binding_code = format!(
                    "[this]() {{
                            [[maybe_unused]] auto self = this;
                            return {init_expr};
                        }}"
                );
                format!("slint::private_api::set_state_binding({prop_access}, {binding_code});")
            }
            llr::BindingKind::Normal => {
                let binding_code = format!(
                    "[this]() {{
                            [[maybe_unused]] auto self = this;
                            return {init_expr};
                        }}"
                );
                match &binding_expression.animation {
                    Some(llr::Animation::Static(anim)) => {
                        let anim = compile_expression(anim, ctx);
                        // Note: The start_time defaults to the current tick, so doesn't need to be
                        // updated here.
                        format!("{prop_access}.set_animated_binding({binding_code},
                                [this](uint64_t **start_time) -> slint::cbindgen_private::PropertyAnimation {{
                                    [[maybe_unused]] auto self = this;
                                    auto anim = {anim};
                                    *start_time = nullptr;
                                    return anim;
                                }});",
                                )
                    }
                    Some(llr::Animation::Transition(animation)) => {
                        let animation = compile_expression(animation, ctx);
                        format!(
                            "{prop_access}.set_animated_binding({binding_code},
                            [this](uint64_t **start_time) -> slint::cbindgen_private::PropertyAnimation {{
                                [[maybe_unused]] auto self = this;
                                auto [animation, change_time] = {animation};
                                **start_time = change_time;
                                return animation;
                            }});",
                        )
                    }
                    None => format!("{prop_access}.set_binding({binding_code});"),
                }
            }
        });
    }
}

/// Returns the text of the C++ code produced by the given root component
pub fn generate(
    doc: &Document,
    config: Config,
    compiler_config: &CompilerConfiguration,
) -> std::io::Result<impl std::fmt::Display> {
    if std::env::var("SLINT_LIVE_PREVIEW").is_ok() {
        return super::cpp_live_preview::generate(doc, config, compiler_config);
    }

    let llr = llr::lower_to_item_tree::lower_to_item_tree(doc, compiler_config);

    let mut file = generate_types(&doc.used_types.borrow().structs_and_enums, &config, &llr);

    for (resource_id, er) in doc.embedded_file_resources.borrow().iter_enumerated() {
        embed_resource(er, resource_id, &mut file.resources);
    }

    #[cfg(feature = "bundle-translations")]
    if let Some(translations) = &llr.translations {
        generate_translation(translations, &llr, &mut file.resources);
    }

    // Forward-declare the root so that sub-components can access singletons, the window, etc.
    file.declarations.extend(
        llr.public_components
            .iter()
            .map(|c| Declaration::Struct(Struct { name: ident(&c.name), ..Default::default() })),
    );

    // forward-declare the global struct
    file.declarations.push(Declaration::Struct(Struct {
        name: SmolStr::new_static(SHARED_GLOBAL_CLASS),
        ..Default::default()
    }));

    // Forward-declare sub components.
    file.declarations.extend(llr.used_sub_components.iter().map(|sub_compo| {
        Declaration::Struct(Struct {
            name: ident(&llr.sub_components[*sub_compo].name),
            ..Default::default()
        })
    }));

    let conditional_includes = ConditionalIncludes::default();

    for sub_compo in &llr.used_sub_components {
        let sub_compo_id = ident(&llr.sub_components[*sub_compo].name);
        let mut sub_compo_struct = Struct { name: sub_compo_id.clone(), ..Default::default() };
        generate_sub_component(
            &mut sub_compo_struct,
            *sub_compo,
            &llr,
            None,
            Access::Public,
            &mut file,
            &conditional_includes,
        );
        file.definitions.extend(sub_compo_struct.extract_definitions().collect::<Vec<_>>());
        file.declarations.push(Declaration::Struct(sub_compo_struct));
    }

    let mut globals_struct =
        Struct { name: SmolStr::new_static(SHARED_GLOBAL_CLASS), ..Default::default() };

    // The window need to be the first member so it is destroyed last
    globals_struct.members.push((
        // FIXME: many of the different component bindings need to access this
        Access::Public,
        Declaration::Var(Var {
            ty: "std::optional<slint::Window>".into(),
            name: "m_window".into(),
            ..Default::default()
        }),
    ));

    globals_struct.members.push((
        Access::Public,
        Declaration::Var(Var {
            ty: "slint::cbindgen_private::ItemTreeWeak".into(),
            name: "root_weak".into(),
            ..Default::default()
        }),
    ));

    let mut window_creation_code = vec![
        format!("auto self = const_cast<{SHARED_GLOBAL_CLASS} *>(this);"),
        "if (!self->m_window.has_value()) {".into(),
        "   auto &window = self->m_window.emplace(slint::private_api::WindowAdapterRc());".into(),
    ];

    if let Some(scale_factor) = compiler_config.const_scale_factor {
        window_creation_code
            .push(format!("window.window_handle().set_const_scale_factor({scale_factor});"));
    }

    window_creation_code.extend([
        "   window.window_handle().set_component(self->root_weak);".into(),
        "}".into(),
        "return *self->m_window;".into(),
    ]);

    globals_struct.members.push((
        Access::Public,
        Declaration::Function(Function {
            name: "window".into(),
            signature: "() const -> slint::Window&".into(),
            statements: Some(window_creation_code),
            ..Default::default()
        }),
    ));

    let mut init_global = Vec::new();
    let mut clone_constructor_global_inits = Vec::new();

    for (idx, glob) in llr.globals.iter_enumerated() {
        if !glob.must_generate() {
            continue;
        }
        let name = format_smolstr!("global_{}", concatenate_ident(&glob.name));
        let ty = if glob.is_builtin {
            generate_global_builtin(&mut file, &conditional_includes, idx, glob, &llr);
            format_smolstr!("slint::cbindgen_private::{}", glob.name)
        } else {
            init_global.push(format!("{name}->init();"));
            generate_global(&mut file, &conditional_includes, idx, glob, &llr);
            ident(&glob.name)
        };

        file.definitions.extend(glob.aliases.iter().map(|name| {
            Declaration::TypeAlias(TypeAlias { old_name: ident(&glob.name), new_name: ident(name) })
        }));

        clone_constructor_global_inits.push(format!("{name}(source.{name})"));

        globals_struct.members.push((
            Access::Public,
            Declaration::Var(Var {
                ty: format_smolstr!("std::shared_ptr<{}>", ty),
                name,
                init: Some(format!("std::make_shared<{ty}>(this)")),
                ..Default::default()
            }),
        ));
    }

    // The globals are not initialized in the constructor: a global's init may evaluate a
    // binding (e.g. `Palette.color-scheme`) that resolves the root through `root_weak` and the
    // root component's `globals` pointer. Those are only set after the SharedGlobals member has
    // been constructed, so the init is deferred to `init_globals()`, called from the root
    // component's `create()` once `globals` and `root_weak` are in place.
    globals_struct.members.push((
        Access::Public,
        Declaration::Function(Function {
            name: globals_struct.name.clone(),
            is_constructor_or_destructor: true,
            signature: "()".into(),
            statements: Some(vec![]),
            ..Default::default()
        }),
    ));
    globals_struct.members.push((
        Access::Public,
        Declaration::Function(Function {
            name: "init_globals".into(),
            signature: "() -> void".into(),
            statements: Some(init_global),
            ..Default::default()
        }),
    ));

    // Build initializer-list string for the clone_with_window_adapter constructor
    {
        let global_inits = std::iter::once("root_weak(source.root_weak)".to_string())
            .chain(clone_constructor_global_inits)
            .collect::<Vec<_>>()
            .join(", ");
        let init_list =
            if global_inits.is_empty() { String::new() } else { format!(" : {global_inits}") };

        // A private constructor for cloning with a different window adapter
        globals_struct.members.push((
                Access::Private,
                Declaration::Function(Function {
                    name: globals_struct.name.clone(),
                    is_constructor_or_destructor: true,
                    signature: format!(
                        "(const {SHARED_GLOBAL_CLASS}& source, const slint::private_api::WindowAdapterRc& adapter){init_list}"
                    ),
                    statements: Some(vec!["m_window.emplace(adapter);".into()]),
                    ..Default::default()
                }),
            ));

        globals_struct.members.push((
                Access::Public,
                Declaration::Function(Function {
                    name: "clone_with_window_adapter".into(),
                    signature: format!("(const slint::private_api::WindowAdapterRc& adapter) const -> {SHARED_GLOBAL_CLASS}*"),
                    statements: Some(vec![format!(
                        "return new {SHARED_GLOBAL_CLASS}(*this, adapter);"
                    )]),
                    ..Default::default()
                }),
            ));
    }

    file.declarations.push(Declaration::Struct(globals_struct));

    if let Some(popup_menu) = &llr.popup_menu {
        let component_id = ident(&llr.sub_components[popup_menu.item_tree.root].name);
        let mut popup_struct = Struct { name: component_id.clone(), ..Default::default() };
        generate_item_tree(
            &mut popup_struct,
            &popup_menu.item_tree,
            &llr,
            None,
            true,
            component_id,
            Access::Public,
            &mut file,
            &conditional_includes,
        );
        file.definitions.extend(popup_struct.extract_definitions().collect::<Vec<_>>());
        file.declarations.push(Declaration::Struct(popup_struct));
    };

    for p in &llr.public_components {
        generate_public_component(&mut file, &conditional_includes, p, &llr);
    }

    generate_type_aliases(&mut file, doc);

    if conditional_includes.iostream.get() {
        file.includes.push("<iostream>".into());
    }

    if conditional_includes.cstdlib.get() {
        file.includes.push("<cstdlib>".into());
    }

    if conditional_includes.cmath.get() {
        file.includes.push("<cmath>".into());
    }

    let cpp_files = file.split_off_cpp_files(config.header_include, config.cpp_files.len());

    for (cpp_file_name, cpp_file) in config.cpp_files.iter().zip(cpp_files) {
        // Important: Write without unnecessary mtime modification to avoid
        // build systems to always detect the generated file as modified.
        fileaccess::write_file_if_changed(cpp_file_name, cpp_file.to_string().as_bytes())?;
    }

    Ok(file)
}

pub fn generate_types(used_types: &[Type], config: &Config, unit: &llr::CompilationUnit) -> File {
    let mut file = File { namespace: config.namespace.clone(), ..Default::default() };

    file.includes.push("<array>".into());
    file.includes.push("<limits>".into());
    file.includes.push("<slint.h>".into());

    file.after_includes = format!(
        "static_assert({x} == SLINT_VERSION_MAJOR && {y} == SLINT_VERSION_MINOR && {z} == SLINT_VERSION_PATCH, \
        \"This file was generated with Slint compiler version {x}.{y}.{z}, but the Slint library used is \" \
        SLINT_VERSION_STRING \". The version numbers must match exactly.\");",
        x = env!("CARGO_PKG_VERSION_MAJOR"),
        y = env!("CARGO_PKG_VERSION_MINOR"),
        z = env!("CARGO_PKG_VERSION_PATCH")
    );

    // The evaluation context for the field default values needs an instance,
    // but the constant expressions cannot call any of the functions that
    // record a conditional include
    let conditional_includes = ConditionalIncludes::default();

    for ty in used_types {
        match ty {
            Type::Struct(s) if s.node().is_some() => {
                generate_struct(&mut file, s, unit, &conditional_includes);
            }
            Type::Enumeration(en) => {
                generate_enum(&mut file, en);
            }
            _ => (),
        }
    }

    debug_assert!(
        !conditional_includes.iostream.get()
            && !conditional_includes.cstdlib.get()
            && !conditional_includes.cmath.get(),
        "a constant expression recorded a conditional include; apply them to the file"
    );

    file
}

fn expand_data_to_cpp_u8_array(data: &[u8]) -> String {
    let mut init = "{ ".to_string();

    for (index, byte) in data.iter().enumerate() {
        if index > 0 {
            init.push(',');
        }
        write!(&mut init, "0x{byte:x}").unwrap();
        if index % 16 == 0 {
            init.push('\n');
        }
    }

    init.push('}');
    init
}

fn embed_resource(
    resource: &crate::embedded_resources::EmbeddedResources,
    resource_id: crate::embedded_resources::EmbeddedResourcesIdx,
    declarations: &mut Vec<Declaration>,
) {
    match &resource.kind {
        crate::embedded_resources::EmbeddedResourcesKind::ListOnly => {}
        crate::embedded_resources::EmbeddedResourcesKind::FileData => {
            let resource_file = crate::fileaccess::load_file(std::path::Path::new(
                resource.path.as_deref().unwrap(),
            ))
            .unwrap(); // embedding pass ensured that the file exists
            let data = resource_file.read();

            declarations.push(Declaration::Var(Var {
                ty: "const uint8_t".into(),
                name: format_smolstr!("slint_embedded_resource_{}", resource_id),
                array_size: Some(data.len()),
                init: Some(expand_data_to_cpp_u8_array(data.as_ref())),
                ..Default::default()
            }));
        }
        crate::embedded_resources::EmbeddedResourcesKind::DataUriPayload(data, _) => {
            declarations.push(Declaration::Var(Var {
                ty: "const uint8_t".into(),
                name: format_smolstr!("slint_embedded_resource_{}", resource_id),
                array_size: Some(data.len()),
                init: Some(expand_data_to_cpp_u8_array(data)),
                ..Default::default()
            }));
        }
        #[cfg(feature = "renderer-software")]
        crate::embedded_resources::EmbeddedResourcesKind::TextureData(
            crate::embedded_resources::Texture {
                data,
                format,
                rect,
                total_size: crate::embedded_resources::Size { width, height },
                original_size:
                    crate::embedded_resources::Size { width: unscaled_width, height: unscaled_height },
            },
        ) => {
            let (r_x, r_y, r_w, r_h) = (rect.x(), rect.y(), rect.width(), rect.height());
            let color = if let crate::embedded_resources::PixelFormat::AlphaMap([r, g, b]) = format
            {
                format!("slint::Color::from_rgb_uint8({r}, {g}, {b})")
            } else {
                "slint::Color{}".to_string()
            };
            let count = data.len();
            let data = data.iter().map(ToString::to_string).join(", ");
            let data_name = format_smolstr!("slint_embedded_resource_{}_data", resource_id);
            declarations.push(Declaration::Var(Var {
                ty: "const uint8_t".into(),
                name: data_name.clone(),
                array_size: Some(count),
                init: Some(format!("{{ {data} }}")),
                ..Default::default()
            }));
            let texture_name = format_smolstr!("slint_embedded_resource_{}_texture", resource_id);
            declarations.push(Declaration::Var(Var {
                ty: "const slint::cbindgen_private::types::StaticTexture".into(),
                name: texture_name.clone(),
                array_size: None,
                init: Some(format!(
                    "{{
                            .rect = {{ {r_x}, {r_y}, {r_w}, {r_h} }},
                            .format = slint::cbindgen_private::types::TexturePixelFormat::{format},
                            .color = {color},
                            .index = 0,
                            }}"
                )),
                ..Default::default()
            }));
            let init = format!(
                "slint::cbindgen_private::types::StaticTextures {{
                        .size = {{ {width}, {height} }},
                        .original_size = {{ {unscaled_width}, {unscaled_height} }},
                        .data = slint::private_api::make_slice({data_name} , {count} ),
                        .textures = slint::private_api::make_slice(&{texture_name}, 1)
                    }}"
            );
            declarations.push(Declaration::Var(Var {
                ty: "const slint::cbindgen_private::types::StaticTextures".into(),
                name: format_smolstr!("slint_embedded_resource_{}", resource_id),
                array_size: None,
                init: Some(init),
                ..Default::default()
            }))
        }
        #[cfg(feature = "renderer-software")]
        crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(
            crate::embedded_resources::BitmapFont {
                family_name,
                character_map,
                units_per_em,
                ascent,
                descent,
                x_height,
                cap_height,
                glyphs,
                weight,
                italic,
                sdf,
            },
        ) => {
            let family_name_var =
                format_smolstr!("slint_embedded_resource_{}_family_name", resource_id);
            let family_name_size = family_name.len();
            declarations.push(Declaration::Var(Var {
                ty: "const uint8_t".into(),
                name: family_name_var.clone(),
                array_size: Some(family_name_size),
                init: Some(format!(
                    "{{ {} }}",
                    family_name.as_bytes().iter().map(ToString::to_string).join(", ")
                )),
                ..Default::default()
            }));

            let charmap_var = format_smolstr!("slint_embedded_resource_{}_charmap", resource_id);
            let charmap_size = character_map.len();
            declarations.push(Declaration::Var(Var {
                ty: "const slint::cbindgen_private::CharacterMapEntry".into(),
                name: charmap_var.clone(),
                array_size: Some(charmap_size),
                init: Some(format!(
                    "{{ {} }}",
                    character_map
                        .iter()
                        .map(|entry| format!(
                            "{{ .code_point = {}, .glyph_index = {} }}",
                            entry.code_point as u32, entry.glyph_index
                        ))
                        .join(", ")
                )),
                ..Default::default()
            }));

            for (glyphset_index, glyphset) in glyphs.iter().enumerate() {
                for (glyph_index, glyph) in glyphset.glyph_data.iter().enumerate() {
                    declarations.push(Declaration::Var(Var {
                        ty: "const uint8_t".into(),
                        name: format_smolstr!(
                            "slint_embedded_resource_{}_gs_{}_gd_{}",
                            resource_id,
                            glyphset_index,
                            glyph_index
                        ),
                        array_size: Some(glyph.data.len()),
                        init: Some(format!(
                            "{{ {} }}",
                            glyph.data.iter().map(ToString::to_string).join(", ")
                        )),
                        ..Default::default()
                    }));
                }

                declarations.push(Declaration::Var(Var{
                    ty: "const slint::cbindgen_private::BitmapGlyph".into(),
                    name: format_smolstr!("slint_embedded_resource_{}_glyphset_{}", resource_id, glyphset_index),
                    array_size: Some(glyphset.glyph_data.len()),
                    init: Some(format!("{{ {} }}", glyphset.glyph_data.iter().enumerate().map(|(glyph_index, glyph)| {
                        format!("{{ .x = {}, .y = {}, .width = {}, .height = {}, .x_advance = {}, .data = slint::private_api::make_slice({}, {}) }}",
                        glyph.x, glyph.y, glyph.width, glyph.height, glyph.x_advance,
                        format_args!("slint_embedded_resource_{}_gs_{}_gd_{}", resource_id, glyphset_index, glyph_index),
                        glyph.data.len()
                    )
                    }).join(", \n"))),
                    ..Default::default()
                }));
            }

            let glyphsets_var =
                format_smolstr!("slint_embedded_resource_{}_glyphsets", resource_id);
            let glyphsets_size = glyphs.len();
            declarations.push(Declaration::Var(Var {
                ty: "const slint::cbindgen_private::BitmapGlyphs".into(),
                name: glyphsets_var.clone(),
                array_size: Some(glyphsets_size),
                init: Some(format!(
                    "{{ {} }}",
                    glyphs
                        .iter()
                        .enumerate()
                        .map(|(glyphset_index, glyphset)| format!(
                            "{{ .pixel_size = {}, .glyph_data = slint::private_api::make_slice({}, {}) }}",
                            glyphset.pixel_size, format_args!("slint_embedded_resource_{}_glyphset_{}", resource_id, glyphset_index), glyphset.glyph_data.len()
                        ))
                        .join(", \n")
                )),
                ..Default::default()
            }));

            let init = format!(
                "slint::cbindgen_private::BitmapFont {{
                        .family_name = slint::private_api::make_slice({family_name_var} , {family_name_size}),
                        .character_map = slint::private_api::make_slice({charmap_var}, {charmap_size}),
                        .units_per_em = {units_per_em},
                        .ascent = {ascent},
                        .descent = {descent},
                        .x_height = {x_height},
                        .cap_height = {cap_height},
                        .glyphs = slint::private_api::make_slice({glyphsets_var}, {glyphsets_size}),
                        .weight = {weight},
                        .italic = {italic},
                        .sdf = {sdf},
                }}"
            );

            declarations.push(Declaration::Var(Var {
                ty: "const slint::cbindgen_private::BitmapFont".into(),
                name: format_smolstr!("slint_embedded_resource_{}", resource_id),
                array_size: None,
                init: Some(init),
                ..Default::default()
            }))
        }
    }
}

fn generate_struct(
    file: &mut File,
    the_struct: &crate::langtype::Struct,
    unit: &llr::CompilationUnit,
    conditional_includes: &ConditionalIncludes,
) {
    let StructName::User { name: user_name, .. } = &the_struct.name else {
        panic!("internal error: Cannot generate anonymous struct");
    };
    // Constant expressions cannot access the globals; make sure a bug in that
    // assumption breaks the build of the generated code with a clear message
    let ctx = EvaluationContext::new_const(
        unit,
        CppGeneratorContext {
            global_access: "no_global_access_in_a_constant_expression".into(),
            conditional_includes,
        },
    );
    let name = ident(user_name);
    // Emit members in declaration order: C++ users initialize structs positionally.
    let mut members = the_struct
        .field_order()
        .iter()
        .map(|name| {
            // When any field has a declared default value, initialize the remaining fields, too,
            // so that default construction is fully deterministic, like in the other language backends.
            let init = match the_struct.field_defaults.get(name) {
                Some(default_value) => {
                    Some(compile_expression(&lower_constant_expression(default_value), &ctx))
                }
                None if !the_struct.field_defaults.is_empty() => Some("{}".into()),
                None => None,
            };
            (
                Access::Public,
                Declaration::Var(Var {
                    ty: the_struct.fields.get(name).unwrap().cpp_type().unwrap(),
                    name: ident(name),
                    init,
                    ..Default::default()
                }),
            )
        })
        .collect::<Vec<_>>();

    members.push((
        Access::Public,
        Declaration::Function(Function {
            name: "operator==".into(),
            signature: format!("(const class {name} &a, const class {name} &b) -> bool = default"),
            is_friend: true,
            statements: None,
            ..Function::default()
        }),
    ));

    file.declarations.push(Declaration::Struct(Struct { name, members, ..Default::default() }))
}

fn generate_enum(file: &mut File, en: &std::sync::Arc<Enumeration>) {
    file.declarations.push(Declaration::Enum(Enum {
        name: ident(&en.name),
        values: (0..en.values.len())
            .map(|value| {
                ident(&EnumerationValue { value, enumeration: en.clone() }.to_pascal_case())
            })
            .collect(),
    }))
}

/// Generate the component in `file`.
///
/// `sub_components`, if Some, will be filled with all the sub component which needs to be added as friends
fn generate_public_component(
    file: &mut File,
    conditional_includes: &ConditionalIncludes,
    component: &llr::PublicComponent,
    unit: &llr::CompilationUnit,
) {
    let component_id = ident(&component.name);

    let mut component_struct = Struct { name: component_id.clone(), ..Default::default() };

    // need to be the first member, because it contains the window which is to be destroyed last
    component_struct.members.push((
        Access::Private,
        Declaration::Var(Var {
            ty: SmolStr::new_static(SHARED_GLOBAL_CLASS),
            name: "m_globals".into(),
            ..Default::default()
        }),
    ));

    for glob in unit.globals.iter().filter(|glob| glob.must_generate() && !glob.is_builtin) {
        component_struct.friends.push(ident(&glob.name));
    }

    let mut global_accessor_function_body = Vec::new();
    let mut builtin_globals = Vec::new();
    for glob in unit.globals.iter().filter(|glob| glob.exported && glob.must_generate()) {
        let accessor_statement = if glob.is_builtin {
            builtin_globals.push(format!("std::is_same_v<T, {}>", ident(&glob.name)));
            format!(
                "{0}if constexpr(std::is_same_v<T, {1}>) {{ return {1}(m_globals.global_{1}); }}",
                if global_accessor_function_body.is_empty() { "" } else { "else " },
                concatenate_ident(&glob.name),
            )
        } else {
            format!(
                "{0}if constexpr(std::is_same_v<T, {1}>) {{ return *m_globals.global_{1}.get(); }}",
                if global_accessor_function_body.is_empty() { "" } else { "else " },
                concatenate_ident(&glob.name),
            )
        };
        global_accessor_function_body.push(accessor_statement);
    }
    if !global_accessor_function_body.is_empty() {
        global_accessor_function_body.push(
            "else { static_assert(!sizeof(T*), \"The type is not global/or exported\"); }".into(),
        );

        component_struct.members.push((
            Access::Public,
            Declaration::Function(Function {
                name: "global".into(),
                signature: if builtin_globals.is_empty() {
                    "() const -> const T&".into()
                } else {
                    format!(
                        "() const -> std::conditional_t<{} , T, const T&>",
                        builtin_globals.iter().join(" || ")
                    )
                },
                statements: Some(global_accessor_function_body),
                template_parameters: Some("typename T".into()),
                ..Default::default()
            }),
        ));
    }

    let ctx = EvaluationContext {
        compilation_unit: unit,
        current_scope: EvaluationScope::SubComponent(component.item_tree.root, None),
        generator_state: CppGeneratorContext {
            global_access: "(&this->m_globals)".to_string(),
            conditional_includes,
        },
        argument_types: &[],
    };

    let old_declarations = file.declarations.len();

    generate_item_tree(
        &mut component_struct,
        &component.item_tree,
        unit,
        None,
        false,
        component_id,
        Access::Private, // Hide properties and other fields from the C++ API
        file,
        conditional_includes,
    );

    // Give generated sub-components, etc. access to our fields

    for new_decl in file.declarations.iter().skip(old_declarations) {
        if let Declaration::Struct(struc @ Struct { .. }) = new_decl {
            component_struct.friends.push(struc.name.clone());
        };
    }

    generate_public_api_for_properties(
        &mut component_struct.members,
        &component.public_properties,
        &component.private_properties,
        &ctx,
    );

    // Window-rooted components route `show`/`hide` through the underlying
    // window adapter, expose `window()`, and have a `run()` that drives the
    // event loop. SystemTrayIcon-rooted components instead toggle the `visible`
    // property on the tray native item, expose no `window()`, and skip
    // `run()` entirely (a tray icon doesn't drive the event loop).
    let (show_body, hide_body) = match component.top_level_type {
        llr::TopLevelComponentType::Window => {
            ("m_globals.window().show();".to_string(), "m_globals.window().hide();".to_string())
        }
        llr::TopLevelComponentType::SystemTrayIcon => {
            let root_sub = &unit.sub_components[component.item_tree.root];
            let tray_item = &root_sub.items[llr::ItemInstanceIdx::from(0usize)];
            debug_assert_eq!(
                tray_item.ty.class_name.as_str(),
                "SystemTrayIcon",
                "TopLevelComponentType::SystemTrayIcon expects the root item to be a SystemTrayIcon"
            );
            let tray_field = field_name(&tray_item.name);
            (
                format!("{tray_field}.visible.set(true);"),
                format!("{tray_field}.visible.set(false);"),
            )
        }
    };

    component_struct.members.push((
        Access::Public,
        Declaration::Function(Function {
            name: "show".into(),
            signature: "() -> void".into(),
            statements: Some(vec![show_body]),
            ..Default::default()
        }),
    ));

    component_struct.members.push((
        Access::Public,
        Declaration::Function(Function {
            name: "hide".into(),
            signature: "() -> void".into(),
            statements: Some(vec![hide_body]),
            ..Default::default()
        }),
    ));

    match component.top_level_type {
        llr::TopLevelComponentType::Window => {
            component_struct.members.push((
                Access::Public,
                Declaration::Function(Function {
                    name: "window".into(),
                    signature: "() const -> slint::Window&".into(),
                    statements: Some(vec!["return m_globals.window();".into()]),
                    ..Default::default()
                }),
            ));
            component_struct.members.push((
                Access::Public,
                Declaration::Function(Function {
                    name: "run".into(),
                    signature: "() -> void".into(),
                    statements: Some(vec![
                        "show();".into(),
                        "slint::run_event_loop();".into(),
                        "hide();".into(),
                    ]),
                    ..Default::default()
                }),
            ));
        }
        llr::TopLevelComponentType::SystemTrayIcon => {}
    }

    component_struct.friends.push("slint::private_api::WindowAdapterRc".into());

    add_friends(&mut component_struct.friends, unit, component.item_tree.root, true);

    fn add_friends(
        friends: &mut Vec<SmolStr>,
        unit: &llr::CompilationUnit,
        c: llr::SubComponentIdx,
        is_root: bool,
    ) {
        let sc = &unit.sub_components[c];
        if !is_root {
            friends.push(ident(&sc.name));
        }
        for repeater in &sc.repeated {
            add_friends(friends, unit, repeater.sub_tree.root, false)
        }
        for popup in &sc.popup_windows {
            add_friends(friends, unit, popup.item_tree.root, false)
        }
        for menu in &sc.menu_item_trees {
            add_friends(friends, unit, menu.root, false)
        }
    }

    file.definitions.extend(component_struct.extract_definitions().collect::<Vec<_>>());
    file.declarations.push(Declaration::Struct(component_struct));
}

fn generate_item_tree(
    target_struct: &mut Struct,
    sub_tree: &llr::ItemTree,
    root: &llr::CompilationUnit,
    parent_ctx: Option<&ParentScope>,
    is_popup: bool,
    item_tree_class_name: SmolStr,
    field_access: Access,
    file: &mut File,
    conditional_includes: &ConditionalIncludes,
) {
    let needs_window_adapter = root.needs_window_adapter();
    // True only for the root tree of a SystemTrayIcon-rooted public component.
    // Repeaters / popup_menu / popup-window trees stay on the windowed code
    // path even when they live inside a tray-only unit (popup menus are
    // window-shaped, and there's no SystemTrayIcon-rooted repeater root anyway).
    let is_system_tray_root = parent_ctx.is_none()
        && !is_popup
        && root.public_components.iter().any(|p| {
            p.item_tree.root == sub_tree.root
                && p.top_level_type == llr::TopLevelComponentType::SystemTrayIcon
        });

    target_struct.friends.push(format_smolstr!(
        "vtable::VRc<slint::private_api::ItemTreeVTable, {}>",
        item_tree_class_name
    ));

    generate_sub_component(
        target_struct,
        sub_tree.root,
        root,
        parent_ctx,
        field_access,
        file,
        conditional_includes,
    );

    let mut item_tree_array: Vec<String> = Default::default();
    let mut item_array: Vec<String> = Default::default();

    sub_tree.tree.visit_in_array(&mut |node, children_offset, parent_index| {
        let parent_index = parent_index as u32;

        match node.item_index {
            Either::Right(mut repeater_index) => {
                assert_eq!(node.children.len(), 0);
                let mut sub_component = &root.sub_components[sub_tree.root];
                for i in &node.sub_component_path {
                    repeater_index += sub_component.sub_components[*i].repeater_offset;
                    sub_component = &root.sub_components[sub_component.sub_components[*i].ty];
                }
                item_tree_array.push(format!(
                    "slint::private_api::make_dyn_node({repeater_index}, {parent_index})"
                ));
            }
            Either::Left(item_index) => {
                let mut compo_offset = String::new();
                let mut sub_component = &root.sub_components[sub_tree.root];
                for i in &node.sub_component_path {
                    let next_sub_component_name =
                        field_name(&sub_component.sub_components[*i].name);
                    write!(
                        compo_offset,
                        "offsetof({}, {}) + ",
                        ident(&sub_component.name),
                        next_sub_component_name
                    )
                    .unwrap();
                    sub_component = &root.sub_components[sub_component.sub_components[*i].ty];
                }

                let item = &sub_component.items[item_index];
                let children_count = node.children.len() as u32;
                let children_index = children_offset as u32;
                let item_array_index = item_array.len() as u32;

                item_tree_array.push(format!(
                    "slint::private_api::make_item_node({}, {}, {}, {}, {})",
                    children_count,
                    children_index,
                    parent_index,
                    item_array_index,
                    node.is_accessible
                ));
                item_array.push(format!(
                    "{{ {}, {} offsetof({}, {}) }}",
                    item.ty.cpp_vtable_getter,
                    compo_offset,
                    ident(&sub_component.name),
                    field_name(&item.name),
                ));
            }
        }
    });

    let mut visit_children_statements = vec![
        "static const auto dyn_visit = [] (const void *base,  [[maybe_unused]] slint::private_api::TraversalOrder order, [[maybe_unused]] slint::private_api::ItemVisitorRefMut visitor, [[maybe_unused]] uint32_t dyn_index) -> uint64_t {".to_owned(),
        format!("    [[maybe_unused]] auto self = reinterpret_cast<const {}*>(base);", item_tree_class_name)];
    let mut subtree_range_statement = vec!["    std::abort();".into()];
    let mut subtree_component_statement = vec!["    std::abort();".into()];

    if target_struct.members.iter().any(|(_, declaration)| {
        matches!(&declaration, Declaration::Function(func @ Function { .. }) if func.name == "visit_dynamic_children")
    }) {
        visit_children_statements
            .push("    return self->visit_dynamic_children(dyn_index, order, visitor);".into());
        subtree_range_statement = vec![
                format!("auto self = reinterpret_cast<const {}*>(component.instance);", item_tree_class_name),
                "return self->subtree_range(dyn_index);".to_owned(),
        ];
        subtree_component_statement = vec![
                format!("auto self = reinterpret_cast<const {}*>(component.instance);", item_tree_class_name),
                "self->subtree_component(dyn_index, subtree_index, result);".to_owned(),
        ];
    } else {
        visit_children_statements.push("    std::abort();".into());
     }

    visit_children_statements.extend([
        "};".into(),
        format!("auto self_rc = reinterpret_cast<const {item_tree_class_name}*>(component.instance)->self_weak.lock()->into_dyn();"),
        "return slint::cbindgen_private::slint_visit_item_tree(&self_rc, get_item_tree(component) , index, order, visitor, dyn_visit);".to_owned(),
    ]);

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "visit_children".into(),
            signature: "(slint::private_api::ItemTreeRef component, intptr_t index, slint::private_api::TraversalOrder order, slint::private_api::ItemVisitorRefMut visitor) -> uint64_t".into(),
            is_static: true,
            statements: Some(visit_children_statements),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "get_item_ref".into(),
            signature: "(slint::private_api::ItemTreeRef component, uint32_t index) -> slint::private_api::ItemRef".into(),
            is_static: true,
            statements: Some(vec![
                "return slint::private_api::get_item_ref(component, get_item_tree(component), item_array(), index);".to_owned(),
            ]),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "get_subtree_range".into(),
            signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] uint32_t dyn_index) -> slint::private_api::IndexRange".into(),
            is_static: true,
            statements: Some(subtree_range_statement),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "get_subtree".into(),
            signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] uint32_t dyn_index, [[maybe_unused]] uintptr_t subtree_index, [[maybe_unused]] slint::private_api::ItemTreeWeak *result) -> void".into(),
            is_static: true,
            statements: Some(subtree_component_statement),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "get_item_tree".into(),
            signature: "(slint::private_api::ItemTreeRef) -> slint::cbindgen_private::Slice<slint::private_api::ItemTreeNode>".into(),
            is_static: true,
            statements: Some(vec![
                "return item_tree();".to_owned(),
            ]),
            ..Default::default()
        }),
    ));

    let parent_item_from_parent_component = parent_ctx.as_ref()
        .map(|parent| {
            parent.repeater_index.map_or_else(|| {
                // No repeater index, this could be a PopupWindow. The parent may
                // already be gone (e.g. walked while a subtree is being torn
                // down), so leave `*result` empty rather than unwrapping a dead
                // weak (matches the Rust backend).
                vec![
                    format!("auto self = reinterpret_cast<const {item_tree_class_name}*>(component.instance);"),
                    "if (auto parent = self->parent.lock()) {".to_string(),
                    // TODO: store popup index in ctx and set it here instead of 0?
                    "    *result = { (*parent)->self_weak, 0 };".to_string(),
                    "}".to_string(),
                    ]
                }, |idx| {
                let current_sub_component = &root.sub_components[parent.sub_component];
                let parent_index = current_sub_component.repeated[idx].index_in_tree;
                vec![
                    format!("auto self = reinterpret_cast<const {item_tree_class_name}*>(component.instance);"),
                    "if (auto parent = self->parent.lock()) {".to_string(),
                    format!("    *result = {{ (*parent)->self_weak, (*parent)->tree_index_of_first_child + {} }};", parent_index - 1),
                    "}".to_string(),
                ]
            })
        })
        .unwrap_or_default();
    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "parent_node".into(),
            signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] slint::private_api::ItemWeak *result) -> void".into(),
            is_static: true,
            statements: Some(parent_item_from_parent_component,),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "embed_component".into(),
            signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] const slint::private_api::ItemTreeWeak *parent_component, [[maybe_unused]] const uint32_t parent_index) -> bool".into(),
            is_static: true,
            statements: Some(vec!["return false; /* todo! */".into()]),
            ..Default::default()
        }),
    ));

    // Statements will be overridden for repeated components!
    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "subtree_index".into(),
            signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component) -> uintptr_t"
                .into(),
            is_static: true,
            statements: Some(vec!["return std::numeric_limits<uintptr_t>::max();".into()]),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "item_tree".into(),
            signature: "() -> slint::cbindgen_private::Slice<slint::private_api::ItemTreeNode>"
                .into(),
            is_static: true,
            statements: Some(vec![
                "static const slint::private_api::ItemTreeNode children[] {".to_owned(),
                format!("    {} }};", item_tree_array.join(", \n")),
                "return slint::private_api::make_slice(std::span(children));".to_owned(),
            ]),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "item_array".into(),
            signature: "() -> const slint::private_api::ItemArray".into(),
            is_static: true,
            statements: Some(vec![
                "static const slint::private_api::ItemArrayEntry items[] {".to_owned(),
                format!("    {} }};", item_array.join(", \n")),
                "return slint::private_api::make_slice(std::span(items));".to_owned(),
            ]),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "layout_info".into(),
            signature:
                "([[maybe_unused]] slint::private_api::ItemTreeRef component, slint::cbindgen_private::Orientation o) -> slint::cbindgen_private::LayoutInfo"
                    .into(),
            is_static: true,
            statements: Some(vec![format!(
                "return reinterpret_cast<const {}*>(component.instance)->layout_info(o);",
                item_tree_class_name
            )]),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "ensure_instantiated".into(),
            signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component) -> bool"
                .into(),
            is_static: true,
            statements: Some(vec![format!(
                "return reinterpret_cast<const {}*>(component.instance)->ensure_instantiated();",
                item_tree_class_name
            )]),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "item_geometry".into(),
            signature:
                "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index) -> slint::cbindgen_private::LogicalRect"
                    .into(),
            is_static: true,
            statements: Some(vec![format!(
                "return reinterpret_cast<const {}*>(component.instance)->item_geometry(index);",
                item_tree_class_name
            ), ]),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "accessible_role".into(),
            signature:
                "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index) -> slint::cbindgen_private::AccessibleRole"
                    .into(),
            is_static: true,
            statements: Some(vec![format!(
                "return reinterpret_cast<const {}*>(component.instance)->accessible_role(index);",
                item_tree_class_name
            )]),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "accessible_string_property".into(),
            signature:
                "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index, slint::cbindgen_private::AccessibleStringProperty what, slint::SharedString *result) -> bool"
                    .into(),
            is_static: true,
            statements: Some(vec![format!(
                "if (auto r = reinterpret_cast<const {}*>(component.instance)->accessible_string_property(index, what)) {{ *result = *r; return true; }} else {{ return false; }}",
                item_tree_class_name
            )]),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "accessibility_action".into(),
            signature:
                "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index, const slint::cbindgen_private::AccessibilityAction *action) -> void"
                    .into(),
            is_static: true,
            statements: Some(vec![format!(
                "reinterpret_cast<const {}*>(component.instance)->accessibility_action(index, *action);",
                item_tree_class_name
            )]),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "supported_accessibility_actions".into(),
            signature:
                "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index) -> uint32_t"
                    .into(),
            is_static: true,
            statements: Some(vec![format!(
                "return reinterpret_cast<const {}*>(component.instance)->supported_accessibility_actions(index);",
                item_tree_class_name
            )]),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "element_infos".into(),
            signature:
                "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] uint32_t index, [[maybe_unused]] slint::SharedString *result) -> bool"
                    .into(),
            is_static: true,
            statements: Some(if root.has_debug_info {
                vec![
                    format!("if (auto infos = reinterpret_cast<const {}*>(component.instance)->element_infos(index)) {{ *result = *infos; }};",
                    item_tree_class_name),
                    "return true;".into()
                ]
            } else {
                vec!["return false;".into()]
            }),
            ..Default::default()
        }),
    ));

    let window_adapter_vtable_statements = if needs_window_adapter {
        vec![format!(
            "*reinterpret_cast<slint::private_api::WindowAdapterRc*>(result) = reinterpret_cast<const {item_tree_class_name}*>(component.instance)->globals->window().window_handle();"
        )]
    } else {
        // Tray-only units have no `WindowAdapter`. The runtime initializes
        // `*result` to None before calling, so leaving it untouched reports
        // "no adapter" — and crucially `do_create=true` no longer silently
        // materializes a hidden window adapter.
        vec![]
    };
    target_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: "window_adapter".into(),
            signature:
                "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] bool do_create, [[maybe_unused]] slint::cbindgen_private::Option<slint::private_api::WindowAdapterRc>* result) -> void"
                    .into(),
            is_static: true,
            statements: Some(window_adapter_vtable_statements),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        Access::Public,
        Declaration::Var(Var {
            ty: "static const slint::private_api::ItemTreeVTable".into(),
            name: "static_vtable".into(),
            ..Default::default()
        }),
    ));

    file.definitions.push(Declaration::Var(Var {
        ty: "const slint::private_api::ItemTreeVTable".into(),
        name: format_smolstr!("{}::static_vtable", item_tree_class_name),
        init: Some(format!(
            "{{ visit_children, get_item_ref, get_subtree_range, get_subtree, \
                get_item_tree, parent_node, embed_component, subtree_index, layout_info, \
                ensure_instantiated, \
                item_geometry, accessible_role, accessible_string_property, accessibility_action, \
                supported_accessibility_actions, element_infos, window_adapter, \
                slint::private_api::drop_in_place<{item_tree_class_name}>, slint::private_api::dealloc }}"
        )),
        ..Default::default()
    }));

    let mut create_parameters = Vec::new();
    let mut init_parent_parameters = "";

    if let Some(parent) = &parent_ctx {
        let parent_type =
            format!("class {} const *", ident(&root.sub_components[parent.sub_component].name));
        create_parameters.push(format!("{parent_type} parent"));

        init_parent_parameters = ", parent";
    }

    let mut create_code = vec![
        format!(
            "auto self_rc = vtable::VRc<slint::private_api::ItemTreeVTable, {0}>::make();",
            target_struct.name
        ),
        format!("auto self = const_cast<{0} *>(&*self_rc);", target_struct.name),
        "self->self_weak = vtable::VWeak(self_rc).into_dyn();".into(),
    ];

    if is_popup {
        create_code.push("self->globals = globals;".into());
        create_parameters.push("const SharedGlobals *globals".into());
    } else if parent_ctx.is_none() {
        create_code.push("slint::cbindgen_private::slint_ensure_backend();".into());

        #[cfg(feature = "bundle-translations")]
        if let Some(translations) = &root.translations {
            let lang_len = translations.languages.len();
            create_code.push(format!(
                "std::array<slint::cbindgen_private::Slice<uint8_t>, {lang_len}> languages {{ {} }};",
                translations
                    .languages
                    .iter()
                    .map(|(l, _)| format!("slint::private_api::string_to_slice({l:?})"))
                    .join(", ")
            ));
            create_code.push(format!("slint::cbindgen_private::slint_translate_set_bundled_languages(slint::private_api::make_slice(std::span(languages)), \
                                                                                                     slint::private_api::make_slice(reinterpret_cast<uint32_t *>(slint_translation_bundle_decimal_separators), {}));",
                                                                                                     translations.languages.len()));
        }

        create_code.push("self->globals = &self->m_globals;".into());
        create_code.push("self->m_globals.root_weak = self->self_weak;".into());
        // Now that `globals` and `root_weak` are set, the globals can be initialized: their
        // init may resolve the root window adapter through these (see `init_globals`).
        create_code.push("self->m_globals.init_globals();".into());
    }

    let global_access =
        if !is_popup && parent_ctx.is_some() { "parent->globals" } else { "self->globals" };
    create_code.extend([
        format!(
            "slint::private_api::register_item_tree(&self_rc.into_dyn(), {global_access}->m_window);",
        ),
        format!("self->init({global_access}, self->self_weak, 0, 1 {init_parent_parameters});"),
    ]);

    // Repeaters run their user_init() code from Repeater::ensure_updated() after update() initialized model_data/index.
    // And in PopupWindow this is also called by the runtime
    if parent_ctx.is_none() && !is_popup {
        if !is_system_tray_root {
            // Ensure that the window exists before user_init, consistent with the
            // Rust codegen order.
            create_code.push(format!("auto &window = {global_access}->window();"));
            create_code.push("self->user_init();".to_string());
            create_code.push("self->m_globals.window();".to_string());
            create_code.push(
                "slint::cbindgen_private::slint_windowrc_ensure_tree_instantiated(\
                 reinterpret_cast<const slint::cbindgen_private::WindowAdapterRcOpaque*>\
                 (&window.window_handle()));"
                    .to_string(),
            );
        } else {
            create_code.push("self->user_init();".to_string());
        }
    }

    create_code
        .push(format!("return slint::ComponentHandle<{0}>{{ self_rc }};", target_struct.name));

    target_struct.members.push((
        Access::Public,
        Declaration::Function(Function {
            name: "create".into(),
            signature: format!(
                "({}) -> slint::ComponentHandle<{}>",
                create_parameters.join(","),
                target_struct.name
            ),
            statements: Some(create_code),
            is_static: true,
            ..Default::default()
        }),
    ));

    let destructor = vec![String::from(
        "if (auto &window = globals->m_window) window->window_handle().unregister_item_tree(this, item_array());",
    )];

    target_struct.members.push((
        Access::Public,
        Declaration::Function(Function {
            name: format_smolstr!("~{}", target_struct.name),
            signature: "()".to_owned(),
            is_constructor_or_destructor: true,
            statements: Some(destructor),
            ..Default::default()
        }),
    ));
}

fn generate_sub_component(
    target_struct: &mut Struct,
    component: llr::SubComponentIdx,
    root: &llr::CompilationUnit,
    parent_ctx: Option<&ParentScope>,
    field_access: Access,
    file: &mut File,
    conditional_includes: &ConditionalIncludes,
) {
    let globals_type_ptr = "const class SharedGlobals*";

    let mut init_parameters = vec![
        format!("{} globals", globals_type_ptr),
        "slint::cbindgen_private::ItemTreeWeak enclosing_component".into(),
        "uint32_t tree_index".into(),
        "uint32_t tree_index_of_first_child".into(),
    ];

    let mut init: Vec<String> =
        vec!["auto self = this;".into(), "self->self_weak = enclosing_component;".into()];

    target_struct.members.push((
        Access::Public,
        Declaration::Var(Var {
            ty: "slint::cbindgen_private::ItemTreeWeak".into(),
            name: "self_weak".into(),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        field_access,
        Declaration::Var(Var {
            ty: globals_type_ptr.into(),
            name: "globals".into(),
            ..Default::default()
        }),
    ));
    init.push("self->globals = globals;".into());

    target_struct.members.push((
        field_access,
        Declaration::Var(Var {
            ty: "uint32_t".into(),
            name: "tree_index_of_first_child".into(),
            ..Default::default()
        }),
    ));
    init.push("this->tree_index_of_first_child = tree_index_of_first_child;".into());

    target_struct.members.push((
        field_access,
        Declaration::Var(Var {
            ty: "uint32_t".into(),
            name: "tree_index".into(),
            ..Default::default()
        }),
    ));
    init.push("self->tree_index = tree_index;".into());

    if let Some(parent_ctx) = &parent_ctx {
        let parent_type = ident(&root.sub_components[parent_ctx.sub_component].name);
        init_parameters.push(format!("class {parent_type} const *parent"));

        target_struct.members.push((
            field_access,
            Declaration::Var(Var {
                ty: format_smolstr!(
                    "vtable::VWeakMapped<slint::private_api::ItemTreeVTable, class {parent_type} const>"
                )
                .clone(),
                name: "parent".into(),
                ..Default::default()
            }),
        ));
        init.push(format!("self->parent = vtable::VRcMapped<slint::private_api::ItemTreeVTable, const {parent_type}>(parent->self_weak.lock().value(), parent);"));
    }

    let ctx = EvaluationContext::new_sub_component(
        root,
        component,
        CppGeneratorContext { global_access: "self->globals".into(), conditional_includes },
        parent_ctx,
    );

    let component = &root.sub_components[component];

    let parent_ctx = ParentScope::new(&ctx, None);

    component.popup_windows.iter().for_each(|popup| {
        let component_id = ident(&root.sub_components[popup.item_tree.root].name);
        let mut popup_struct = Struct { name: component_id.clone(), ..Default::default() };
        generate_item_tree(
            &mut popup_struct,
            &popup.item_tree,
            root,
            Some(&parent_ctx),
            true,
            component_id,
            Access::Public,
            file,
            conditional_includes,
        );
        file.definitions.extend(popup_struct.extract_definitions());
        file.declarations.push(Declaration::Struct(popup_struct));
    });
    for menu in &component.menu_item_trees {
        let component_id = ident(&root.sub_components[menu.root].name);
        let mut menu_struct = Struct { name: component_id.clone(), ..Default::default() };
        generate_item_tree(
            &mut menu_struct,
            menu,
            root,
            Some(&parent_ctx),
            false,
            component_id,
            Access::Public,
            file,
            conditional_includes,
        );
        file.definitions.extend(menu_struct.extract_definitions());
        file.declarations.push(Declaration::Struct(menu_struct));
    }

    for property in component.properties.iter() {
        let cpp_name = field_name(&property.name);
        let ty =
            format_smolstr!("slint::private_api::Property<{}>", property.ty.cpp_type().unwrap());
        target_struct.members.push((
            field_access,
            Declaration::Var(Var { ty, name: cpp_name, ..Default::default() }),
        ));
    }
    for callback in component.callbacks.iter() {
        let cpp_name = field_name(&callback.name);
        let param_types = callback.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
        let ty = format_smolstr!(
            "slint::private_api::Callback<{}({})>",
            callback.ret_ty.cpp_type().unwrap(),
            param_types.join(", ")
        );
        target_struct.members.push((
            field_access,
            Declaration::Var(Var { ty, name: cpp_name, ..Default::default() }),
        ));
        if callback.needs_tracker {
            let tracker_name = callback_tracker_name(&callback.name);
            target_struct.members.push((
                field_access,
                Declaration::Var(Var {
                    ty: "slint::private_api::Property<uint8_t>".into(),
                    name: tracker_name,
                    ..Default::default()
                }),
            ));
        }
    }

    for (i, _) in component.change_callbacks.iter().enumerate() {
        target_struct.members.push((
            field_access,
            Declaration::Var(Var {
                ty: "slint::private_api::ChangeTracker".into(),
                name: format_smolstr!("change_tracker{}", i),
                ..Default::default()
            }),
        ));
    }

    let mut user_init = vec!["[[maybe_unused]] auto self = this;".into()];

    let mut children_visitor_cases = Vec::new();
    let mut subtrees_ranges_cases = Vec::new();
    let mut subtrees_components_cases = Vec::new();
    let mut ensure_instantiated_stmts: Vec<String> = Vec::new();

    // The pre-init code (custom font registration) runs before the property initialization.
    init.extend(component.pre_init_code.iter().map(|e| {
        let mut expr_str = compile_expression(&e.borrow(), &ctx);
        expr_str.push(';');
        expr_str
    }));

    for sub in &component.sub_components {
        let sub_field = field_name(&sub.name);
        let sub_sc = &root.sub_components[sub.ty];
        let local_tree_index: u32 = sub.index_in_tree as _;
        let local_index_of_first_child: u32 = sub.index_of_first_child_in_tree as _;

        // For children of sub-components, the item index generated by the generate_item_indices pass
        // starts at 1 (0 is the root element).
        let global_index = if local_tree_index == 0 {
            "tree_index".into()
        } else {
            format!("tree_index_of_first_child + {local_tree_index} - 1")
        };
        let global_children = if local_index_of_first_child == 0 {
            "0".into()
        } else {
            format!("tree_index_of_first_child + {local_index_of_first_child} - 1")
        };

        init.push(format!(
            "this->{sub_field}.init(globals, self_weak.into_dyn(), {global_index}, {global_children});"
        ));
        user_init.push(format!("this->{sub_field}.user_init();"));

        let sub_component_repeater_count = sub_sc.repeater_count(root);
        if sub_component_repeater_count > 0 {
            let mut case_code = String::new();
            let repeater_offset = sub.repeater_offset;

            for local_repeater_index in 0..sub_component_repeater_count {
                write!(case_code, "case {}: ", repeater_offset + local_repeater_index).unwrap();
            }

            children_visitor_cases.push(format!(
                "\n        {case_code} {{
                        return self->{sub_field}.visit_dynamic_children(dyn_index - {repeater_offset}, order, visitor);
                    }}",
            ));
            subtrees_ranges_cases.push(format!(
                "\n        {case_code} {{
                        return self->{sub_field}.subtree_range(dyn_index - {repeater_offset});
                    }}",
            ));
            subtrees_components_cases.push(format!(
                "\n        {case_code} {{
                        self->{sub_field}.subtree_component(dyn_index - {repeater_offset}, subtree_index, result);
                        return;
                    }}",
            ));
            ensure_instantiated_stmts
                .push(format!("_changed |= self->{sub_field}.ensure_instantiated();"));
        }

        target_struct.members.push((
            field_access,
            Declaration::Var(Var {
                ty: ident(&sub_sc.name),
                name: sub_field,
                ..Default::default()
            }),
        ));
    }

    for (i, _) in component.popup_windows.iter().enumerate() {
        target_struct.members.push((
            field_access,
            Declaration::Var(Var {
                ty: ident("mutable uint32_t"),
                name: format_smolstr!("popup_id_{}", i),
                ..Default::default()
            }),
        ));
    }

    for twb in &component.two_way_bindings {
        let p1 = access_local_member(&twb.prop1, &ctx);
        if let Some(info) = twb.resolve_model(&ctx) {
            init.push(generate_model_two_way_binding(&ctx, &info, &p1, &twb.field_access));
        } else if twb.field_access.is_empty() {
            let ty = ctx.relative_property_ty(&twb.prop1, 0).cpp_type().unwrap();
            init.push(
                access_member(&twb.prop2, &ctx).then(|p2| {
                    format!("slint::private_api::Property<{ty}>::link_two_way(&{p1}, &{p2})",)
                }) + ";",
            );
        } else {
            let prop2_ty = ctx.property_ty(&twb.prop2);
            let cpp_ty = prop2_ty.cpp_type().unwrap();
            let (access, _) = lower_field_access_chain("x".into(), prop2_ty, &twb.field_access);
            init.push(
                access_member(&twb.prop2, &ctx).then(|p2|
                    format!("slint::private_api::Property<{cpp_ty}>::link_two_way_with_map(&{p2}, &{p1}, [](const auto &x){{ return {access}; }}, [](auto &x, const auto &v){{ {access} = v; }})")
                ) + ";",
            );
        }
    }

    let mut properties_init_code = Vec::new();
    for (prop, expression) in &component.property_init {
        handle_property_init(prop, expression, &mut properties_init_code, &ctx)
    }
    for prop in &component.const_properties {
        let p = access_local_member(prop, &ctx);
        properties_init_code.push(format!("{p}.set_constant();"));
    }

    // Create all member components for the header
    for item in &component.items {
        target_struct.members.push((
            field_access,
            Declaration::Var(Var {
                ty: format_smolstr!("slint::cbindgen_private::{}", ident(&item.ty.class_name)),
                name: field_name(&item.name),
                init: Some("{}".to_owned()),
                ..Default::default()
            }),
        ));
    }

    for (idx, repeated) in component.repeated.iter_enumerated() {
        let sc = &root.sub_components[repeated.sub_tree.root];
        let data_type = repeated.data_prop.map(|data_prop| sc.properties[data_prop].ty.clone());

        generate_repeated_component(
            repeated,
            root,
            ParentScope::new(&ctx, Some(idx)),
            data_type.as_ref(),
            file,
            conditional_includes,
        );

        let idx = usize::from(idx);
        let repeater_id = format_smolstr!("repeater_{}", idx);

        let model = compile_expression(&repeated.model.borrow(), &ctx);

        // FIXME: optimize  if repeated.model.is_constant()
        properties_init_code.push(format!(
            "self->{repeater_id}.set_model_binding([self] {{ (void)self; return {model}; }});",
        ));

        if let Some(listview) = &repeated.listview {
            let content_y = access_member(&listview.content_y, &ctx).unwrap();
            let lv_w = access_member(&listview.listview_width, &ctx).unwrap();
            let lv_h = access_member(&listview.listview_height, &ctx).unwrap();
            let content_w = listview.content_width.as_ref().map_or_else(
                || "nullptr".to_string(),
                |w| {
                    let w = access_member(w, &ctx).unwrap();
                    format!("&{w}")
                },
            );
            let content_h = listview.content_height.as_ref().map_or_else(
                || "nullptr".to_string(),
                |h| {
                    let h = access_member(h, &ctx).unwrap();
                    format!("&{h}")
                },
            );

            children_visitor_cases.push(format!(
                "\n        case {idx}: {{
                self->{repeater_id}.track_changes_listview({content_w}, {content_h}, &{content_y}, {lv_w}.get(), &{lv_h});
                return self->{repeater_id}.visit(order, visitor);
            }}",
            ));
            ensure_instantiated_stmts.push(format!(
                "_changed |= self->{repeater_id}.ensure_updated_listview(self, {content_w}, {content_h}, &{content_y}, {lv_w}.get(), {lv_h}.get());"
            ));
        } else {
            children_visitor_cases.push(format!(
                "\n        case {idx}: {{
                return self->{repeater_id}.visit(order, visitor);
            }}",
            ));
            ensure_instantiated_stmts
                .push(format!("_changed |= self->{repeater_id}.ensure_updated(self);"));
        }
        subtrees_ranges_cases.push(format!(
            "\n        case {idx}: {{
                self->{repeater_id}.track_instance_changes();
                return self->{repeater_id}.index_range();
            }}",
        ));
        subtrees_components_cases.push(format!(
            "\n        case {idx}: {{
                *result = self->{repeater_id}.instance_at(subtree_index);
                return;
            }}",
        ));

        let rep_type = match data_type {
            Some(data_type) => {
                format_smolstr!(
                    "slint::private_api::Repeater<class {}, {}>",
                    ident(&sc.name),
                    data_type.cpp_type().unwrap()
                )
            }
            None => format_smolstr!("slint::private_api::Conditional<class {}>", ident(&sc.name)),
        };
        target_struct.members.push((
            field_access,
            Declaration::Var(Var { ty: rep_type, name: repeater_id, ..Default::default() }),
        ));
    }

    init.extend(properties_init_code);

    user_init.extend(component.init_code.iter().map(|e| {
        let mut expr_str = compile_expression(&e.borrow(), &ctx);
        expr_str.push(';');
        expr_str
    }));

    user_init.extend(component.change_callbacks.iter().enumerate().map(|(idx, (p, e))| {
        let code = compile_expression(&e.borrow(), &ctx);
        let prop = compile_expression(&llr::Expression::PropertyReference(p.clone()), &ctx);
        format!("self->change_tracker{idx}.init(self, [](auto self) {{ return {prop}; }}, []([[maybe_unused]] auto self, auto) {{ {code}; }});")
    }));

    if !component.timers.is_empty() {
        let mut update_timers = vec!["auto self = this;".into()];
        for (i, tmr) in component.timers.iter().enumerate() {
            user_init.push("self->update_timers();".to_string());
            let name = format_smolstr!("timer{}", i);
            let running = compile_expression(&tmr.running.borrow(), &ctx);
            let interval = compile_expression(&tmr.interval.borrow(), &ctx);
            let callback = compile_expression(&tmr.triggered.borrow(), &ctx);
            update_timers.push(format!(
                "{{ std::int64_t millis = ({running}) ? static_cast<std::int64_t>({interval}) : -1;"
            ));
            update_timers.push("if (millis >= 0) {".into());
            update_timers.push("   auto interval = std::chrono::milliseconds(millis);".into());
            update_timers.push(format!(
                "   if (!self->{name}.running() || self->{name}.interval() != interval)"
            ));
            update_timers.push(format!("       self->{name}.start(slint::TimerMode::Repeated, interval, [self] {{ {callback}; }});"));
            update_timers.push(format!("}} else {{ self->{name}.stop(); }} }}"));
            target_struct.members.push((
                field_access,
                Declaration::Var(Var { ty: "slint::Timer".into(), name, ..Default::default() }),
            ));
        }
        target_struct.members.push((
            field_access,
            Declaration::Function(Function {
                name: "update_timers".into(),
                signature: "() -> void".into(),
                statements: Some(update_timers),
                ..Default::default()
            }),
        ));
    }

    target_struct.members.extend(
        generate_functions(component.functions.as_ref(), &ctx).map(|x| (Access::Public, x)),
    );

    target_struct.members.push((
        field_access,
        Declaration::Function(Function {
            name: "init".into(),
            signature: format!("({}) -> void", init_parameters.join(",")),
            statements: Some(init),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        field_access,
        Declaration::Function(Function {
            name: "user_init".into(),
            signature: "() -> void".into(),
            statements: Some(user_init),
            ..Default::default()
        }),
    ));

    target_struct.members.push((
        field_access,
        Declaration::Function(Function {
            name: "layout_info".into(),
            signature: "(slint::cbindgen_private::Orientation o) const -> slint::cbindgen_private::LayoutInfo"
                .into(),
            statements: Some(vec![
                "[[maybe_unused]] auto self = this;".into(),
                format!(
                    "return o == slint::cbindgen_private::Orientation::Horizontal ? {} : {};",
                    compile_expression(&component.layout_info_h.borrow(), &ctx),
                    compile_expression(&component.layout_info_v.borrow(), &ctx)
                ),
            ]),
            ..Default::default()
        }),
    ));

    let mut dispatch_item_function =
        |name: &str, signature: &str, forward_args: &str, code: Vec<String>| {
            let mut code = ["[[maybe_unused]] auto self = this;".into()]
                .into_iter()
                .chain(code)
                .collect::<Vec<_>>();

            let mut else_ = "";
            for sub in &component.sub_components {
                let sub_sc = &ctx.compilation_unit.sub_components[sub.ty];
                let sub_items_count = sub_sc.child_item_count(ctx.compilation_unit);
                code.push(format!("{else_}if (index == {}) {{", sub.index_in_tree,));
                code.push(format!(
                    "    return self->{}.{name}(0{forward_args});",
                    field_name(&sub.name)
                ));
                if sub_items_count > 1 {
                    code.push(format!(
                        "}} else if (index >= {} && index < {}) {{",
                        sub.index_of_first_child_in_tree,
                        sub.index_of_first_child_in_tree + sub_items_count - 1
                            + sub_sc.repeater_count(ctx.compilation_unit)
                    ));
                    code.push(format!(
                        "    return self->{}.{name}(index - {}{forward_args});",
                        field_name(&sub.name),
                        sub.index_of_first_child_in_tree - 1
                    ));
                }
                else_ = "} else ";
            }
            let ret =
                if signature.contains("->") && !signature.contains("-> void") { "{}" } else { "" };
            code.push(format!("{else_}return {ret};"));
            target_struct.members.push((
                field_access,
                Declaration::Function(Function {
                    name: name.into(),
                    signature: signature.into(),
                    statements: Some(code),
                    ..Default::default()
                }),
            ));
        };

    let mut item_geometry_cases = vec!["switch (index) {".to_string()];
    item_geometry_cases.extend(
        component
            .geometries
            .iter()
            .enumerate()
            .filter_map(|(i, x)| x.as_ref().map(|x| (i, x)))
            .map(|(index, expr)| {
                format!(
                    "    case {index}: return slint::private_api::convert_anonymous_rect({});",
                    compile_expression(&expr.borrow(), &ctx)
                )
            }),
    );
    item_geometry_cases.push("}".into());

    dispatch_item_function(
        "item_geometry",
        "(uint32_t index) const -> slint::cbindgen_private::Rect",
        "",
        item_geometry_cases,
    );

    let mut accessible_role_cases = vec!["switch (index) {".into()];
    let mut accessible_string_cases = vec!["switch ((index << 8) | uintptr_t(what)) {".into()];
    let mut accessibility_action_cases =
        vec!["switch ((index << 8) | uintptr_t(action.tag)) {".into()];
    let mut supported_accessibility_actions = BTreeMap::<u32, BTreeSet<_>>::new();
    for ((index, what), expr) in &component.accessible_prop {
        let e = compile_expression(&expr.borrow(), &ctx);
        if what == "Role" {
            accessible_role_cases.push(format!("    case {index}: return {e};"));
        } else if let Some(what) = what.strip_prefix("Action") {
            let label = format!(
                "    case ({index} << 8) | uintptr_t(slint::cbindgen_private::AccessibilityAction::Tag::{what}):"
            );
            let arg_count = crate::generator::accessibility_action_argument_count(what);
            accessibility_action_cases.push(if arg_count == 0 {
                format!("{label} return {e};")
            } else {
                let member = ident(&crate::generator::to_kebab_case(what));
                let args = (0..arg_count)
                    .map(|i| format!("[[maybe_unused]] auto arg_{i} = action.{member}._{i}; "))
                    .join("");
                format!("{label} {{ {args}return {e}; }}")
            });
            supported_accessibility_actions
                .entry(*index)
                .or_default()
                .insert(format!("slint::cbindgen_private::SupportedAccessibilityAction_{what}"));
        } else {
            accessible_string_cases.push(format!("    case ({index} << 8) | uintptr_t(slint::cbindgen_private::AccessibleStringProperty::{what}): return {e};"));
        }
    }
    accessible_role_cases.push("}".into());
    accessible_string_cases.push("}".into());
    accessibility_action_cases.push("}".into());

    let mut supported_accessibility_actions_cases = vec!["switch (index) {".into()];
    supported_accessibility_actions_cases.extend(supported_accessibility_actions.into_iter().map(
        |(index, values)| format!("    case {index}: return {};", values.into_iter().join("|")),
    ));
    supported_accessibility_actions_cases.push("}".into());

    dispatch_item_function(
        "accessible_role",
        "(uint32_t index) const -> slint::cbindgen_private::AccessibleRole",
        "",
        accessible_role_cases,
    );
    dispatch_item_function(
        "accessible_string_property",
        "(uint32_t index, slint::cbindgen_private::AccessibleStringProperty what) const -> std::optional<slint::SharedString>",
        ", what",
        accessible_string_cases,
    );

    dispatch_item_function(
        "accessibility_action",
        "(uint32_t index, const slint::cbindgen_private::AccessibilityAction &action) const -> void",
        ", action",
        accessibility_action_cases,
    );

    dispatch_item_function(
        "supported_accessibility_actions",
        "(uint32_t index) const -> uint32_t",
        "",
        supported_accessibility_actions_cases,
    );

    let mut element_infos_cases = vec!["switch (index) {".to_string()];
    element_infos_cases.extend(
        component
            .element_infos
            .iter()
            .map(|(index, ids)| format!("    case {index}: return \"{ids}\";")),
    );
    element_infos_cases.push("}".into());

    dispatch_item_function(
        "element_infos",
        "(uint32_t index) const -> std::optional<slint::SharedString>",
        "",
        element_infos_cases,
    );

    {
        let mut stmts = vec![
            "[[maybe_unused]] auto self = this;".to_owned(),
            "bool _changed = false;".to_owned(),
        ];
        stmts.extend(ensure_instantiated_stmts);
        stmts.push("return _changed;".to_owned());
        target_struct.members.push((
            field_access,
            Declaration::Function(Function {
                name: "ensure_instantiated".into(),
                signature: "() const -> bool".into(),
                statements: Some(stmts),
                ..Default::default()
            }),
        ));
    }

    if !children_visitor_cases.is_empty() {
        target_struct.members.push((
            field_access,
            Declaration::Function(Function {
                name: "visit_dynamic_children".into(),
                signature: "(uint32_t dyn_index, [[maybe_unused]] slint::private_api::TraversalOrder order, [[maybe_unused]] slint::private_api::ItemVisitorRefMut visitor) const -> uint64_t".into(),
                statements: Some(vec![
                    "    auto self = this;".to_owned(),
                    format!("    switch(dyn_index) {{ {} }};", children_visitor_cases.join("")),
                    "    std::abort();".to_owned(),
                ]),
                ..Default::default()
            }),
        ));
        target_struct.members.push((
            field_access,
            Declaration::Function(Function {
                name: "subtree_range".into(),
                signature: "(uintptr_t dyn_index) const -> slint::private_api::IndexRange".into(),
                statements: Some(vec![
                    "[[maybe_unused]] auto self = this;".to_owned(),
                    format!("    switch(dyn_index) {{ {} }};", subtrees_ranges_cases.join("")),
                    "    std::abort();".to_owned(),
                ]),
                ..Default::default()
            }),
        ));
        target_struct.members.push((
            field_access,
            Declaration::Function(Function {
                name: "subtree_component".into(),
                signature: "(uintptr_t dyn_index, [[maybe_unused]] uintptr_t subtree_index, [[maybe_unused]] slint::private_api::ItemTreeWeak *result) const -> void".into(),
                statements: Some(vec![
                    "[[maybe_unused]] auto self = this;".to_owned(),
                    format!("    switch(dyn_index) {{ {} }};", subtrees_components_cases.join("")),
                    "    std::abort();".to_owned(),
                ]),
                ..Default::default()
            }),
        ));
    }
}

/// Generates the `layout_item_info` member function for a repeated component struct.
/// Dispatches by `child_index` to per-child layout info queries, supporting static children
/// and inner repeaters within a row child template.
fn generate_layout_item_info_decl(
    root_sc: &llr::SubComponent,
    ctx: &EvaluationContext,
) -> Declaration {
    const SIGNATURE: &str = "(slint::cbindgen_private::Orientation o, [[maybe_unused]] std::optional<size_t> child_index) const -> slint::cbindgen_private::LayoutItemInfo";

    if root_sc.row_child_templates.is_none()
        || (root_sc.grid_layout_children.is_empty()
            && !llr::has_inner_repeaters(&root_sc.row_child_templates))
    {
        return Declaration::Function(Function {
            name: "layout_item_info".into(),
            signature: SIGNATURE.to_owned(),
            statements: Some(vec!["return { layout_info({&static_vtable, const_cast<void *>(static_cast<const void *>(this))}, o) };".into()]),
            ..Function::default()
        });
    }

    let templates = root_sc.row_child_templates.as_ref().unwrap();
    let n = templates.len();

    // Generate a sequential scan through all templates in declaration order.
    // Count up from 0; for Static entries check count == index, for Repeated entries
    // check whether index falls within [count, count + inner_len).
    let mut body = String::from(
        "[[maybe_unused]] auto self = this;\n\
         if (child_index.has_value()) {\n\
             size_t index = *child_index;\n\
             size_t count = 0;\n",
    );
    for (i, entry) in templates.iter().enumerate() {
        let is_last = i + 1 == n;
        match entry {
            llr::RowChildTemplateInfo::Static { child_index } => {
                let child = &root_sc.grid_layout_children[*child_index];
                let layout_info_h_code = compile_expression(&child.layout_info_h.borrow(), ctx);
                let layout_info_v_code = compile_expression(&child.layout_info_v.borrow(), ctx);
                let advance = if is_last { String::new() } else { "count += 1;\n".to_owned() };
                write!(
                    body,
                    "if (count == index) {{\n\
                         return {{ (o == slint::cbindgen_private::Orientation::Horizontal) ? ({layout_info_h_code}) : ({layout_info_v_code}) }};\n\
                     }}\n\
                     {advance}",
                )
                .unwrap();
            }
            llr::RowChildTemplateInfo::Repeated { repeater_index } => {
                let inner_rep_id = format!("repeater_{}", usize::from(*repeater_index));
                let advance =
                    if is_last { String::new() } else { "count += inner_len;\n".to_owned() };
                write!(
                    body,
                    "{{\n\
                     self->{inner_rep_id}.track_instance_changes();\n\
                     size_t inner_len = {inner_rep_id}.len();\n\
                     if (index >= count && index - count < inner_len) {{\n\
                         if (auto vrc = {inner_rep_id}.instance_at(index - count).lock()) {{\n\
                             auto vref = vrc->borrow();\n\
                             return {{ vref.vtable->layout_info(vref, o) }};\n\
                         }}\n\
                     }}\n\
                     {advance}}}\n",
                )
                .unwrap();
            }
        }
    }
    body.push_str(
        // Phantom cell: return "unconstrained" info (matches Rust's LayoutInfo::default()).
        // field order: max, max_percent, min, min_percent, preferred, stretch
        "return { slint::cbindgen_private::LayoutInfo{ std::numeric_limits<float>::max(), 100.f, 0, 0, 0, 0 } };\n\
         }\n\
         return { layout_info({&static_vtable, const_cast<void *>(static_cast<const void *>(this))}, o) };",
    );
    Declaration::Function(Function {
        name: "layout_item_info".into(),
        signature: SIGNATURE.to_owned(),
        statements: Some(vec![body]),
        ..Function::default()
    })
}

fn generate_flexbox_layout_item_info_decl(
    root_sc: &llr::SubComponent,
    ctx: &EvaluationContext,
) -> Vec<Declaration> {
    const SIGNATURE: &str = "(slint::cbindgen_private::Orientation o, [[maybe_unused]] std::optional<size_t> child_index) const -> slint::cbindgen_private::FlexboxLayoutItemInfo";

    let for_repeated_compiled = root_sc
        .flexbox_layout_item_info_for_repeated
        .as_ref()
        .map(|expr| compile_expression(&expr.borrow(), ctx));

    let body = if let Some(compiled) = &for_repeated_compiled {
        // Break the height-for-width recursion for a repeated instance in a
        // column FlexboxLayout: use the constrained vertical info (measured at
        // its preferred width via layoutinfo-v-with-constraint) instead of
        // reading self.width through the parent flex cache.
        let v_constrained = root_sc
            .layout_info_v_constrained_for_repeated
            .as_ref()
            .map(|e| {
                let v = compile_expression(&e.borrow(), ctx);
                format!(
                    "if (o == slint::cbindgen_private::Orientation::Vertical && !child_index.has_value()) {{ \
                         info.constraint = {v}; return info; }} "
                )
            })
            .unwrap_or_default();
        // Mirror of `v_constrained` for the other axis: a width-for-height
        // instance (e.g. a wrapping column FlexboxLayout) must not read
        // self.height. Use the unbounded constrained horizontal info.
        let h_constrained = root_sc
            .layout_info_h_constrained_for_repeated
            .as_ref()
            .map(|e| {
                let h = compile_expression(&e.borrow(), ctx);
                format!(
                    "if (o == slint::cbindgen_private::Orientation::Horizontal && !child_index.has_value()) {{ \
                         info.constraint = {h}; return info; }} "
                )
            })
            .unwrap_or_default();
        format!(
            "[[maybe_unused]] auto self = this; \
             auto info = {compiled}; \
             {v_constrained}\
             {h_constrained}\
             info.constraint = layout_item_info(o, child_index).constraint; \
             return info;"
        )
    } else {
        // Equivalent of the Rust trait default `layout_item_info(o).into()`, whose
        // props are FlexItemProps::default() (note flex_shrink defaults to 1, not 0).
        "auto base = layout_item_info(o, child_index); \
         return { base.constraint, { 0.0f, 1.0f, -1.0f, slint::cbindgen_private::CrossAxisSelfAlignment::Auto, 0 } };"
            .to_owned()
    };

    // A column FlexboxLayout calls this with its real container width so a
    // height-for-width instance wraps to the same height as a static cell. The
    // expression reads the `flex_cross_width` parameter.
    //
    // `layout_info_v_at_cross_width_for_repeated` is only set for a
    // height-for-width root, which also forces `flexbox_layout_item_info_for_repeated`
    // (see `lower_to_item_tree`), so the other cases have no width-dependent
    // height and just delegate. Unlike Rust, where `RepeatedItemTree` provides
    // defaults and the generator emits nothing when there is no flex info, a C++
    // repeated struct has no base class: both members are always emitted, and the
    // delegating body below is the equivalent of the Rust trait default.
    let at_cross_width_body = match (
        &for_repeated_compiled,
        root_sc.layout_info_v_at_cross_width_for_repeated.as_ref(),
    ) {
        (Some(compiled), Some(e)) => {
            let v = compile_expression(&e.borrow(), ctx);
            format!(
                "[[maybe_unused]] auto self = this; \
                 auto info = {compiled}; \
                 info.constraint = {v}; \
                 return info;"
            )
        }
        _ => "return flexbox_layout_item_info(slint::cbindgen_private::Orientation::Vertical, std::nullopt);"
            .to_owned(),
    };

    // Mirror of `at_cross_width_body`: a FlexboxLayout calls this with the
    // height it assigned so a width-for-height instance resolves to the width
    // it really needs. The expression reads the `flex_cross_height` parameter.
    let at_cross_height_body = match (
        &for_repeated_compiled,
        root_sc.layout_info_h_at_cross_height_for_repeated.as_ref(),
    ) {
        (Some(compiled), Some(e)) => {
            let h = compile_expression(&e.borrow(), ctx);
            format!(
                "[[maybe_unused]] auto self = this; \
                 auto info = {compiled}; \
                 info.constraint = {h}; \
                 return info;"
            )
        }
        _ => "return flexbox_layout_item_info(slint::cbindgen_private::Orientation::Horizontal, std::nullopt);"
            .to_owned(),
    };

    vec![
        Declaration::Function(Function {
            name: "flexbox_layout_item_info".into(),
            signature: SIGNATURE.to_owned(),
            statements: Some(vec![body]),
            ..Function::default()
        }),
        Declaration::Function(Function {
            name: "flexbox_layout_item_info_at_cross_width".into(),
            signature:
                "([[maybe_unused]] float flex_cross_width) const -> slint::cbindgen_private::FlexboxLayoutItemInfo"
                    .to_owned(),
            statements: Some(vec![at_cross_width_body]),
            ..Function::default()
        }),
        Declaration::Function(Function {
            name: "flexbox_layout_item_info_at_cross_height".into(),
            signature:
                "([[maybe_unused]] float flex_cross_height) const -> slint::cbindgen_private::FlexboxLayoutItemInfo"
                    .to_owned(),
            statements: Some(vec![at_cross_height_body]),
            ..Function::default()
        }),
    ]
}

/// Generates the `grid_layout_input_for_repeated` member function for a repeated component struct,
/// or returns `None` if the sub-component doesn't participate in a grid layout as a repeated row.
fn generate_grid_layout_input_decl(
    root_sc: &llr::SubComponent,
    ctx: &EvaluationContext,
) -> Option<Declaration> {
    let expr = root_sc.grid_layout_input_for_repeated.as_ref()?;
    let compiled_expr = compile_expression(&expr.borrow(), ctx);
    // Ensure the expression is terminated as a statement (CodeBlock with 1 item doesn't add semicolon)
    let statement =
        if compiled_expr.is_empty() || compiled_expr.ends_with(';') || compiled_expr.ends_with('}')
        {
            compiled_expr
        } else {
            format!("{compiled_expr};")
        };

    // Generate fill code for all template children in declaration order
    let fn_body: Vec<String> = if llr::has_inner_repeaters(&root_sc.row_child_templates) {
        let templates = root_sc.row_child_templates.as_ref().unwrap();
        let static_count = llr::static_child_count(templates);
        let auto_val = i_slint_common::ROW_COL_AUTO;
        // When static children are present: fill them via the compiled expression into a temp
        // array, then interleave with inner-repeater cells in declaration order.
        // When there are no static children: skip the array/index variables entirely to avoid
        // unused-variable warnings when compiling the generated C++ with -Werror.
        let mut fill_code = if static_count > 0 {
            format!(
                "std::array<slint::cbindgen_private::GridLayoutInputData, {static_count}> statics{{}};\n\
                 {{\n\
                     // Intentionally shadows the outer `result` so the compiled statement fills `statics`.\n\
                     auto result = std::span<slint::cbindgen_private::GridLayoutInputData>{{statics.data(), statics.size()}};\n\
                     {statement}\n\
                 }}\n\
                 size_t static_idx = 0;\n\
                 size_t write_idx = 0;\n"
            )
        } else {
            String::from("size_t write_idx = 0;\n")
        };
        for entry in templates {
            match entry {
                llr::RowChildTemplateInfo::Static { .. } => {
                    write!(
                        fill_code,
                        "if (write_idx < result.size()) {{\n\
                             auto data = statics[static_idx];\n\
                             data.new_row = (write_idx == 0) && new_row;\n\
                             result[write_idx] = data;\n\
                         }}\n\
                         ++write_idx; ++static_idx;\n"
                    )
                    .unwrap();
                }
                llr::RowChildTemplateInfo::Repeated { repeater_index } => {
                    let inner_rep_id = format!("repeater_{}", usize::from(*repeater_index));
                    // Let the inner cell report its own col/row/colspan/rowspan.
                    write!(
                        fill_code,
                        "this->{inner_rep_id}.track_instance_changes();\n\
                         {inner_rep_id}.for_each([&](const auto &sub_comp) {{\n\
                             if (write_idx < result.size()) {{\n\
                                 sub_comp->grid_layout_input_for_repeated((write_idx == 0) && new_row, result.subspan(write_idx, 1));\n\
                             }}\n\
                             ++write_idx;\n\
                         }});\n"
                    )
                    .unwrap();
                }
            }
        }
        // Padding loop: fill remaining slots with sentinel values. C++ zero-initializes
        // result (col=0, row=0), so we need to specify auto explicitly.
        write!(
            fill_code,
            "while (write_idx < result.size()) {{\n\
                 result[write_idx] = slint::cbindgen_private::GridLayoutInputData {{ false, {auto_val:.1}f, {auto_val:.1}f, 1.0f, 1.0f }};\n\
                 ++write_idx;\n\
             }}\n"
        )
        .unwrap();
        vec!["[[maybe_unused]] auto self = this;".into(), fill_code]
    } else {
        vec!["[[maybe_unused]] auto self = this;".into(), statement]
    };

    Some(Declaration::Function(Function {
        name: "grid_layout_input_for_repeated".into(),
        signature: "([[maybe_unused]] bool new_row, [[maybe_unused]] std::span<slint::cbindgen_private::GridLayoutInputData> result) const -> void"
            .to_owned(),
        statements: Some(fn_body),
        ..Function::default()
    }))
}

fn generate_repeated_component(
    repeated: &llr::RepeatedElement,
    unit: &llr::CompilationUnit,
    parent_ctx: ParentScope,
    model_data_type: Option<&Type>,
    file: &mut File,
    conditional_includes: &ConditionalIncludes,
) {
    let root_sc = &unit.sub_components[repeated.sub_tree.root];
    let repeater_id = ident(&root_sc.name);
    let mut repeater_struct = Struct { name: repeater_id.clone(), ..Default::default() };
    generate_item_tree(
        &mut repeater_struct,
        &repeated.sub_tree,
        unit,
        Some(&parent_ctx),
        false,
        repeater_id.clone(),
        Access::Public,
        file,
        conditional_includes,
    );

    let ctx = EvaluationContext {
        compilation_unit: unit,
        current_scope: EvaluationScope::SubComponent(repeated.sub_tree.root, Some(&parent_ctx)),
        generator_state: CppGeneratorContext {
            global_access: "self->globals".into(),
            conditional_includes,
        },
        argument_types: &[],
    };

    let access_prop = |idx: &llr::PropertyIdx| {
        access_member(
            &llr::LocalMemberReference { sub_component_path: Vec::new(), reference: (*idx).into() }
                .into(),
            &ctx,
        )
        .unwrap()
    };
    let index_prop = repeated.index_prop.iter().map(access_prop);
    let data_prop = repeated.data_prop.iter().map(access_prop);

    if let Some(model_data_type) = model_data_type {
        let mut update_statements = vec!["[[maybe_unused]] auto self = this;".into()];
        update_statements.extend(index_prop.map(|prop| format!("{prop}.set(i);")));
        update_statements.extend(data_prop.map(|prop| format!("{prop}.set(data);")));

        repeater_struct.members.push((
            Access::Public, // Because Repeater accesses it
            Declaration::Function(Function {
                name: "update_data".into(),
                signature: format!(
                    "([[maybe_unused]] int i, [[maybe_unused]] const {} &data) const -> void",
                    model_data_type.cpp_type().unwrap()
                ),
                statements: Some(update_statements),
                ..Function::default()
            }),
        ));
    }

    repeater_struct.members.push((
        Access::Public, // Because Repeater accesses it
        Declaration::Function(Function {
            name: "init".into(),
            signature: "() -> void".into(),
            statements: Some(vec!["user_init();".into()]),
            ..Function::default()
        }),
    ));

    if let Some(listview) = &repeated.listview {
        let p_y = access_member(&listview.prop_y, &ctx).unwrap();
        let p_height = access_member(&listview.prop_height, &ctx).unwrap();

        repeater_struct.members.push((
            Access::Public, // Because Repeater accesses it
            Declaration::Function(Function {
                name: "listview_layout".into(),
                signature: "(float *offset_y) const -> float".to_owned(),
                statements: Some(vec![
                    "[[maybe_unused]] auto self = this;".into(),
                    format!("{}.set(*offset_y);", p_y),
                    format!("*offset_y += {}.get();", p_height),
                    "return layout_info({&static_vtable, const_cast<void *>(static_cast<const void *>(this))}, slint::cbindgen_private::Orientation::Horizontal).min;".into(),
                ]),
                ..Function::default()
            }),
        ));
    } else {
        repeater_struct.members.push((
            Access::Public, // Because Repeater accesses it
            generate_layout_item_info_decl(root_sc, &ctx),
        ));
        for decl in generate_flexbox_layout_item_info_decl(root_sc, &ctx) {
            repeater_struct.members.push((Access::Public, decl));
        }
        if let Some(decl) = generate_grid_layout_input_decl(root_sc, &ctx) {
            repeater_struct.members.push((Access::Public, decl));
        }
    }

    if let Some(index_prop) = repeated.index_prop {
        // Override default subtree_index function implementation
        let subtree_index_func = repeater_struct
            .members
            .iter_mut()
            .find(|(_, d)| matches!(d, Declaration::Function(f) if f.name == "subtree_index"));

        if let Declaration::Function(f) = &mut subtree_index_func.unwrap().1 {
            let index = access_prop(&index_prop);
            f.statements = Some(vec![
                format!(
                    "auto self = reinterpret_cast<const {}*>(component.instance);",
                    repeater_id
                ),
                format!("return {index}.get();"),
            ]);
        }
    }

    file.definitions.extend(repeater_struct.extract_definitions().collect::<Vec<_>>());
    file.declarations.push(Declaration::Struct(repeater_struct));
}

fn generate_global(
    file: &mut File,
    conditional_includes: &ConditionalIncludes,
    global_idx: llr::GlobalIdx,
    global: &llr::GlobalComponent,
    root: &llr::CompilationUnit,
) {
    let mut global_struct = Struct { name: ident(&global.name), ..Default::default() };

    for property in global.properties.iter() {
        let cpp_name = field_name(&property.name);
        let ty =
            format_smolstr!("slint::private_api::Property<{}>", property.ty.cpp_type().unwrap());
        global_struct.members.push((
            // FIXME: this is public (and also was public in the pre-llr generator) because other generated code accesses the
            // fields directly. But it shouldn't be from an API point of view since the same `global_struct` class is public API
            // when the global is exported and exposed in the public component.
            Access::Public,
            Declaration::Var(Var { ty, name: cpp_name, ..Default::default() }),
        ));
    }
    for callback in global.callbacks.iter().filter(|p| p.use_count.get() > 0) {
        let cpp_name = field_name(&callback.name);
        let param_types = callback.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
        let ty = format_smolstr!(
            "slint::private_api::Callback<{}({})>",
            callback.ret_ty.cpp_type().unwrap(),
            param_types.join(", ")
        );
        global_struct.members.push((
            // FIXME: this is public (and also was public in the pre-llr generator) because other generated code accesses the
            // fields directly. But it shouldn't be from an API point of view since the same `global_struct` class is public API
            // when the global is exported and exposed in the public component.
            Access::Public,
            Declaration::Var(Var { ty, name: cpp_name, ..Default::default() }),
        ));
        if callback.needs_tracker {
            let tracker_name = callback_tracker_name(&callback.name);
            global_struct.members.push((
                Access::Public,
                Declaration::Var(Var {
                    ty: "slint::private_api::Property<uint8_t>".into(),
                    name: tracker_name,
                    ..Default::default()
                }),
            ));
        }
    }

    let mut init = vec!["(void)this->globals;".into()];
    let ctx = EvaluationContext::new_global(
        root,
        global_idx,
        CppGeneratorContext { global_access: "this->globals".into(), conditional_includes },
    );

    for (property_index, expression) in &global.init_values {
        handle_property_init(
            &llr::LocalMemberReference::from(property_index.clone()).into(),
            expression,
            &mut init,
            &ctx,
        )
    }

    for i in global.change_callbacks.keys() {
        global_struct.members.push((
            Access::Private,
            Declaration::Var(Var {
                ty: "slint::private_api::ChangeTracker".into(),
                name: format_smolstr!("change_tracker{}", usize::from(*i)),
                ..Default::default()
            }),
        ));
    }

    init.extend(global.change_callbacks.iter().map(|(p, e)| {
        let code = compile_expression(&e.borrow(), &ctx);
        let prop = access_member(&llr::LocalMemberReference::from(*p).into(), &ctx);
        prop.then(|prop| {
            format!("this->change_tracker{}.init(this, [this]([[maybe_unused]] auto self) {{ return {prop}.get(); }}, [this]([[maybe_unused]] auto self, auto) {{ {code}; }});", usize::from(*p))
        })
    }));

    global_struct.members.push((
        Access::Public,
        Declaration::Function(Function {
            name: ident(&global.name),
            signature: "(const class SharedGlobals *globals)".into(),
            is_constructor_or_destructor: true,
            statements: Some(Vec::new()),
            constructor_member_initializers: vec!["globals(globals)".into()],
            ..Default::default()
        }),
    ));
    global_struct.members.push((
        Access::Private,
        Declaration::Function(Function {
            name: ident("init"),
            signature: "() -> void".into(),
            statements: Some(init),
            ..Default::default()
        }),
    ));
    global_struct.members.push((
        Access::Private,
        Declaration::Var(Var {
            ty: "const class SharedGlobals*".into(),
            name: "globals".into(),
            ..Default::default()
        }),
    ));
    global_struct.friends.push(SmolStr::new_static(SHARED_GLOBAL_CLASS));

    generate_public_api_for_properties(
        &mut global_struct.members,
        &global.public_properties,
        &global.private_properties,
        &ctx,
    );
    global_struct
        .members
        .extend(generate_functions(global.functions.as_ref(), &ctx).map(|x| (Access::Public, x)));

    file.definitions.extend(global_struct.extract_definitions().collect::<Vec<_>>());
    file.declarations.push(Declaration::Struct(global_struct));
}

fn generate_global_builtin(
    file: &mut File,
    conditional_includes: &ConditionalIncludes,
    global_idx: llr::GlobalIdx,
    global: &llr::GlobalComponent,
    root: &llr::CompilationUnit,
) {
    let mut global_struct = Struct { name: ident(&global.name), ..Default::default() };
    let ctx = EvaluationContext::new_global(
        root,
        global_idx,
        CppGeneratorContext {
            global_access: "\n#error binding in builtin global\n".into(),
            conditional_includes,
        },
    );

    global_struct.members.push((
        Access::Public,
        Declaration::Function(Function {
            name: ident(&global.name),
            signature: format!(
                "(std::shared_ptr<slint::cbindgen_private::{}> builtin)",
                ident(&global.name)
            ),
            is_constructor_or_destructor: true,
            statements: Some(Vec::new()),
            constructor_member_initializers: vec!["builtin(std::move(builtin))".into()],
            ..Default::default()
        }),
    ));
    global_struct.members.push((
        Access::Private,
        Declaration::Var(Var {
            ty: format_smolstr!(
                "std::shared_ptr<slint::cbindgen_private::{}>",
                ident(&global.name)
            ),
            name: "builtin".into(),
            ..Default::default()
        }),
    ));
    global_struct.friends.push(SmolStr::new_static(SHARED_GLOBAL_CLASS));

    generate_public_api_for_properties(
        &mut global_struct.members,
        &global.public_properties,
        &global.private_properties,
        &ctx,
    );
    file.definitions.extend(global_struct.extract_definitions().collect::<Vec<_>>());
    file.declarations.push(Declaration::Struct(global_struct));
}

fn generate_functions<'a>(
    functions: &'a [llr::Function],
    ctx: &'a EvaluationContext<'_>,
) -> impl Iterator<Item = Declaration> + 'a {
    functions.iter().map(|f| {
        let mut ctx2 = ctx.clone();
        ctx2.argument_types = &f.args;
        let ret = if f.ret_ty != Type::Void { "return " } else { "" };
        let body = vec![
            "[[maybe_unused]] auto self = this;".into(),
            format!("{ret}{};", compile_expression(&f.code.borrow(), &ctx2)),
        ];
        Declaration::Function(Function {
            name: concatenate_ident(&format_smolstr!("fn_{}", f.name)),
            signature: format!(
                "({}) const -> {}",
                f.args
                    .iter()
                    .enumerate()
                    .map(|(i, ty)| format!("[[maybe_unused]] {} arg_{}", ty.cpp_type().unwrap(), i))
                    .join(", "),
                f.ret_ty.cpp_type().unwrap()
            ),
            statements: Some(body),
            ..Default::default()
        })
    })
}

fn generate_public_api_for_properties(
    declarations: &mut Vec<(Access, Declaration)>,
    public_properties: &llr::PublicProperties,
    private_properties: &llr::PrivateProperties,
    ctx: &EvaluationContext,
) {
    for (name, p) in public_properties {
        let access = access_member(&p.prop, ctx).unwrap();

        if let Type::Callback(callback) = &p.ty {
            let param_types =
                callback.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
            let callback_emitter = vec![
                "slint::private_api::assert_main_thread();".into(),
                "[[maybe_unused]] auto self = this;".into(),
                format!(
                    "return {}.call({});",
                    access,
                    (0..callback.args.len()).map(|i| format!("arg_{i}")).join(", ")
                ),
            ];
            declarations.push((
                Access::Public,
                Declaration::Function(Function {
                    name: accessor_names::cpp_accessor_name(name, AccessorKind::Invoker),
                    signature: format!(
                        "({}) const -> {}",
                        param_types
                            .iter()
                            .enumerate()
                            .map(|(i, ty)| format!("{ty} arg_{i}"))
                            .join(", "),
                        callback.return_type.cpp_type().unwrap()
                    ),
                    statements: Some(callback_emitter),
                    ..Default::default()
                }),
            ));
            let tracker = access_callback_tracker_cpp(&p.prop, ctx);
            let mut on_stmts = vec![
                "slint::private_api::assert_main_thread();".into(),
                "[[maybe_unused]] auto self = this;".into(),
                format!("{}.set_handler(std::forward<Functor>(callback_handler));", access),
            ];
            if let Some(t) = &tracker {
                on_stmts.push(t.then(|x| format!("{x}.mark_dirty();")));
            }
            declarations.push((
                Access::Public,
                Declaration::Function(Function {
                    name: accessor_names::cpp_accessor_name(name, AccessorKind::Handler),
                    template_parameters: Some(format!(
                        "std::invocable<{}> Functor",
                        param_types.join(", "),
                    )),
                    signature: "(Functor && callback_handler) const".into(),
                    statements: Some(on_stmts),
                    ..Default::default()
                }),
            ));
        } else if let Type::Function(function) = &p.ty {
            let param_types =
                function.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
            let ret = function.return_type.cpp_type().unwrap();
            let call_code = vec![
                "[[maybe_unused]] auto self = this;".into(),
                format!(
                    "{}{access}({});",
                    if function.return_type == Type::Void { "" } else { "return " },
                    (0..function.args.len()).map(|i| format!("arg_{i}")).join(", ")
                ),
            ];
            declarations.push((
                Access::Public,
                Declaration::Function(Function {
                    name: accessor_names::cpp_accessor_name(name, AccessorKind::Invoker),
                    signature: format!(
                        "({}) const -> {ret}",
                        param_types
                            .iter()
                            .enumerate()
                            .map(|(i, ty)| format!("{ty} arg_{i}"))
                            .join(", "),
                    ),
                    statements: Some(call_code),
                    ..Default::default()
                }),
            ));
        } else {
            let cpp_property_type = p.ty.cpp_type().expect("Invalid type in public properties");
            let prop_getter: Vec<String> = vec![
                "slint::private_api::assert_main_thread();".into(),
                "[[maybe_unused]] auto self = this;".into(),
                format!("return {}.get();", access),
            ];
            declarations.push((
                Access::Public,
                Declaration::Function(Function {
                    name: accessor_names::cpp_accessor_name(name, AccessorKind::Getter),
                    signature: format!("() const -> {cpp_property_type}"),
                    statements: Some(prop_getter),
                    ..Default::default()
                }),
            ));

            if !p.read_only() {
                let prop_setter: Vec<String> = vec![
                    "slint::private_api::assert_main_thread();".into(),
                    "[[maybe_unused]] auto self = this;".into(),
                    property_set_value_code(&p.prop, "value", ctx) + ";",
                ];
                declarations.push((
                    Access::Public,
                    Declaration::Function(Function {
                        name: accessor_names::cpp_accessor_name(name, AccessorKind::Setter),
                        signature: format!("(const {cpp_property_type} &value) const -> void"),
                        statements: Some(prop_setter),
                        ..Default::default()
                    }),
                ));
            } else {
                declarations.push((
                    Access::Private,
                    Declaration::Function(Function {
                        name: accessor_names::cpp_accessor_name(name, AccessorKind::Setter),
                        signature: format!(
                            "(const {cpp_property_type} &) const = SLINT_DELETED_FUNCTION(\"property '{}' is declared as 'out' (read-only). Declare it as 'in' or 'in-out' to enable the setter\")", name
                        ),
                        ..Default::default()
                    }),
                ));
            }
        }
    }

    for (name, ty) in private_properties {
        if let Type::Function(function) = &ty {
            let param_types = function.args.iter().map(|t| t.cpp_type().unwrap()).join(", ");
            declarations.push((
                Access::Private,
                Declaration::Function(Function {
                    name: accessor_names::cpp_accessor_name(name, AccessorKind::Invoker),
                    signature: format!(
                        "({param_types}) const = SLINT_DELETED_FUNCTION(\"the function '{name}' is declared as private. Declare it as 'public'\")",
                    ),
                    ..Default::default()
                }),
            ));
        } else {
            declarations.push((
                Access::Private,
                Declaration::Function(Function {
                    name: accessor_names::cpp_accessor_name(name, AccessorKind::Getter),
                    signature: format!(
                        "() const = SLINT_DELETED_FUNCTION(\"the property '{name}' is declared as private. Declare it as 'in', 'out', or 'in-out' to make it public\")",
                    ),
                    ..Default::default()
                }),
            ));
            declarations.push((
                Access::Private,
                Declaration::Function(Function {
                    name: accessor_names::cpp_accessor_name(name, AccessorKind::Setter),
                    signature: format!(
                        "(const auto &) const = SLINT_DELETED_FUNCTION(\"property '{name}' is declared as private. Declare it as 'in' or 'in-out' to make it public\")",
                    ),
                    ..Default::default()
                }),
            ));
        }
    }
}

fn follow_sub_component_path<'a>(
    compilation_unit: &'a llr::CompilationUnit,
    root: llr::SubComponentIdx,
    sub_component_path: &[llr::SubComponentInstanceIdx],
) -> (String, &'a llr::SubComponent) {
    let mut compo_path = String::new();
    let mut sub_component = &compilation_unit.sub_components[root];
    for i in sub_component_path {
        let sub_component_name = field_name(&sub_component.sub_components[*i].name);
        write!(compo_path, "{sub_component_name}.").unwrap();
        sub_component = &compilation_unit.sub_components[sub_component.sub_components[*i].ty];
    }
    (compo_path, sub_component)
}

fn access_window_field(ctx: &EvaluationContext) -> String {
    format!("{}->window().window_handle()", ctx.generator_state.global_access)
}

/// Walks up `parent_level` parent pointers, starting from `self`.
fn parent_access_path(parent_level: usize) -> MemberAccess {
    let mut path = MemberAccess::Direct("self".to_string());
    for _ in 0..parent_level {
        path = path.and_then(|x| format!("{x}->parent.lock()"));
    }
    path
}

/// Returns the code that can access the given property (but without the set or get)
fn access_member(reference: &llr::MemberReference, ctx: &EvaluationContext) -> MemberAccess {
    match reference {
        llr::MemberReference::Relative { parent_level, local_reference } => {
            let path = parent_access_path(*parent_level);
            if let Some(sub_component) = ctx.parent_sub_component_idx(*parent_level) {
                let (compo_path, sub_component) = follow_sub_component_path(
                    ctx.compilation_unit,
                    sub_component,
                    &local_reference.sub_component_path,
                );
                match &local_reference.reference {
                    llr::LocalMemberIndex::Property(property_index) => {
                        let property_name =
                            field_name(&sub_component.properties[*property_index].name);
                        path.with_member(format!("->{compo_path}{property_name}"))
                    }
                    llr::LocalMemberIndex::Callback(callback_index) => {
                        let callback_name =
                            field_name(&sub_component.callbacks[*callback_index].name);
                        path.with_member(format!("->{compo_path}{callback_name}"))
                    }
                    llr::LocalMemberIndex::Function(function_index) => {
                        let function_name = ident(&sub_component.functions[*function_index].name);
                        path.with_member(format!("->{compo_path}fn_{function_name}"))
                    }
                    llr::LocalMemberIndex::Timer(timer_index) => path
                        .with_member(format!("->{compo_path}timer{}", usize::from(*timer_index))),
                    llr::LocalMemberIndex::Native { item_index, prop_name, .. } => {
                        let item_name = field_name(&sub_component.items[*item_index].name);
                        if prop_name.is_empty()
                            || matches!(
                                sub_component.items[*item_index].ty.lookup_property(prop_name),
                                Some(Type::Function { .. })
                            )
                        {
                            // then this is actually a reference to the element itself
                            // (or a call to a builtin member function)
                            path.with_member(format!("->{compo_path}{item_name}"))
                        } else {
                            let property_name = ident(prop_name);
                            path.with_member(format!("->{compo_path}{item_name}.{property_name}"))
                        }
                    }
                }
            } else if let Some(current_global) = ctx.current_global() {
                match &local_reference.reference {
                    llr::LocalMemberIndex::Property(property_index) => {
                        let property_name =
                            field_name(&current_global.properties[*property_index].name);
                        MemberAccess::Direct(format!("this->{property_name}"))
                    }
                    llr::LocalMemberIndex::Function(function_index) => {
                        let function_name = ident(&current_global.functions[*function_index].name);
                        MemberAccess::Direct(format!("this->fn_{function_name}"))
                    }
                    llr::LocalMemberIndex::Callback(callback_index) => {
                        let callback_name =
                            field_name(&current_global.callbacks[*callback_index].name);
                        MemberAccess::Direct(format!("this->{callback_name}"))
                    }
                    _ => unreachable!(),
                }
            } else {
                unreachable!()
            }
        }
        llr::MemberReference::Global { global_index, member } => {
            let global = &ctx.compilation_unit.globals[*global_index];
            // Builtin globals are structs from the C++ runtime library whose fields keep
            // the declared names
            let field = |name| if global.is_builtin { ident(name) } else { field_name(name) };
            let name = match member {
                llr::LocalMemberIndex::Property(property_index) => {
                    field(&global.properties[*property_index].name)
                }
                llr::LocalMemberIndex::Callback(callback_index) => {
                    field(&global.callbacks[*callback_index].name)
                }
                llr::LocalMemberIndex::Function(function_index) => {
                    ident(&format!("fn_{}", global.functions[*function_index].name))
                }
                _ => unreachable!(),
            };
            if matches!(ctx.current_scope, EvaluationScope::Global(i) if i == *global_index) {
                if global.is_builtin {
                    MemberAccess::Direct(format!("builtin->{name}"))
                } else {
                    MemberAccess::Direct(format!("this->{name}"))
                }
            } else {
                let global_access = &ctx.generator_state.global_access;
                let global_id = format!("global_{}", concatenate_ident(&global.name));
                MemberAccess::Direct(format!("{global_access}->{global_id}->{name}"))
            }
        }
    }
}

fn access_local_member(reference: &llr::LocalMemberReference, ctx: &EvaluationContext) -> String {
    access_member(&reference.clone().into(), ctx).unwrap()
}

/// Returns the C++ field name for the change-tracker property of a callback.
fn callback_tracker_name(callback_name: &str) -> SmolStr {
    format_smolstr!("callback_tracker_{}", callback_name.replace('-', "_"))
}

/// Returns the name of the C++ field holding a property, callback, item, or sub-component
/// instance.
/// The prefix keeps the field apart from generated member functions
/// (e.g. a callback named `set-foo` and the `set_foo` setter of a property `foo`)
/// and from reserved members such as `repeater_0` or `self_weak`.
fn field_name(name: &str) -> SmolStr {
    format_smolstr!("field_{}", concatenate_ident(name))
}

/// Returns the C++ code to access the change-tracker `Property<uint8_t>` for an exported callback.
/// Returns `None` if the callback doesn't have a tracker.
fn access_callback_tracker_cpp(
    reference: &llr::MemberReference,
    ctx: &EvaluationContext,
) -> Option<MemberAccess> {
    fn in_global(
        g: &llr::GlobalComponent,
        callback_idx: &llr::CallbackIdx,
        self_: &str,
    ) -> Option<MemberAccess> {
        if !g.callbacks[*callback_idx].needs_tracker {
            return None;
        }
        let tracker_name = callback_tracker_name(&g.callbacks[*callback_idx].name);
        Some(MemberAccess::Direct(format!("{self_}{tracker_name}")))
    }

    match reference {
        llr::MemberReference::Global {
            global_index,
            member: llr::LocalMemberIndex::Callback(callback_idx),
        } => {
            let global = &ctx.compilation_unit.globals[*global_index];
            if matches!(ctx.current_scope, EvaluationScope::Global(i) if i == *global_index) {
                in_global(global, callback_idx, "this->")
            } else {
                let global_access = &ctx.generator_state.global_access;
                let global_id = format!("global_{}", concatenate_ident(&global.name));
                in_global(global, callback_idx, &format!("{global_access}->{global_id}->"))
            }
        }
        llr::MemberReference::Relative { parent_level, local_reference } => {
            let llr::LocalMemberIndex::Callback(callback_idx) = &local_reference.reference else {
                return None;
            };
            if let Some(current_global) = ctx.current_global() {
                return in_global(current_global, callback_idx, "this->");
            }
            let sc_idx = ctx.parent_sub_component_idx(*parent_level)?;
            let (compo_path, sub_component) = follow_sub_component_path(
                ctx.compilation_unit,
                sc_idx,
                &local_reference.sub_component_path,
            );
            if !sub_component.callbacks[*callback_idx].needs_tracker {
                return None;
            }
            let tracker_name = callback_tracker_name(&sub_component.callbacks[*callback_idx].name);
            let path = parent_access_path(*parent_level);
            Some(path.with_member(format!("->{compo_path}{tracker_name}")))
        }
        _ => None,
    }
}

/// Helper to access a member property/callback of a component.
///
/// Because the parent can be deleted (issue #3464), this might be an option when accessing the parent
#[derive(Clone)]
enum MemberAccess {
    /// The string is just an expression
    Direct(String),
    /// The string is a an expression to an `std::optional`
    Option(String),
    /// The first string is an expression to an `std::optional`,
    /// the second is a string to be appended after dereferencing the optional
    /// like so: `<1>.transform([](auto &&x) { return x<2>; })`
    OptionWithMember(String, String),
}

impl MemberAccess {
    /// Used for code that is meant to return `()`
    fn then(&self, f: impl FnOnce(&str) -> String) -> String {
        match self {
            MemberAccess::Direct(t) => f(t),
            MemberAccess::Option(t) => {
                format!("slint::private_api::optional_then({t}, [&](auto&&x) {{ {}; }})", f("x"))
            }
            MemberAccess::OptionWithMember(t, m) => {
                format!(
                    "slint::private_api::optional_then({t}, [&](auto&&x) {{ {}; }})",
                    f(&format!("x{}", m))
                )
            }
        }
    }

    fn map_or_default(&self, f: impl FnOnce(&str) -> String) -> String {
        match self {
            MemberAccess::Direct(t) => f(t),
            MemberAccess::Option(t) => {
                format!(
                    "slint::private_api::optional_or_default(slint::private_api::optional_transform({t}, [&](auto&&x) {{ return {}; }}))",
                    f("x")
                )
            }
            MemberAccess::OptionWithMember(t, m) => {
                format!(
                    "slint::private_api::optional_or_default(slint::private_api::optional_transform({t}, [&](auto&&x) {{ return {}; }}))",
                    f(&format!("x{}", m))
                )
            }
        }
    }

    fn and_then(&self, f: impl Fn(&str) -> String) -> MemberAccess {
        match self {
            MemberAccess::Direct(t) => MemberAccess::Option(f(t)),
            MemberAccess::Option(t) => MemberAccess::Option(format!(
                "slint::private_api::optional_and_then({t}, [&](auto&&x) {{ return {}; }})",
                f("x")
            )),
            MemberAccess::OptionWithMember(t, m) => MemberAccess::Option(format!(
                "slint::private_api::optional_and_then({t}, [&](auto&&x) {{ return {}; }})",
                f(&format!("x{}", m))
            )),
        }
    }

    fn get_property(self) -> String {
        self.map_or_default(|x| format!("{x}.get()"))
    }

    /// To be used when we know that the reference was local
    #[track_caller]
    fn unwrap(self) -> String {
        match self {
            MemberAccess::Direct(t) => t,
            _ => panic!("not a local property?"),
        }
    }

    fn with_member(self, member: String) -> MemberAccess {
        match self {
            MemberAccess::Direct(t) => MemberAccess::Direct(format!("{t}{member}")),
            MemberAccess::Option(t) => MemberAccess::OptionWithMember(t, member),
            MemberAccess::OptionWithMember(t, m) => {
                MemberAccess::OptionWithMember(t, format!("{m}{member}"))
            }
        }
    }
}

/// Returns the NativeClass for a PropertyReference::InNativeItem
/// (or a InParent of InNativeItem )
/// As well as the property name
fn native_prop_info<'a, 'b>(
    item_ref: &'b llr::MemberReference,
    ctx: &'a EvaluationContext,
) -> (&'a NativeClass, &'b str) {
    let llr::MemberReference::Relative { parent_level, local_reference } = item_ref else {
        unreachable!()
    };
    let llr::LocalMemberIndex::Native { item_index, prop_name, .. } = &local_reference.reference
    else {
        unreachable!()
    };

    let (_, sub_component) = follow_sub_component_path(
        ctx.compilation_unit,
        ctx.parent_sub_component_idx(*parent_level).unwrap(),
        &local_reference.sub_component_path,
    );
    (&sub_component.items[*item_index].ty, prop_name)
}

fn shared_string_literal(string: &str) -> String {
    format!(r#"slint::SharedString(u8"{}")"#, escape_string(string))
}

impl std::fmt::Display for crate::expression_tree::ImageReference {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            crate::expression_tree::ImageReference::None => write!(f, r#"slint::Image()"#),
            resource_ref @ (crate::expression_tree::ImageReference::Path(_)
            | crate::expression_tree::ImageReference::Url(_)) => write!(
                f,
                r#"slint::Image::load_from_path(slint::SharedString(u8"{}"))"#,
                escape_string(resource_ref.source().unwrap())
            ),
            crate::expression_tree::ImageReference::DataUri(_) => {
                unreachable!("data: URIs are embedded before code generation")
            }
            crate::expression_tree::ImageReference::EmbeddedData { resource_id, extension } => {
                let symbol = format!("slint_embedded_resource_{resource_id}");
                write!(
                    f,
                    r#"slint::private_api::load_image_from_embedded_data({symbol}, "{}")"#,
                    escape_string(extension)
                )
            }
            crate::expression_tree::ImageReference::EmbeddedTexture { resource_id } => {
                write!(
                    f,
                    "slint::private_api::image_from_embedded_textures(&slint_embedded_resource_{resource_id})"
                )
            }
        }
    }
}

fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String {
    use llr::Expression;
    match expr {
        Expression::StringLiteral(s) => shared_string_literal(s),
        Expression::NumberLiteral(num) => {
            if num.is_nan() {
                "std::numeric_limits<double>::quiet_NaN()".to_string()
            } else if num.is_infinite() {
                if *num > 0. {
                    "std::numeric_limits<double>::infinity()".to_string()
                } else {
                    "-std::numeric_limits<double>::infinity()".to_string()
                }
            } else if num.abs() > 1_000_000_000. {
                // If the numbers are too big, decimal notation will give too many digit
                format!("{num:+e}")
            } else {
                num.to_string()
            }
        }
        Expression::BoolLiteral(b) => b.to_string(),
        Expression::KeysLiteral(ks) => {
            format!(
                "[&](const slint::SharedString &key, bool alt, bool control, bool shift, bool meta, bool ignoreShift, bool ignoreAlt) {{
                    slint::Keys out;
                    slint::private_api::make_keys(out, key, alt, control, shift, meta, ignoreShift, ignoreAlt);
                    return out;
                }}({}, {}, {}, {}, {}, {}, {})",
                shared_string_literal(&ks.key),
                ks.modifiers.alt,
                ks.modifiers.control,
                ks.modifiers.shift,
                ks.modifiers.meta,
                ks.ignore_shift,
                ks.ignore_alt,
            )
        }
        Expression::PropertyReference(nr) => access_member(nr, ctx).get_property(),
        Expression::BuiltinFunctionCall { function, arguments } => {
            compile_builtin_function_call(function.clone(), arguments, ctx)
        }
        Expression::CallBackCall { callback, arguments } => {
            let f = access_member(callback, ctx);
            let tracker_get = access_callback_tracker_cpp(callback, ctx)
                .map(|t| format!("(void)({}), ", t.get_property()))
                .unwrap_or_default();
            let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
            if expr.ty(ctx) == Type::Void {
                f.then(|f| format!("{tracker_get}{f}.call({})", a.join(",")))
            } else {
                f.map_or_default(|f| format!("({tracker_get}{f}.call({}))", a.join(",")))
            }
        }
        Expression::FunctionCall { function, arguments } => {
            let f = access_member(function, ctx);
            let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
            if expr.ty(ctx) == Type::Void {
                f.then(|f| format!("{}({})", f, a.join(",")))
            } else {
                f.map_or_default(|f| format!("{}({})", f, a.join(",")))
            }
        }
        Expression::ItemMemberFunctionCall { function } => {
            let item = access_member(function, ctx);
            let item_rc = access_item_rc(function, ctx);
            let window = access_window_field(ctx);
            let (native, name) = native_prop_info(function, ctx);
            let function_name = format!(
                "slint_{}_{}",
                native.class_name.to_lowercase(),
                ident(name).to_lowercase()
            );
            if expr.ty(ctx) == Type::Void {
                item.then(|item| {
                    format!("{function_name}(&{item}, &{window}.handle(), &{item_rc})")
                })
            } else {
                item.map_or_default(|item| {
                    format!("{function_name}(&{item}, &{window}.handle(), &{item_rc})")
                })
            }
        }
        Expression::ExtraBuiltinFunctionCall { function, arguments, return_ty: _ } => {
            let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
            format!("slint::private_api::{}({})", ident(function), a.join(","))
        }
        Expression::FunctionParameterReference { index, .. } => format!("arg_{index}"),
        Expression::StoreLocalVariable { name, value } => {
            format!("[[maybe_unused]] auto {} = {};", ident(name), compile_expression(value, ctx))
        }
        Expression::ReadLocalVariable { name, .. } => ident(name).to_string(),
        Expression::StructFieldAccess { base, name } => match base.ty(ctx) {
            Type::Struct(s) => struct_field_access(compile_expression(base, ctx), &s, name),
            _ => panic!("Expression::ObjectAccess's base expression is not an Object type"),
        },
        Expression::ArrayIndex { array, index } => {
            format!(
                "slint::private_api::access_array_index({}, {})",
                compile_expression(array, ctx),
                compile_expression(index, ctx)
            )
        }
        Expression::Cast { from, to } => {
            let f = compile_expression(from, ctx);
            match (from.ty(ctx), to) {
                (Type::Float32, Type::Int32) => {
                    format!("slint::private_api::saturating_float_to_int({f})")
                }
                (from, Type::String) if from.as_unit_product().is_some() => {
                    format!("slint::SharedString::from_number({f})")
                }
                (Type::Float32, Type::Model) | (Type::Int32, Type::Model) => {
                    format!(
                        "std::make_shared<slint::private_api::UIntModel>(std::max(0, slint::private_api::saturating_float_to_int({f})))"
                    )
                }
                (Type::Array(_), Type::Model) => f,
                (Type::Float32, Type::Color) => {
                    format!("slint::Color::from_argb_encoded({f})")
                }
                (Type::Color, Type::Brush) => {
                    format!("slint::Brush({f})")
                }
                (Type::Brush, Type::Color) => {
                    format!("{f}.color()")
                }
                (Type::Struct(lhs), Type::Struct(rhs)) => {
                    debug_assert_eq!(
                        lhs.fields, rhs.fields,
                        "cast of struct with deferent fields should be handled before llr"
                    );
                    match (&lhs.name, &rhs.name) {
                        (StructName::None, targetstruct) if targetstruct.is_some() => {
                            // Convert from an anonymous struct to a named one
                            format!(
                                "[&](const auto &o){{ {struct_name} s; {fields} return s; }}({obj})",
                                struct_name = to.cpp_type().unwrap(),
                                fields = lhs
                                    .fields
                                    .keys()
                                    .enumerate()
                                    .map(|(i, n)| format!("s.{} = std::get<{}>(o); ", ident(n), i))
                                    .join(""),
                                obj = f,
                            )
                        }
                        (sourcestruct, StructName::None) if sourcestruct.is_some() => {
                            // Convert from a named struct to an anonymous one
                            format!(
                                "[&](const auto &o){{ return std::make_tuple({}); }}({f})",
                                rhs.fields.keys().map(|n| format!("o.{}", ident(n))).join(", ")
                            )
                        }
                        _ => f,
                    }
                }
                (Type::Array(..), Type::PathData)
                    if matches!(
                        from.as_ref(),
                        Expression::Array { element_ty: Type::Struct { .. }, .. }
                    ) =>
                {
                    let path_elements = match from.as_ref() {
                        Expression::Array { element_ty: _, values, output: _ } => {
                            values.iter().map(|path_elem_expr| {
                                let (field_count, qualified_elem_type_name) =
                                    match path_elem_expr.ty(ctx) {
                                        Type::Struct(s) if s.name.is_some() => {
                                            (s.fields.len(), s.name.cpp_type().unwrap().clone())
                                        }
                                        _ => unreachable!(),
                                    };
                                // Turn slint::private_api::PathLineTo into `LineTo`
                                let elem_type_name = qualified_elem_type_name
                                    .split("::")
                                    .last()
                                    .unwrap()
                                    .strip_prefix("Path")
                                    .unwrap();
                                let elem_init = if field_count > 0 {
                                    compile_expression(path_elem_expr, ctx)
                                } else {
                                    String::new()
                                };
                                format!(
                                    "slint::private_api::PathElement::{elem_type_name}({elem_init})"
                                )
                            })
                        }
                        _ => {
                            unreachable!()
                        }
                    }
                    .collect::<Vec<_>>();
                    if !path_elements.is_empty() {
                        format!(
                            r#"[&](){{
                                slint::private_api::PathElement elements[{}] = {{
                                    {}
                                }};
                                return slint::private_api::PathData(&elements[0], std::size(elements));
                            }}()"#,
                            path_elements.len(),
                            path_elements.join(",")
                        )
                    } else {
                        "slint::private_api::PathData()".into()
                    }
                }
                (Type::Struct { .. }, Type::PathData)
                    if matches!(from.as_ref(), Expression::Struct { .. }) =>
                {
                    let (events, points) = match from.as_ref() {
                        Expression::Struct { ty: _, values } => (
                            compile_expression(&values["events"], ctx),
                            compile_expression(&values["points"], ctx),
                        ),
                        _ => {
                            unreachable!()
                        }
                    };
                    format!(
                        r#"[&](auto events, auto points){{
                            return slint::private_api::PathData(events.ptr, events.len, points.ptr, points.len);
                        }}({events}, {points})"#
                    )
                }
                (Type::Enumeration(e), Type::String) => {
                    let mut cases = e.values.iter().enumerate().map(|(idx, v)| {
                        let c = compile_expression(
                            &Expression::EnumerationValue(EnumerationValue {
                                value: idx,
                                enumeration: e.clone(),
                            }),
                            ctx,
                        );
                        format!("case {c}: return {v:?};")
                    });
                    format!(
                        "[&]() -> slint::SharedString {{ switch ({f}) {{ {} default: return {{}}; }} }}()",
                        cases.join(" ")
                    )
                }
                _ => f,
            }
        }
        Expression::CodeBlock(sub) => match sub.len() {
            0 => String::new(),
            1 => compile_expression(&sub[0], ctx),
            len => {
                let mut x = sub.iter().enumerate().map(|(i, e)| {
                    if i == len - 1 {
                        return_compile_expression(e, ctx, None) + ";"
                    } else {
                        compile_expression(e, ctx)
                    }
                });
                format!("[&]{{ {} }}()", x.join(";"))
            }
        },
        Expression::PropertyAssignment { property, value } => {
            let value = compile_expression(value, ctx);
            property_set_value_code(property, &value, ctx)
        }
        Expression::ModelDataAssignment { level, value } => {
            let value = compile_expression(value, ctx);
            let mut path = "self".to_string();
            let EvaluationScope::SubComponent(mut sc, mut par) = ctx.current_scope else {
                unreachable!()
            };
            let mut repeater_index = None;
            for _ in 0..=*level {
                let x = par.unwrap();
                par = x.parent;
                repeater_index = x.repeater_index;
                sc = x.sub_component;
                write!(path, "->parent.lock().value()").unwrap();
            }
            let repeater_index = repeater_index.unwrap();
            let local_reference = ctx.compilation_unit.sub_components[sc].repeated[repeater_index]
                .index_prop
                .unwrap()
                .into();
            let index_prop =
                llr::MemberReference::Relative { parent_level: *level, local_reference };
            let index_access = access_member(&index_prop, ctx).get_property();
            write!(path, "->repeater_{}", usize::from(repeater_index)).unwrap();
            format!("{path}.model_set_row_data({index_access}, {value})")
        }
        Expression::ArrayIndexAssignment { array, index, value } => {
            debug_assert!(matches!(array.ty(ctx), Type::Array(_)));
            let base_e = compile_expression(array, ctx);
            let index_e = compile_expression(index, ctx);
            let value_e = compile_expression(value, ctx);
            format!(
                "[&](auto index, const auto &base) {{ if (index >= 0. && std::size_t(index) < base->row_count()) base->set_row_data(index, {value_e}); }}({index_e}, {base_e})"
            )
        }
        Expression::SliceIndexAssignment { slice_name, index, value } => {
            let value_e = compile_expression(value, ctx);
            format!("{slice_name}[{index}] = {value_e}")
        }
        Expression::BinaryExpression { lhs, rhs, op } => {
            let lhs_str = compile_expression(lhs, ctx);
            let rhs_str = compile_expression(rhs, ctx);

            let lhs_ty = lhs.ty(ctx);

            if lhs_ty.as_unit_product().is_some() && (*op == '=' || *op == '!') {
                let op = if *op == '=' { "<" } else { ">=" };
                format!(
                    "(std::abs(float({lhs_str} - {rhs_str})) {op} std::numeric_limits<float>::epsilon())"
                )
            } else {
                let mut buffer = [0; 3];
                format!(
                    "({lhs_str} {op} {rhs_str})",
                    op = match op {
                        '=' => "==",
                        '!' => "!=",
                        '≤' => "<=",
                        '≥' => ">=",
                        '&' => "&&",
                        '|' => "||",
                        '/' => "/(float)",
                        '-' => "-(float)", // conversion to float to avoid overflow between unsigned
                        _ => op.encode_utf8(&mut buffer),
                    },
                )
            }
        }
        Expression::UnaryOp { sub, op } => {
            format!("({op} {sub})", sub = compile_expression(sub, ctx), op = op,)
        }
        Expression::ImageReference { resource_ref, nine_slice } => match &nine_slice {
            Some([a, b, c, d]) => {
                format!(
                    "([&] {{ auto image = {resource_ref}; image.set_nine_slice_edges({a}, {b}, {c}, {d}); return image; }})()"
                )
            }
            None => resource_ref.to_string(),
        },
        Expression::Condition { condition, true_expr, false_expr } => {
            let ty = expr.ty(ctx);
            let cond_code = compile_expression(condition, ctx);
            let cond_code = remove_parentheses(&cond_code);
            let true_code = compile_expression(true_expr, ctx);
            let false_code = compile_expression(false_expr, ctx);
            if ty == Type::Void {
                format!("if ({cond_code}) {{ {true_code}; }} else {{ {false_code}; }}")
            } else {
                format!("({cond_code} ? {true_code} : {false_code})")
            }
        }
        Expression::Array { element_ty, values, output } => {
            let ty = element_ty.cpp_type().unwrap();
            let mut val = values
                .iter()
                .map(|e| format!("{ty} ( {expr} )", expr = compile_expression(e, ctx), ty = ty));
            match output {
                llr::ArrayOutput::Model => format!(
                    "std::make_shared<slint::VectorModel<{ty}>>(std::vector<{ty}>{{ {val} }})",
                    ty = ty,
                    val = val.join(", ")
                ),
                llr::ArrayOutput::Slice => format!(
                    "slint::private_api::make_slice<{ty}>(std::array<{ty}, {count}>{{ {val} }}.data(), {count})",
                    count = values.len(),
                    ty = ty,
                    val = val.join(", ")
                ),
                llr::ArrayOutput::Vector => {
                    format!("std::vector<{ty}>{{ {val} }}", ty = ty, val = val.join(", "))
                }
            }
        }
        Expression::Struct { ty, values } => {
            if ty.name.is_none() {
                let mut elem = ty.fields.iter().map(|(k, t)| {
                    values
                        .get(k)
                        .map(|e| compile_expression(e, ctx))
                        .map(|e| {
                            // explicit conversion to avoid warning C4244 (possible loss of data) with MSVC
                            if t.as_unit_product().is_some() {
                                format!("{}({e})", t.cpp_type().unwrap())
                            } else {
                                e
                            }
                        })
                        .unwrap_or_else(|| "(Error: missing member in object)".to_owned())
                });
                format!("std::make_tuple({})", elem.join(", "))
            } else {
                format!(
                    "[&]({args}){{ {ty} o{{}}; {fields}return o; }}({vals})",
                    args = (0..values.len()).map(|i| format!("const auto &a_{i}")).join(", "),
                    ty = Type::Struct(ty.clone()).cpp_type().unwrap(),
                    fields = values
                        .keys()
                        .enumerate()
                        .map(|(i, f)| format!("o.{} = a_{}; ", ident(f), i))
                        .join(""),
                    vals = values.values().map(|e| compile_expression(e, ctx)).join(", "),
                )
            }
        }
        Expression::MouseCursor(cursor) => match cursor {
            llr::MouseCursorInner::BuiltIn(cursor) => {
                let cursor = compile_expression(cursor.as_ref(), ctx);
                format!("slint::cbindgen_private::MouseCursorInner({cursor})")
            }
            llr::MouseCursorInner::CustomMouseCursor { image, hotspot_x, hotspot_y } => {
                let image = compile_expression(image.as_ref(), ctx);
                let hotspot_x = compile_expression(hotspot_x.as_ref(), ctx);
                let hotspot_y = compile_expression(hotspot_y.as_ref(), ctx);
                format!(
                    "slint::cbindgen_private::MouseCursorInner({image}, {hotspot_x}, {hotspot_y})"
                )
            }
        },
        Expression::EasingCurve(EasingCurve::Linear) => {
            "slint::cbindgen_private::EasingCurve()".into()
        }
        Expression::EasingCurve(EasingCurve::CubicBezier(a, b, c, d)) => format!(
            "slint::cbindgen_private::EasingCurve(slint::cbindgen_private::EasingCurve::Tag::CubicBezier, {a}, {b}, {c}, {d})"
        ),
        // The other curves have no parameters and their C++ Tag matches the variant name.
        Expression::EasingCurve(e) => {
            format!("slint::cbindgen_private::EasingCurve::Tag::{e:?}")
        }
        Expression::LinearGradient { angle, stops } => {
            let angle = compile_expression(angle, ctx);
            let mut stops_it = stops.iter().map(|(color, stop)| {
                let color = compile_expression(color, ctx);
                let position = compile_expression(stop, ctx);
                format!("slint::private_api::GradientStop{{ {color}, float({position}), }}")
            });
            format!(
                "[&] {{ const slint::private_api::GradientStop stops[] = {{ {} }}; return slint::Brush(slint::private_api::LinearGradientBrush({}, stops, {})); }}()",
                stops_it.join(", "),
                angle,
                stops.len()
            )
        }
        Expression::RadialGradient { center, radius, stops } => {
            let mut stops_it = stops.iter().map(|(color, stop)| {
                let color = compile_expression(color, ctx);
                let position = compile_expression(stop, ctx);
                format!("slint::private_api::GradientStop{{ {color}, float({position}), }}")
            });
            let center_setup = match (center, radius) {
                (Some((cx, cy)), Some(r)) => {
                    let cx = compile_expression(cx, ctx);
                    let cy = compile_expression(cy, ctx);
                    let r = compile_expression(r, ctx);
                    format!(
                        "return slint::Brush(slint::private_api::RadialGradientBrush(stops, {stops_count}, float({cx}), float({cy}), float({r})));",
                        stops_count = stops.len()
                    )
                }
                (Some((cx, cy)), None) => {
                    let cx = compile_expression(cx, ctx);
                    let cy = compile_expression(cy, ctx);
                    format!(
                        "return slint::Brush(slint::private_api::RadialGradientBrush(stops, {stops_count}, float({cx}), float({cy}), -1.0f));",
                        stops_count = stops.len()
                    )
                }
                (None, Some(r)) => {
                    let r = compile_expression(r, ctx);
                    format!(
                        "return slint::Brush(slint::private_api::RadialGradientBrush(stops, {stops_count}, std::numeric_limits<float>::quiet_NaN(), std::numeric_limits<float>::quiet_NaN(), float({r})));",
                        stops_count = stops.len()
                    )
                }
                (None, None) => {
                    format!(
                        "return slint::Brush(slint::private_api::RadialGradientBrush(stops, {}));",
                        stops.len()
                    )
                }
            };
            format!(
                "[&] {{ const slint::private_api::GradientStop stops[] = {{ {} }}; {} }}()",
                stops_it.join(", "),
                center_setup
            )
        }
        Expression::ConicGradient { from_angle, center, stops } => {
            let from_angle = compile_expression(from_angle, ctx);
            let mut stops_it = stops.iter().map(|(color, stop)| {
                let color = compile_expression(color, ctx);
                let position = compile_expression(stop, ctx);
                format!("slint::private_api::GradientStop{{ {color}, float({position}), }}")
            });
            let center_setup = if let Some((cx, cy)) = center {
                let cx = compile_expression(cx, ctx);
                let cy = compile_expression(cy, ctx);
                format!(
                    "return slint::Brush(slint::private_api::ConicGradientBrush(float({from_angle}), stops, {stops_count}, float({cx}), float({cy})));",
                    stops_count = stops.len()
                )
            } else {
                format!(
                    "return slint::Brush(slint::private_api::ConicGradientBrush(float({from_angle}), stops, {}));",
                    stops.len()
                )
            };
            format!(
                "[&] {{ const slint::private_api::GradientStop stops[] = {{ {} }}; {} }}()",
                stops_it.join(", "),
                center_setup
            )
        }
        Expression::EnumerationValue(value) => {
            let prefix =
                if value.enumeration.node.is_some() { "" } else { "slint::cbindgen_private::" };
            format!(
                "{prefix}{}::{}",
                ident(&value.enumeration.name),
                ident(&value.to_pascal_case()),
            )
        }
        Expression::LayoutCacheAccess {
            layout_cache_prop,
            index,
            repeater_index,
            entries_per_item,
        } => {
            let cache = access_member(layout_cache_prop, ctx);
            cache.map_or_default(|cache| {
                if let Some(ri) = repeater_index {
                    format!(
                        "slint::private_api::layout_cache_access({}.get(), {}, {}, {})",
                        cache,
                        index,
                        compile_expression(ri, ctx),
                        entries_per_item
                    )
                } else {
                    format!("{cache}.get()[{index}]")
                }
            })
        }
        Expression::GridRepeaterCacheAccess {
            layout_cache_prop,
            index,
            repeater_index,
            stride,
            child_offset,
            inner_repeater_index,
            entries_per_item,
        } => {
            let cache = access_member(layout_cache_prop, ctx);
            cache.map_or_default(|cache| {
                let stride_val = compile_expression(stride, ctx);
                let col_offset = if let Some(inner_ri) = inner_repeater_index {
                    format!(
                        "{} + {} * {}",
                        child_offset,
                        compile_expression(inner_ri, ctx),
                        entries_per_item
                    )
                } else {
                    child_offset.to_string()
                };
                format!(
                    "slint::private_api::layout_cache_grid_repeater_access({}.get(), {}, {}, {}, {})",
                    cache,
                    index,
                    compile_expression(repeater_index, ctx),
                    stride_val,
                    col_offset
                )
            })
        }
        Expression::WithLayoutItemInfo {
            cells_variable,
            repeater_indices_var_name,
            repeater_steps_var_name,
            elements,
            orientation,
            sub_expression,
        } => generate_with_layout_item_info(
            cells_variable,
            repeater_indices_var_name.as_ref().map(SmolStr::as_str),
            repeater_steps_var_name.as_ref().map(SmolStr::as_str),
            elements.as_ref(),
            *orientation,
            sub_expression,
            ctx,
        ),
        Expression::WithFlexboxLayoutItemInfo {
            cells_h_variable,
            cells_v_variable,
            flex_props_variable,
            repeater_indices_var_name,
            elements,
            repeated_cross_width,
            sub_expression,
        } => generate_with_flexbox_layout_item_info(
            cells_h_variable,
            cells_v_variable,
            flex_props_variable,
            repeater_indices_var_name.as_ref().map(SmolStr::as_str),
            elements.as_ref(),
            repeated_cross_width.as_deref(),
            sub_expression,
            ctx,
        ),
        Expression::SolveFlexboxLayoutWithMeasure {
            data,
            repeater_indices,
            measure_cells,
            default_cells,
            cells_variables,
        } => {
            let data = compile_expression(data, ctx);
            let repeater_indices = compile_expression(repeater_indices, ctx);
            // cbindgen does not expose `LayoutInfo::preferred_bounded()`, so
            // inline it: preferred_bounded = max(min(preferred, max), min).
            const BOUNDED: &str = "std::max(std::min(li.preferred, li.max), li.min)";
            // Without a repeater the cell index is known at compile time, so switch
            // on it (O(1) dispatch). With a repeater the count is only known at
            // runtime: walk the elements, advancing `cursor` by 1 per static cell
            // and by the repeater's instance count per repeater, until `index`'s
            // range is found.
            let (v_body, h_body) = if cells_variables.is_none() {
                let mut v_cases = String::new();
                let mut h_cases = String::new();
                for (i, item) in measure_cells.iter().enumerate() {
                    if let Either::Left((h_info, v_info)) = item {
                        let v = compile_expression(v_info, ctx);
                        let h = compile_expression(h_info, ctx);
                        v_cases.push_str(&format!(
                            "case {i}: {{ auto li = {v}; return {{ w, {BOUNDED} }}; }}\n"
                        ));
                        h_cases.push_str(&format!(
                            "case {i}: {{ auto li = {h}; return {{ {BOUNDED}, h }}; }}\n"
                        ));
                    }
                }
                (
                    format!("switch (index) {{\n{v_cases}default: break;\n}}\n"),
                    format!("switch (index) {{\n{h_cases}default: break;\n}}\n"),
                )
            } else {
                let mut v_steps = String::new();
                let mut h_steps = String::new();
                for item in measure_cells {
                    match item {
                        Either::Left((h_info, v_info)) => {
                            let v = compile_expression(v_info, ctx);
                            let h = compile_expression(h_info, ctx);
                            v_steps.push_str(&format!(
                                "if (index == cursor) {{ auto li = {v}; return {{ w, {BOUNDED} }}; }}\n\
                                 cursor += 1;\n"
                            ));
                            h_steps.push_str(&format!(
                                "if (index == cursor) {{ auto li = {h}; return {{ {BOUNDED}, h }}; }}\n\
                                 cursor += 1;\n"
                            ));
                        }
                        Either::Right(repeater) => {
                            let i = usize::from(repeater.repeater_index);
                            v_steps.push_str(&format!(
                                "{{ auto len = self->repeater_{i}.len(); \
                                 if (index >= cursor && index < cursor + len) {{ \
                                     if (auto *sub_comp = self->repeater_{i}.typed_instance_at(index - cursor)) {{ \
                                         auto li = sub_comp->flexbox_layout_item_info_at_cross_width(w).constraint; \
                                         return {{ w, {BOUNDED} }}; }} \
                                     return {{ w, h }}; }} \
                                 cursor += len; }}\n"
                            ));
                            h_steps.push_str(&format!(
                                "{{ auto len = self->repeater_{i}.len(); \
                                 if (index >= cursor && index < cursor + len) {{ \
                                     if (auto *sub_comp = self->repeater_{i}.typed_instance_at(index - cursor)) {{ \
                                         auto li = sub_comp->flexbox_layout_item_info_at_cross_height(h).constraint; \
                                         return {{ {BOUNDED}, h }}; }} \
                                     return {{ w, h }}; }} \
                                 cursor += len; }}\n"
                            ));
                        }
                    }
                }
                (
                    format!("[[maybe_unused]] uintptr_t cursor = 0;\n{v_steps}"),
                    format!("[[maybe_unused]] uintptr_t cursor = 0;\n{h_steps}"),
                )
            };
            // Preferred size per cell, returned when taffy asks for a dimension
            // without a known cross-axis size. With a repeater the cell count is
            // only known at runtime, so read the flat cell arrays the enclosing
            // `WithFlexboxLayoutItemInfo` built; otherwise inline the constants.
            let (defaults_decl, default_w, default_h) = match cells_variables {
                Some((cells_h, cells_v)) => {
                    let (cells_h, cells_v) = (ident(cells_h), ident(cells_v));
                    (
                        String::new(),
                        format!(
                            "index < {cells_h}.len ? [&]{{ auto li = {cells_h}.ptr[index].constraint; return {BOUNDED}; }}() : 0.0f"
                        ),
                        format!(
                            "index < {cells_v}.len ? [&]{{ auto li = {cells_v}.ptr[index].constraint; return {BOUNDED}; }}() : 0.0f"
                        ),
                    )
                }
                None => {
                    let mut pref_w = String::new();
                    let mut pref_h = String::new();
                    for item in default_cells {
                        if let Either::Left((h_info, v_info)) = item {
                            let h = compile_expression(h_info, ctx);
                            let v = compile_expression(v_info, ctx);
                            pref_w.push_str(&format!(
                                "[&]{{ auto li = {h}; return {BOUNDED}; }}(),\n"
                            ));
                            pref_h.push_str(&format!(
                                "[&]{{ auto li = {v}; return {BOUNDED}; }}(),\n"
                            ));
                        }
                    }
                    (
                        format!(
                            "const float pref_w[] = {{ {pref_w} }};\n\
                             const float pref_h[] = {{ {pref_h} }};\n\
                             const size_t cell_count = sizeof(pref_w) / sizeof(float);\n"
                        ),
                        "index < cell_count ? pref_w[index] : 0.0f".to_owned(),
                        "index < cell_count ? pref_h[index] : 0.0f".to_owned(),
                    )
                }
            };
            format!(
                "slint::private_api::solve_flexbox_layout_with_measure({data}, {repeater_indices}, \
                 [&](uintptr_t index, std::optional<float> known_w, std::optional<float> known_h) \
                 -> std::pair<float, float> {{\n\
                    {defaults_decl}\
                    float w = known_w.value_or({default_w});\n\
                    float h = known_h.value_or({default_h});\n\
                    if (known_w.has_value() && !known_h.has_value()) {{\n\
                        [[maybe_unused]] float measure_known_w = w;\n\
                        {v_body}\
                        return {{ w, h }};\n\
                    }}\n\
                    if (known_h.has_value() && !known_w.has_value()) {{\n\
                        [[maybe_unused]] float measure_known_h = h;\n\
                        {h_body}\
                        return {{ w, h }};\n\
                    }}\n\
                    return {{ w, h }};\n\
                 }})"
            )
        }
        Expression::WithGridInputData {
            cells_variable,
            repeater_indices_var_name,
            repeater_steps_var_name,
            elements,
            sub_expression,
        } => generate_with_grid_input_data(
            cells_variable,
            repeater_indices_var_name,
            repeater_steps_var_name,
            elements.as_ref(),
            sub_expression,
            ctx,
        ),
        Expression::MinMax { ty, op, lhs, rhs } => {
            let ident = match op {
                MinMaxOp::Min => "min",
                MinMaxOp::Max => "max",
            };
            let lhs_code = compile_expression(lhs, ctx);
            let rhs_code = compile_expression(rhs, ctx);
            format!(
                r#"std::{ident}<{ty}>({lhs_code}, {rhs_code})"#,
                ty = ty.cpp_type().unwrap_or_default(),
                ident = ident,
                lhs_code = lhs_code,
                rhs_code = rhs_code
            )
        }
        Expression::EmptyComponentFactory => panic!("component-factory not yet supported in C++"),
        Expression::EmptyDataTransfer => "slint::DataTransfer()".into(),
        Expression::TranslationReference { format_args, string_index, plural } => {
            let args = compile_expression(format_args, ctx);
            match plural {
                Some(plural) => {
                    let plural = compile_expression(plural, ctx);
                    format!(
                        "slint::private_api::translate_from_bundle_with_plural(slint_translation_bundle_plural_{string_index}_str, slint_translation_bundle_plural_{string_index}_idx,  slint_translated_plural_rules, {args}, {plural})"
                    )
                }
                None => format!(
                    "slint::private_api::translate_from_bundle(slint_translation_bundle_{string_index}, {args})"
                ),
            }
        }
        Expression::Closure { arg_name, expression } => {
            let arg = ident(arg_name);
            let expr = compile_expression(expression, ctx);

            format!("[&](auto const &{arg}) -> bool {{ return {expr}; }}")
        }
    }
}

fn struct_field_access(base: String, s: &crate::langtype::Struct, name: &str) -> String {
    if s.name.is_none() {
        let index = s
            .fields
            .keys()
            .position(|k| k == name)
            .expect("Expression::ObjectAccess: Cannot find a key in an object");
        format!("std::get<{}>({})", index, base)
    } else {
        format!("{}.{}", base, ident(name))
    }
}

fn compile_builtin_function_call(
    function: BuiltinFunction,
    arguments: &[llr::Expression],
    ctx: &EvaluationContext,
) -> String {
    let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
    let pi_180 = std::f64::consts::PI / 180.0;

    match function {
        BuiltinFunction::GetWindowScaleFactor => {
            format!("{}.scale_factor()", access_window_field(ctx))
        }
        BuiltinFunction::GetWindowDefaultFontSize => {
            "slint::private_api::get_resolved_default_font_size(*this)".to_string()
        }
        BuiltinFunction::AnimationTick => "slint::cbindgen_private::slint_animation_tick()".into(),
        BuiltinFunction::Debug => {
            ctx.generator_state.conditional_includes.iostream.set(true);
            format!("slint::private_api::debug({});", a.join(","))
        }
        BuiltinFunction::DecimalSeparator => "slint::private_api::decimal_separator()".into(),
        BuiltinFunction::Mod => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("([](float a, float b) {{ auto r = std::fmod(a, b); return r >= 0 ? r : r + std::abs(b); }})({},{})", a.next().unwrap(), a.next().unwrap())
        }
        BuiltinFunction::Round => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::round({})", a.next().unwrap())
        }
        BuiltinFunction::Ceil => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::ceil({})", a.next().unwrap())
        }
        BuiltinFunction::Floor => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::floor({})", a.next().unwrap())
        }
        BuiltinFunction::Sqrt => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::sqrt({})", a.next().unwrap())
        }
        BuiltinFunction::Abs => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::abs({})", a.next().unwrap())
        }
        BuiltinFunction::Log => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::log({}) / std::log({})", a.next().unwrap(), a.next().unwrap())
        }
        BuiltinFunction::Ln => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::log({})", a.next().unwrap())
        }
        BuiltinFunction::Pow => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::pow(({}), ({}))", a.next().unwrap(), a.next().unwrap())
        }
        BuiltinFunction::Exp => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::exp({})", a.next().unwrap())
        }
        BuiltinFunction::Sin => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::sin(({}) * {})", a.next().unwrap(), pi_180)
        }
        BuiltinFunction::Cos => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::cos(({}) * {})", a.next().unwrap(), pi_180)
        }
        BuiltinFunction::Tan => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::tan(({}) * {})", a.next().unwrap(), pi_180)
        }
        BuiltinFunction::ASin => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::asin({}) / {}", a.next().unwrap(), pi_180)
        }
        BuiltinFunction::ACos => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::acos({}) / {}", a.next().unwrap(), pi_180)
        }
        BuiltinFunction::ATan => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::atan({}) / {}", a.next().unwrap(), pi_180)
        }
        BuiltinFunction::ATan2 => {
            ctx.generator_state.conditional_includes.cmath.set(true);
            format!("std::atan2({}, {}) / {}", a.next().unwrap(), a.next().unwrap(), pi_180)
        }
        BuiltinFunction::ToFixed => {
            format!("[](double n, int d) {{ slint::SharedString out; slint::cbindgen_private::slint_shared_string_from_number_fixed(&out, n, std::max(d, 0)); return out; }}({}, {})",
                a.next().unwrap(), a.next().unwrap(),
            )
        }
        BuiltinFunction::ToPrecision => {
            format!("[](double n, int p) {{ slint::SharedString out; slint::cbindgen_private::slint_shared_string_from_number_precision(&out, n, std::max(p, 0)); return out; }}({}, {})",
                a.next().unwrap(), a.next().unwrap(),
            )
        }
        BuiltinFunction::ToStringUnlocalized => {
            format!("[](double n) {{ slint::SharedString out; slint::cbindgen_private::slint_shared_string_from_number_unlocalized(&out, n); return out; }}({})",
                a.next().unwrap(),
            )
        }
        BuiltinFunction::SetFocusItem => {
            if let [llr::Expression::PropertyReference(pr)] = arguments {
                let window = access_window_field(ctx);
                let focus_item = access_item_rc(pr, ctx);
                format!("{window}.set_focus_item({focus_item}, true, slint::cbindgen_private::FocusReason::Programmatic);")
            } else {
                panic!("internal error: invalid args to SetFocusItem {arguments:?}")
            }
        }
        BuiltinFunction::ClearFocusItem => {
            if let [llr::Expression::PropertyReference(pr)] = arguments {
                let window = access_window_field(ctx);
                let focus_item = access_item_rc(pr, ctx);
                format!("{window}.set_focus_item({focus_item}, false, slint::cbindgen_private::FocusReason::Programmatic);")
            } else {
                panic!("internal error: invalid args to ClearFocusItem {arguments:?}")
            }
        }
        /* std::from_chars is unfortunately not yet implemented in all stdlib compiler we support.
         * And std::strtod depends on the locale. Use slint_string_to_float implemented in Rust
        BuiltinFunction::StringIsFloat => {
            "[](const auto &a){ double v; auto r = std::from_chars(std::begin(a), std::end(a), v); return r.ptr == std::end(a); }"
                .into()
        }
        BuiltinFunction::StringToFloat => {
            "[](const auto &a){ double v; auto r = std::from_chars(std::begin(a), std::end(a), v); return r.ptr == std::end(a) ? v : 0; }"
                .into()
        }*/
        BuiltinFunction::StringIsFloat => {
            ctx.generator_state.conditional_includes.cstdlib.set(true);
            format!("[](const auto &a){{ float res = 0; return slint::cbindgen_private::slint_string_to_float(&a, &res); }}({})", a.next().unwrap())
        }
        BuiltinFunction::StringToFloat => {
            ctx.generator_state.conditional_includes.cstdlib.set(true);
            format!("[](const auto &a){{ float res = 0; slint::cbindgen_private::slint_string_to_float(&a, &res); return res; }}({})", a.next().unwrap())
        }
        BuiltinFunction::StringIsEmpty => {
            format!("{}.empty()", a.next().unwrap())
        }
        BuiltinFunction::StringCharacterCount => {
            format!("[](const auto &a){{ return slint::cbindgen_private::slint_string_character_count(&a); }}({})", a.next().unwrap())
        }
        BuiltinFunction::StringToLowercase => {
            format!("{}.to_lowercase()", a.next().unwrap())
        }
        BuiltinFunction::StringToUppercase => {
            format!("{}.to_uppercase()", a.next().unwrap())
        }
        BuiltinFunction::StringStartsWith => {
            format!("{}.starts_with({})", a.next().unwrap(), a.next().unwrap())
        }
        BuiltinFunction::StringEndsWith => {
            format!("{}.ends_with({})", a.next().unwrap(), a.next().unwrap())
        }
        BuiltinFunction::KeysToString => {
            format!("{}.to_string()", a.next().unwrap())
        }
        BuiltinFunction::ColorRgbaStruct => {
            format!("{}.to_argb_uint()", a.next().unwrap())
        }
        BuiltinFunction::ColorHsvaStruct => {
            format!("{}.to_hsva()", a.next().unwrap())
        }
        BuiltinFunction::ColorOklchStruct => {
            format!("{}.to_oklch()", a.next().unwrap())
        }
        BuiltinFunction::ColorBrighter => {
            format!("{}.brighter({})", a.next().unwrap(), a.next().unwrap())
        }
        BuiltinFunction::ColorDarker => {
            format!("{}.darker({})", a.next().unwrap(), a.next().unwrap())
        }
        BuiltinFunction::ColorTransparentize => {
            format!("{}.transparentize({})", a.next().unwrap(), a.next().unwrap())
        }
        BuiltinFunction::ColorMix => {
            format!("{}.mix({}, {})", a.next().unwrap(), a.next().unwrap(), a.next().unwrap())
        }
        BuiltinFunction::ColorWithAlpha => {
            format!("{}.with_alpha({})", a.next().unwrap(), a.next().unwrap())
        }
        BuiltinFunction::ImageSize => {
            format!("{}.size()", a.next().unwrap())
        }
        BuiltinFunction::ArrayLength => {
            format!("slint::private_api::model_length({})", a.next().unwrap())
        }
        BuiltinFunction::ArrayPush => {
            let model = a.next().unwrap();
            let value = a.next().unwrap();
            format!("slint::private_api::model_push({model}, {value})")
        }
        BuiltinFunction::ArrayRemove => {
            let model = a.next().unwrap();
            let index = a.next().unwrap();
            format!("slint::private_api::model_remove({model}, {index})")
        }
        BuiltinFunction::ArrayInsert => {
            let model = a.next().unwrap();
            let index = a.next().unwrap();
            let value = a.next().unwrap();
            format!("slint::private_api::model_insert({model}, {index}, {value})")
        }
        BuiltinFunction::Rgb => {
            format!("slint::Color::from_argb_uint8(std::clamp(static_cast<float>({a}) * 255., 0., 255.), std::clamp(static_cast<int>({r}), 0, 255), std::clamp(static_cast<int>({g}), 0, 255), std::clamp(static_cast<int>({b}), 0, 255))",
                r = a.next().unwrap(),
                g = a.next().unwrap(),
                b = a.next().unwrap(),
                a = a.next().unwrap(),
            )
        }
        BuiltinFunction::Hsv => {
            format!("slint::Color::from_hsva(static_cast<float>({h}), std::clamp(static_cast<float>({s}), 0.f, 1.f), std::clamp(static_cast<float>({v}), 0.f, 1.f), std::clamp(static_cast<float>({a}), 0.f, 1.f))",
                h = a.next().unwrap(),
                s = a.next().unwrap(),
                v = a.next().unwrap(),
                a = a.next().unwrap(),
            )
        }
        BuiltinFunction::Oklch => {
            format!("slint::Color::from_oklch(std::clamp(static_cast<float>({l}), 0.f, 1.f), std::max(static_cast<float>({c}), 0.f), static_cast<float>({h}), std::clamp(static_cast<float>({alpha}), 0.f, 1.f))",
                l = a.next().unwrap(),
                c = a.next().unwrap(),
                h = a.next().unwrap(),
                alpha = a.next().unwrap(),
            )
        }
        BuiltinFunction::ColorScheme => {
            // Route through the runtime helper so a `Palette.color-scheme` binding
            // inside a SystemTrayIcon-rooted component naturally resolves against
            // the tray's scheme without going through any window adapter.
            format!(
                "[&]{{ auto _root = (*{0}->root_weak.lock()).into_dyn(); return slint::cbindgen_private::slint_context_color_scheme(&_root); }}()",
                ctx.generator_state.global_access
            )
        }
        BuiltinFunction::AccentColor => {
            format!(
                "[&]{{ auto _root = (*{0}->root_weak.lock()).into_dyn(); slint::Color col; slint::cbindgen_private::slint_context_accent_color(&_root, &col); return col; }}()",
                ctx.generator_state.global_access
            )
        }
        BuiltinFunction::SupportsNativeMenuBar => {
            format!("{}.supports_native_menu_bar()", access_window_field(ctx))
        }
        BuiltinFunction::SetupMenuBar => {
            let window = access_window_field(ctx);
            let [llr::Expression::PropertyReference(entries_r), llr::Expression::PropertyReference(sub_menu_r), llr::Expression::PropertyReference(activated_r), llr::Expression::NumberLiteral(tree_index), llr::Expression::BoolLiteral(no_native), condition, visible, ..] = arguments
            else {
                panic!("internal error: incorrect argument count to SetupMenuBar")
            };

            let current_sub_component = ctx.current_sub_component().unwrap();
            let item_tree_id = ident(&ctx.compilation_unit.sub_components[current_sub_component.menu_item_trees[*tree_index as usize].root].name);
            let access_entries = access_member(entries_r, ctx).unwrap();
            let access_sub_menu = access_member(sub_menu_r, ctx).unwrap();
            let access_activated = access_member(activated_r, ctx).unwrap();
            if *no_native {
                format!(r"{{
                    auto item_tree = {item_tree_id}::create(self);
                    auto item_tree_dyn = item_tree.into_dyn();
                    auto menu_wrapper = slint::private_api::create_menu_wrapper(item_tree_dyn);
                    slint::private_api::slint_windowrc_setup_menu_bar_shortcuts(&{window}.handle(), &menu_wrapper);
                    slint::private_api::setup_popup_menu_from_menu_item_tree(menu_wrapper, {access_entries}, {access_sub_menu}, {access_activated});
                }}")
            } else {
                let compile_prop = |prop_expr: &llr::Expression| {
                    let binding = compile_expression(prop_expr, ctx);
                    format!(r"[](auto menu_tree) {{
                                auto self_mapped = reinterpret_cast<const {item_tree_id} *>(menu_tree->operator->())->parent.lock();
                                [[maybe_unused]] auto self = &**self_mapped;
                                return {binding};
                            }}")
                };

                let condition = compile_prop(condition);
                let visible = compile_prop(visible);

                format!(r"{{
                    auto item_tree = {item_tree_id}::create(self);
                    auto item_tree_dyn = item_tree.into_dyn();
                    auto menu_wrapper = slint::private_api::create_menu_wrapper(item_tree_dyn, {condition}, {visible});
                    slint::private_api::slint_windowrc_setup_menu_bar_shortcuts(&{window}.handle(), &menu_wrapper);
                    if ({window}.supports_native_menu_bar()) {{
                        slint::cbindgen_private::slint_windowrc_setup_native_menu_bar(&{window}.handle(), &menu_wrapper);
                    }} else {{
                        slint::private_api::setup_popup_menu_from_menu_item_tree(menu_wrapper, {access_entries}, {access_sub_menu}, {access_activated});
                    }}
                }}")
            }
        }
        BuiltinFunction::SetupSystemTrayIcon => {
            let [
                llr::Expression::PropertyReference(system_tray_ref),
                llr::Expression::NumberLiteral(tree_index),
                rest @ ..,
            ] = arguments
            else {
                panic!("internal error: incorrect arguments to SetupSystemTrayIcon")
            };

            let current_sub_component = ctx.current_sub_component().unwrap();
            let item_tree_id = ident(
                &ctx.compilation_unit.sub_components
                    [current_sub_component.menu_item_trees[*tree_index as usize].root]
                    .name,
            );
            let system_tray = access_member(system_tray_ref, ctx).unwrap();
            let system_tray_rc = access_item_rc(system_tray_ref, ctx);

            // `if cond : Menu { ... }` is lowered to a condition lambda passed
            // alongside the menu wrapper. `create_menu_wrapper` already accepts
            // the optional condition pointer.
            let condition = if let [condition] = rest {
                let condition = compile_expression(condition, ctx);
                format!(
                    r"[](auto menu_tree) {{
                        auto self_mapped = reinterpret_cast<const {item_tree_id} *>(menu_tree->operator->())->parent.lock();
                        [[maybe_unused]] auto self = &**self_mapped;
                        return {condition};
                    }}"
                )
            } else {
                "nullptr".to_string()
            };

            format!(
                r"{{
                    auto item_tree = {item_tree_id}::create(self);
                    auto menu_wrapper = slint::private_api::create_menu_wrapper(item_tree.into_dyn(), {condition});
                    slint::cbindgen_private::ItemRc item_rc{{ {system_tray_rc} }};
                    slint::cbindgen_private::slint_system_tray_icon_set_menu(&{system_tray}, &item_rc, &menu_wrapper);
                }}"
            )
        }
        BuiltinFunction::Use24HourFormat => {
            "slint::cbindgen_private::slint_date_time_use_24_hour_format()".to_string()
        }
        BuiltinFunction::MonthDayCount => {
            format!("slint::cbindgen_private::slint_date_time_month_day_count({}, {})", a.next().unwrap(), a.next().unwrap())
        }
        BuiltinFunction::MonthOffset => {
            format!("slint::cbindgen_private::slint_date_time_month_offset({}, {})", a.next().unwrap(), a.next().unwrap())
        }
        BuiltinFunction::FormatDate => {
            format!("[](const auto &format, int d, int m, int y) {{ slint::SharedString out; slint::cbindgen_private::slint_date_time_format_date(&format, d, m, y, &out); return out; }}({}, {}, {}, {})",
                a.next().unwrap(), a.next().unwrap(), a.next().unwrap(), a.next().unwrap()
            )
        }
        BuiltinFunction::DateNow => {
            "[] { int32_t d=0, m=0, y=0; slint::cbindgen_private::slint_date_time_date_now(&d, &m, &y); return std::make_shared<slint::VectorModel<int32_t>>(std::vector<int32_t>{ d, m, y }); }()".into()
        }
        BuiltinFunction::ValidDate => {
            format!(
                "[](const auto &a, const auto &b) {{ int32_t d=0, m=0, y=0; return slint::cbindgen_private::slint_date_time_parse_date(&a, &b, &d, &m, &y); }}({}, {})",
                a.next().unwrap(), a.next().unwrap()
            )
        }
        BuiltinFunction::ParseDate => {
            format!(
                "[](const auto &a, const auto &b) {{ int32_t d=0, m=0, y=0; slint::cbindgen_private::slint_date_time_parse_date(&a, &b, &d, &m, &y); return std::make_shared<slint::VectorModel<int32_t>>(std::vector<int32_t>{{ d, m, y }}); }}({}, {})",
                a.next().unwrap(), a.next().unwrap()
            )
        }
        BuiltinFunction::SetTextInputFocused => {
            format!("{}.set_text_input_focused({})", access_window_field(ctx), a.next().unwrap())
        }
        BuiltinFunction::TextInputFocused => {
            format!("{}.text_input_focused()", access_window_field(ctx))
        }
        BuiltinFunction::ShowPopupWindow => {
            // `owner_ref` is the popup's declaring component (its `popup_id` and scope); `anchor_ref`
            // is the parent item for positioning. A trailing argument may carry the synthesized
            // `is-open` property reference, resolved in this call's own frame (see
            // lower_show_popup_window).
            if let [llr::Expression::NumberLiteral(popup_index), close_policy, llr::Expression::PropertyReference(owner_ref), llr::Expression::PropertyReference(anchor_ref), is_open_args @ ..] =
                arguments
            {
                let mut component_access = MemberAccess::Direct("self".into());
                let llr::MemberReference::Relative { parent_level, local_reference } = owner_ref else {unreachable!()};
                for _ in 0..*parent_level {
                    component_access = component_access.and_then(|x| format!("{x}->parent.lock()"));
                }

                let window = access_window_field(ctx);
                let (compo_path, _) = follow_sub_component_path(
                    ctx.compilation_unit,
                    ctx.parent_sub_component_idx(*parent_level).unwrap(),
                    &local_reference.sub_component_path,
                );
                let parent_component = access_item_rc(anchor_ref, ctx);
                ctx.with_reference_scope(*parent_level, &local_reference.sub_component_path, |parent_ctx| {
                let popup = &ctx.compilation_unit.sub_components[parent_ctx.sub_component]
                    .popup_windows[*popup_index as usize];
                let popup_window_id =
                    ident(&ctx.compilation_unit.sub_components[popup.item_tree.root].name);
                let popup_ctx = EvaluationContext::new_sub_component(
                    ctx.compilation_unit,
                    popup.item_tree.root,
                    CppGeneratorContext { global_access: "self->globals".into(), conditional_includes: ctx.generator_state.conditional_includes },
                    Some(&parent_ctx),
                );
                let position = compile_expression(&popup.position.borrow(), &popup_ctx);
                let close_policy = compile_expression(close_policy, ctx);
                let window_kind = if popup.is_tooltip { "slint::cbindgen_private::WindowKind::ToolTip" } else { "slint::cbindgen_private::WindowKind::Popup" };
                // Keep the parent's `is-open` property in sync. The setter is passed directly into
                // `show_popup`, so there is no extra registration call and no second popup lookup. The
                // `false` is delivered when the popup is dropped, which may be long after this frame, so
                // we cannot capture a raw `self`. We capture a weak *mapped* handle to the current
                // component instance -- the C++ equivalent of Rust's `self_weak`, which for a
                // sub-component points at that sub-component itself rather than at the enclosing item
                // tree (`self->self_weak`). Popups without `is-open` get a no-op setter.
                let is_open_setter = match is_open_args.first() {
                    Some(llr::Expression::PropertyReference(is_open_ref)) => {
                        let self_ty = ident(&ctx.current_sub_component().expect("ShowPopupWindow is invoked on a sub-component").name);
                        let set_is_open = access_member(is_open_ref, ctx).then(|p| format!("{p}.set(is_open)"));
                        format!(
                            "[weak = vtable::VWeakMapped<slint::private_api::ItemTreeVTable, const {self_ty}>( \
                                    vtable::VRcMapped<slint::private_api::ItemTreeVTable, const {self_ty}>(self->self_weak.lock().value(), self))] \
                             (bool is_open) {{ \
                                auto rc = weak.lock(); \
                                if (!rc) return; \
                                [[maybe_unused]] auto self = &**rc; \
                                {set_is_open}; \
                            }}"
                        )
                    }
                    _ => "[](bool) {}".to_string(),
                };
                component_access.then(|component_access| {
                    let compo_ptr = if compo_path.is_empty() {
                        format!("&*({component_access})")
                    } else {
                        format!("&({component_access}->{})", compo_path.trim_end_matches('.'))
                    };
                    format!(
                        // Use a block statement to create own globals and popup instance
                        "{window}.close_popup({component_access}->{compo_path}popup_id_{popup_index}); \
                        {component_access}->{compo_path}popup_id_{popup_index} =  \
                            {window}.template show_popup<{popup_window_id}>({compo_ptr},  \
                                                                            [=](auto self) {{ return {position}; }},  \
                                                                            {close_policy},  \
                                                                            {{ {parent_component} }},  \
                                                                            {window_kind},  \
                                                                            {is_open_setter})"
                    )
                })
                })
            } else {
                panic!("internal error: invalid args to ShowPopupWindow {arguments:?}")
            }
        }
        BuiltinFunction::ClosePopupWindow => {
            if let [llr::Expression::NumberLiteral(popup_index), llr::Expression::PropertyReference(parent_ref)] = arguments {
                let mut component_access = MemberAccess::Direct("self".into());
                let llr::MemberReference::Relative { parent_level, local_reference } = parent_ref else {unreachable!()};
                for _ in 0..*parent_level {
                    component_access = component_access.and_then(|x| format!("{x}->parent.lock()"));
                }
                let (compo_path, _) = follow_sub_component_path(
                    ctx.compilation_unit,
                    ctx.parent_sub_component_idx(*parent_level).unwrap(),
                    &local_reference.sub_component_path,
                );

                component_access.then(|component_access| format!("{component_access}->{compo_path}globals->window().window_handle().close_popup({component_access}->{compo_path}popup_id_{popup_index})"))
            } else {
                panic!("internal error: invalid args to ClosePopupWindow {arguments:?}")
            }
        }

        BuiltinFunction::ShowPopupMenu | BuiltinFunction::ShowPopupMenuInternal => {
            let [llr::Expression::PropertyReference(context_menu_ref), entries, position] = arguments
            else {
                panic!("internal error: invalid args to ShowPopupMenu {arguments:?}")
            };

            let context_menu = access_member(context_menu_ref, ctx);
            let context_menu_rc = access_item_rc(context_menu_ref, ctx);
            let position = compile_expression(position, ctx);
            let popup = ctx
                .compilation_unit
                .popup_menu
                .as_ref()
                .expect("there should be a popup menu if we want to show it");
            let popup_id = ident(&ctx.compilation_unit.sub_components[popup.item_tree.root].name);
            let window = access_window_field(ctx);

            let popup_ctx = EvaluationContext::new_sub_component(
                ctx.compilation_unit,
                popup.item_tree.root,
                CppGeneratorContext { global_access: "self->globals".into(), conditional_includes: ctx.generator_state.conditional_includes },
                None,
            );
            let access_entries = access_member(&popup.entries, &popup_ctx).unwrap();
            let access_sub_menu = access_member(&popup.sub_menu, &popup_ctx).unwrap();
            let access_activated = access_member(&popup.activated, &popup_ctx).unwrap();
            let access_close = access_member(&popup.close, &popup_ctx).unwrap();

            let close_popup = context_menu.then(|context_menu| {
                format!("{window}.close_popup({context_menu}.popup_id)")
            });
            let set_id = context_menu
                .then(|context_menu| format!("{context_menu}.popup_id = id"));

            if let llr::Expression::NumberLiteral(tree_index) = entries {
                // We have an MenuItem tree
                let current_sub_component = ctx.current_sub_component().unwrap();
                let item_tree_id = ident(&ctx.compilation_unit.sub_components[current_sub_component.menu_item_trees[*tree_index as usize].root].name);
                format!(r"{{
                    auto item_tree = {item_tree_id}::create(self);
                    auto item_tree_dyn = item_tree.into_dyn();
                    auto menu_wrapper = slint::private_api::create_menu_wrapper(item_tree_dyn);
                    {close_popup};
                    auto id = {window}.template show_popup_menu<{popup_id}>({globals}, {position}, {{ {context_menu_rc} }}, [self, &menu_wrapper](auto popup_menu) {{
                        auto parent_weak = self->self_weak;
                        auto self_ = self;
                        auto self = popup_menu;
                        slint::private_api::setup_popup_menu_from_menu_item_tree(menu_wrapper, {access_entries}, {access_sub_menu}, {access_activated});
                        {access_close}.set_handler([parent_weak,self = self_] {{ if(auto lock = parent_weak.lock()) {{ {close_popup}; }} }});
                    }}, menu_wrapper);
                    {set_id};
                }}", globals = ctx.generator_state.global_access)
            } else {
                // ShowPopupMenuInternal
                let forward_callback = |access, cb, default| {
                    let call = context_menu.map_or_default(|context_menu| format!("{context_menu}.{cb}.call(entry)"));
                    format!("{access}.set_handler(
                        [parent_weak,self = self_](const auto &entry) {{
                            if(auto lock = parent_weak.lock()) {{
                                return {call};
                            }} else {{
                                return {default};
                            }}
                        }});")
                };
                let fw_sub_menu = forward_callback(access_sub_menu, "sub_menu", "std::shared_ptr<slint::Model<slint::cbindgen_private::MenuEntry>>()");
                let fw_activated = forward_callback(access_activated, "activated", "");
                let entries = compile_expression(entries, ctx);
                format!(r"
                    {close_popup};
                    auto id = {window}.template show_popup_menu<{popup_id}>({globals}, {position}, {{ {context_menu_rc} }}, [self](auto popup_menu) {{
                        auto parent_weak = self->self_weak;
                        auto self_ = self;
                        auto entries = {entries};
                        auto self = popup_menu;
                        {access_entries}.set(std::move(entries));
                        {fw_sub_menu}
                        {fw_activated}
                        {access_close}.set_handler([parent_weak,self = self_] {{ if(auto lock = parent_weak.lock()) {{ {close_popup}; }} }});
                    }});
                    {set_id};
                ", globals = ctx.generator_state.global_access)
            }
        }
        BuiltinFunction::SetSelectionOffsets => {
            if let [llr::Expression::PropertyReference(pr), from, to] = arguments {
                let item = access_member(pr, ctx);
                let item_rc = access_item_rc(pr, ctx);
                let window = access_window_field(ctx);
                let start = compile_expression(from, ctx);
                let end = compile_expression(to, ctx);
                item.then(|item| {
                    format!("slint_textinput_set_selection_offsets(&{item}, &{window}.handle(), &{item_rc}, static_cast<int>({start}), static_cast<int>({end}))")
                })
            } else {
                panic!("internal error: invalid args to set-selection-offsets {arguments:?}")
            }
        }
        BuiltinFunction::ItemFontMetrics => {
            if let [llr::Expression::PropertyReference(pr)] = arguments {
                let item_rc = access_item_rc(pr, ctx);
                let window = access_window_field(ctx);
                format!(
                    "[&]{{ slint::cbindgen_private::FontMetrics fm; slint_cpp_text_item_fontmetrics(&{window}.handle(), &{item_rc}, &fm); return fm; }}()"
                )
            } else {
                panic!("internal error: invalid args to ItemFontMetrics {arguments:?}")
            }
        }
        BuiltinFunction::ItemAbsolutePosition => {
            if let [llr::Expression::PropertyReference(pr)] = arguments {
                let item_rc = access_item_rc(pr, ctx);
                format!("slint::LogicalPosition(slint::cbindgen_private::slint_item_absolute_position(&{item_rc}))")
            } else {
                panic!("internal error: invalid args to ItemAbsolutePosition {arguments:?}")
            }
        }
        BuiltinFunction::RegisterCustomFontByPath => {
            if let [llr::Expression::StringLiteral(path)] = arguments {
                let window = access_window_field(ctx);
                format!("{window}.register_font_from_path(\"{}\");", escape_string(path))
            } else {
                panic!(
                    "internal error: argument to RegisterCustomFontByPath must be a string literal"
                )
            }
        }
        BuiltinFunction::RegisterCustomFontByMemory => {
            if let [llr::Expression::NumberLiteral(resource_id)] = &arguments {
                let window = access_window_field(ctx);
                let resource_id: usize = *resource_id as _;
                let symbol = format!("slint_embedded_resource_{resource_id}");
                format!("{window}.register_font_from_data({symbol}, std::size({symbol}));")
            } else {
                panic!("internal error: invalid args to RegisterCustomFontByMemory {arguments:?}")
            }
        }
        BuiltinFunction::RegisterBitmapFont => {
            if let [llr::Expression::NumberLiteral(resource_id)] = &arguments {
                let window = access_window_field(ctx);
                let resource_id: usize = *resource_id as _;
                let symbol = format!("slint_embedded_resource_{resource_id}");
                format!("{window}.register_bitmap_font({symbol});")
            } else {
                panic!("internal error: invalid args to RegisterBitmapFont {arguments:?}")
            }
        }
        BuiltinFunction::ImplicitLayoutInfo(orient) => {
            if let [llr::Expression::PropertyReference(pr), constraint_expr] = arguments {
                let native = native_prop_info(pr, ctx).0;
                let item_rc = access_item_rc(pr, ctx);
                let constraint = compile_expression(constraint_expr, ctx);
                access_member(pr, ctx).then(|item|
                format!(
                    "slint::private_api::item_layout_info({vt}, const_cast<slint::cbindgen_private::{ty}*>(&{item}), {o}, {constraint}, &{window}, {item_rc})",
                    vt = native.cpp_vtable_getter,
                    ty = native.class_name,
                    o = to_cpp_orientation(orient),
                    window = access_window_field(ctx),
                ))
            } else {
                panic!("internal error: invalid args to ImplicitLayoutInfo {arguments:?}")
            }
        }
        BuiltinFunction::Translate => {
            format!("slint::private_api::translate({})", a.join(","))
        }
        BuiltinFunction::UpdateTimers => {
            "self->update_timers()".into()
        }
        BuiltinFunction::DetectOperatingSystem => {
            "slint::cbindgen_private::slint_detect_operating_system()".to_string()
        }
        // start and stop are unreachable because they are lowered to simple assignment of running
        BuiltinFunction::StartTimer => unreachable!(),
        BuiltinFunction::StopTimer => unreachable!(),
        BuiltinFunction::RestartTimer => {
            if let [llr::Expression::PropertyReference(pr)] = arguments {
                access_member(pr, ctx)
                    .then(|x| format!("const_cast<slint::Timer&>({x}).restart()"))
            } else {
                panic!("internal error: invalid args to RestartTimer {arguments:?}")
            }
        }
        BuiltinFunction::OpenUrl => {
            let url = a.next().unwrap();
            let window = access_window_field(ctx);
            format!("slint::private_api::open_url({url}, {window})")
        }
        BuiltinFunction::MacosBringAllWindowsToFront => {
            "slint::private_api::macos_bring_all_windows_to_front()".to_owned()
        }
        BuiltinFunction::ParseMarkdown => {
            let format_string = a.next().unwrap();
            let args = a.next().unwrap();
            format!("slint::private_api::parse_markdown({}, {})", format_string, args)
        }
        BuiltinFunction::StringToStyledText => {
            let string = a.next().unwrap();
            format!("slint::private_api::string_to_styled_text({})", string)
        }
        BuiltinFunction::ColorToStyledText => {
            let color = a.next().unwrap();
            format!("slint::private_api::color_to_styled_text({})", color)
        }
        BuiltinFunction::PathPointAt => {
            if let [llr::Expression::PropertyReference(pr), t] = arguments {
                let item_rc = access_item_rc(pr, ctx);
                let t = compile_expression(t, ctx);
                format!(
                    "slint::LogicalPosition(slint::cbindgen_private::slint_path_point_at(&{item_rc}, static_cast<float>({t})))"
                )
            } else {
                panic!("internal error: invalid args to PathPointAt {arguments:?}")
            }
        }
        BuiltinFunction::PathAngleAt => {
            if let [llr::Expression::PropertyReference(pr), t] = arguments {
                let item_rc = access_item_rc(pr, ctx);
                let t = compile_expression(t, ctx);
                format!(
                    "slint::cbindgen_private::slint_path_angle_at(&{item_rc}, static_cast<float>({t}))"
                )
            } else {
                panic!("internal error: invalid args to PathAngleAt {arguments:?}")
            }
        }
        BuiltinFunction::ArrayAny => {
            format!("slint::private_api::model_any({}, {})", a.next().unwrap(), a.next().unwrap())
        },
        BuiltinFunction::ArrayAll => {
            format!("slint::private_api::model_all({}, {})", a.next().unwrap(), a.next().unwrap())
        },
    }
}

/// Builds the C++ snippet that, for each inner repeater in `templates`, calls
/// `ensure_updated` on the sub-component and updates `max_total`.
fn build_inner_ensure_code(templates: &[llr::RowChildTemplateInfo], static_count: usize) -> String {
    templates
        .iter()
        .filter_map(|e| match e {
            llr::RowChildTemplateInfo::Repeated { repeater_index } => {
                let inner_rep_id = format!("repeater_{}", usize::from(*repeater_index));
                Some(format!(
                    "sub_comp->{inner_rep_id}.track_instance_changes();\n\
                     max_total = std::max(max_total, {static_count} + sub_comp->{inner_rep_id}.len());\n"
                ))
            }
            _ => None,
        })
        .collect()
}

fn generate_repeater_loop_code(
    repeater_index: llr::RepeatedElementIdx,
    row_child_templates: &Option<Vec<llr::RowChildTemplateInfo>>,
    repeater_steps_var_name: &Option<SmolStr>,
    repeater_idx: usize,
    dynamic_stride_var_name: &str,
    dynamic_loop_code: impl FnOnce(String, usize, String, String) -> String,
    static_loop_code: impl FnOnce(String, usize, bool, String) -> String,
) -> String {
    let repeater_id = format!("repeater_{}", usize::from(repeater_index));
    if llr::has_inner_repeaters(row_child_templates) {
        let templates = row_child_templates.as_ref().unwrap();
        let static_count = llr::static_child_count(templates);
        let inner_ensure = build_inner_ensure_code(templates, static_count);
        let rs_init = repeater_steps_var_name.as_ref().map_or(String::new(), |rs| {
            format!("{rs}_array[{repeater_idx}] = {dynamic_stride_var_name};")
        });
        dynamic_loop_code(repeater_id, static_count, inner_ensure, rs_init)
    } else {
        let step = row_child_templates.as_deref().map_or(1, |t| t.len());
        let rs_init = repeater_steps_var_name
            .as_ref()
            .map_or(String::new(), |rs| format!("{rs}_array[{repeater_idx}] = {step};"));
        static_loop_code(repeater_id, step, row_child_templates.is_none(), rs_init)
    }
}

fn generate_with_layout_item_info(
    cells_variable: &str,
    repeated_indices_var_name: Option<&str>,
    repeater_steps_var_name: Option<&str>,
    elements: &[Either<llr::Expression, llr::LayoutRepeatedElement>],
    orientation: Orientation,
    sub_expression: &llr::Expression,
    ctx: &llr_EvaluationContext<CppGeneratorContext>,
) -> String {
    let repeated_indices_var_name = repeated_indices_var_name.map(ident);
    let repeater_steps_var_name = repeater_steps_var_name.map(ident);
    let mut push_code =
        "std::vector<slint::cbindgen_private::LayoutItemInfo> cells_vector;".to_owned();
    let mut repeater_idx = 0usize;

    for item in elements {
        match item {
            Either::Left(value) => {
                write!(
                    push_code,
                    "cells_vector.push_back({{ {} }});",
                    compile_expression(value, ctx)
                )
                .unwrap();
            }
            Either::Right(repeater) => {
                let repeater_index = usize::from(repeater.repeater_index);
                write!(push_code, "self->repeater_{repeater_index}.track_instance_changes();")
                    .unwrap();

                if let Some(ri) = &repeated_indices_var_name {
                    write!(
                        push_code,
                        "{ri}_array[{c}] = cells_vector.size();",
                        c = repeater_idx * 2
                    )
                    .unwrap();
                    write!(
                        push_code,
                        "{ri}_array[{c}] = self->repeater_{repeater_index}.len();",
                        c = repeater_idx * 2 + 1,
                    )
                    .unwrap();
                }
                let repeater_loop_code = generate_repeater_loop_code(
                    repeater.repeater_index,
                    &repeater.row_child_templates,
                    &repeater_steps_var_name,
                    repeater_idx,
                    "max_total",
                    |repeater_id, static_count, inner_ensure, rs_init| {
                        // for_each only visits instantiated slots; pad the cells up to len()
                        // afterwards so the cell count matches the repeater length recorded in
                        // the repeater_indices array (not-yet-instantiated rows get placeholders).
                        format!(
                            "{{
                                size_t max_total = {static_count};
                                self->{repeater_id}.for_each([&](const auto &sub_comp) {{
                                    {inner_ensure}
                                }});
                                {rs_init}
                                auto start_offset = cells_vector.size();
                                self->{repeater_id}.for_each([&](const auto &sub_comp) {{
                                    for (size_t child_idx = 0; child_idx < max_total; ++child_idx) {{
                                        cells_vector.push_back(sub_comp->layout_item_info({o}, child_idx));
                                    }}
                                }});
                                cells_vector.resize(start_offset + self->{repeater_id}.len() * max_total);
                            }}",
                            o = to_cpp_orientation(orientation),
                        )
                    },
                    |repeater_id, step, is_column_repeater, rs_init| {
                        if step == 0 {
                            rs_init
                        } else if step == 1 && is_column_repeater {
                            // Column-repeater: each sub-component IS a cell; nullopt returns its own layout_info
                            format!(
                                "{rs_init}{{
                                    auto start_offset = cells_vector.size();
                                    self->{repeater_id}.for_each([&](const auto &sub_comp){{ cells_vector.push_back(sub_comp->layout_item_info({o}, std::nullopt)); }});
                                    cells_vector.resize(start_offset + self->{repeater_id}.len());
                                }}",
                                o = to_cpp_orientation(orientation),
                            )
                        } else {
                            format!(
                                "{rs_init}{{
                                    auto start_offset = cells_vector.size();
                                    self->{repeater_id}.for_each([&](const auto &sub_comp){{
                                        for (size_t child_idx = 0; child_idx < {step}; ++child_idx) {{
                                            cells_vector.push_back(sub_comp->layout_item_info({o}, child_idx));
                                        }}
                                    }});
                                    cells_vector.resize(start_offset + self->{repeater_id}.len() * {step});
                                }}",
                                o = to_cpp_orientation(orientation),
                            )
                        }
                    },
                );
                push_code.push_str(&repeater_loop_code);
                repeater_idx += 1;
            }
        }
    }

    let ri = repeated_indices_var_name.as_ref().map_or(String::new(), |ri| {
        write!(
            push_code,
            "slint::cbindgen_private::Slice<int> {ri} = slint::private_api::make_slice(std::span({ri}_array));"
        )
        .unwrap();
        format!("std::array<int, {}> {ri}_array;", 2 * repeater_idx)
    });
    let rs = repeater_steps_var_name.as_ref().map_or(String::new(), |rs| {
        write!(
            push_code,
            "slint::cbindgen_private::Slice<int> {rs} = slint::private_api::make_slice(std::span({rs}_array));"
        )
        .unwrap();
        format!("std::array<int, {}> {rs}_array;", repeater_idx)
    });
    format!(
        "[&]{{ {ri} {rs} {push_code} slint::cbindgen_private::Slice<slint::cbindgen_private::LayoutItemInfo>{} = slint::private_api::make_slice(std::span(cells_vector)); return {}; }}()",
        ident(cells_variable),
        compile_expression(sub_expression, ctx)
    )
}

fn generate_with_flexbox_layout_item_info(
    cells_h_variable: &str,
    cells_v_variable: &str,
    flex_props_variable: &str,
    repeated_indices_var_name: Option<&str>,
    elements: &[Either<
        (llr::Expression, llr::Expression, llr::Expression),
        llr::LayoutRepeatedElement,
    >],
    repeated_cross_width: Option<&llr::Expression>,
    sub_expression: &llr::Expression,
    ctx: &llr_EvaluationContext<CppGeneratorContext>,
) -> String {
    let repeated_indices_var_name = repeated_indices_var_name.map(ident);
    // Container width forwarded to repeated cells' vertical query (column flex),
    // so a height-for-width instance wraps to the real width like a static cell.
    let cross_width = repeated_cross_width.map(|w| compile_expression(w, ctx));
    let mut push_code =
        "std::vector<slint::cbindgen_private::LayoutItemInfo> cells_vector_h; std::vector<slint::cbindgen_private::LayoutItemInfo> cells_vector_v; std::vector<slint::cbindgen_private::FlexItemProps> flex_props_vector;".to_owned();
    let mut repeater_idx = 0usize;

    for item in elements {
        match item {
            Either::Left((value_h, value_v, value_flex)) => {
                write!(
                    push_code,
                    "cells_vector_h.push_back({{ {} }}); cells_vector_v.push_back({{ {} }}); flex_props_vector.push_back({{ {} }});",
                    compile_expression(value_h, ctx),
                    compile_expression(value_v, ctx),
                    compile_expression(value_flex, ctx)
                )
                .unwrap();
            }
            Either::Right(repeater) => {
                let repeater_index = usize::from(repeater.repeater_index);
                write!(push_code, "self->repeater_{repeater_index}.track_instance_changes();")
                    .unwrap();

                if let Some(ri) = &repeated_indices_var_name {
                    write!(
                        push_code,
                        "{ri}_array[{c}] = cells_vector_h.size();",
                        c = repeater_idx * 2
                    )
                    .unwrap();
                    write!(
                        push_code,
                        "{ri}_array[{c}] = self->repeater_{repeater_index}.len();",
                        c = repeater_idx * 2 + 1,
                    )
                    .unwrap();
                }
                repeater_idx += 1;
                // for_each only visits instantiated slots; pad the cells up to len() afterwards so
                // the cell count matches the repeater length recorded in the repeater_indices array
                // (not-yet-instantiated rows get placeholders).
                // For a column flex, measure each instance's vertical info at the
                // container width; otherwise use its preferred-width default.
                let v_query = match &cross_width {
                    Some(w) => format!(
                        "sub_comp->flexbox_layout_item_info_at_cross_width(static_cast<float>({w}))"
                    ),
                    None => "sub_comp->flexbox_layout_item_info(slint::cbindgen_private::Orientation::Vertical, std::nullopt)".to_owned(),
                };
                // The instance vtable returns the bundled FlexboxLayoutItemInfo; split
                // it into the constraint cell and the (axis-independent) flex props.
                write!(
                    push_code,
                    "{{ \
                     auto start_offset = cells_vector_h.size(); \
                     self->repeater_{repeater_index}.for_each([&](const auto &sub_comp){{ \
                     auto info_h = sub_comp->flexbox_layout_item_info(slint::cbindgen_private::Orientation::Horizontal, std::nullopt); \
                     auto info_v = {v_query}; \
                     flex_props_vector.push_back(info_h.props); \
                     cells_vector_h.push_back({{ info_h.constraint }}); \
                     cells_vector_v.push_back({{ info_v.constraint }}); }}); \
                     auto repeater_len = self->repeater_{repeater_index}.len(); \
                     cells_vector_h.resize(start_offset + repeater_len); \
                     cells_vector_v.resize(start_offset + repeater_len); \
                     flex_props_vector.resize(start_offset + repeater_len); }}"
                )
                .unwrap();
            }
        }
    }

    let ri = repeated_indices_var_name.as_ref().map_or(String::new(), |ri| {
        write!(
            push_code,
            "slint::cbindgen_private::Slice<int> {ri} = slint::private_api::make_slice(std::span({ri}_array));"
        )
        .unwrap();
        format!("std::array<int, {}> {ri}_array;", 2 * repeater_idx)
    });
    format!(
        "[&]{{ {ri} {push_code} [[maybe_unused]] slint::cbindgen_private::Slice<slint::cbindgen_private::LayoutItemInfo>{cells_h} = slint::private_api::make_slice(std::span(cells_vector_h)); [[maybe_unused]] slint::cbindgen_private::Slice<slint::cbindgen_private::LayoutItemInfo>{cells_v} = slint::private_api::make_slice(std::span(cells_vector_v)); [[maybe_unused]] slint::cbindgen_private::Slice<slint::cbindgen_private::FlexItemProps>{flex_props} = slint::private_api::make_slice(std::span(flex_props_vector)); return {}; }}()",
        compile_expression(sub_expression, ctx),
        cells_h = ident(cells_h_variable),
        cells_v = ident(cells_v_variable),
        flex_props = ident(flex_props_variable),
    )
}

fn generate_with_grid_input_data(
    cells_variable: &str,
    repeated_indices_var_name: &SmolStr,
    repeater_steps_var_name: &SmolStr,
    elements: &[Either<llr::Expression, llr::GridLayoutRepeatedElement>],
    sub_expression: &llr::Expression,
    ctx: &llr_EvaluationContext<CppGeneratorContext>,
) -> String {
    let repeated_indices_var_name = Some(ident(repeated_indices_var_name));
    let repeater_steps_var_name = Some(ident(repeater_steps_var_name));
    let mut push_code =
        "std::vector<slint::cbindgen_private::GridLayoutInputData> cells_vector;".to_owned();
    let mut repeater_idx = 0usize;
    let mut has_new_row_bool = false;

    for item in elements {
        match item {
            Either::Left(value) => {
                write!(
                    push_code,
                    "cells_vector.push_back({{ {} }});",
                    compile_expression(value, ctx)
                )
                .unwrap();
            }
            Either::Right(repeater) => {
                let repeater_id = format!("repeater_{}", usize::from(repeater.repeater_index));
                write!(push_code, "self->{repeater_id}.track_instance_changes();").unwrap();

                if let Some(ri) = &repeated_indices_var_name {
                    write!(push_code, "{ri}_array[{}] = cells_vector.size();", repeater_idx * 2)
                        .unwrap();
                    write!(
                        push_code,
                        "{ri}_array[{c}] = self->{repeater_id}.len();",
                        c = repeater_idx * 2 + 1,
                    )
                    .unwrap();
                }
                let maybe_bool = if has_new_row_bool { "" } else { "bool " };
                let repeater_loop_code = generate_repeater_loop_code(
                    repeater.repeater_index,
                    &repeater.row_child_templates,
                    &repeater_steps_var_name,
                    repeater_idx,
                    "total_item_count",
                    |repeater_id, static_count, inner_ensure, rs_init| {
                        format!(
                            "{maybe_bool} new_row = {new_row};
                            {{
                                size_t max_total = {static_count};
                                self->{repeater_id}.for_each([&](const auto &sub_comp) {{
                                    {inner_ensure}
                                }});
                                size_t total_item_count = max_total;
                                {rs_init}
                                auto start_offset = cells_vector.size();
                                cells_vector.resize(start_offset + self->{repeater_id}.len() * total_item_count);
                                std::size_t i = 0;
                                self->{repeater_id}.for_each([&](const auto &sub_comp) {{
                                    auto offset = start_offset + i * total_item_count;
                                    sub_comp->grid_layout_input_for_repeated(new_row, std::span(cells_vector).subspan(offset, total_item_count));
                                    ++i;
                                }});
                            }}",
                            new_row = repeater.new_row,
                        )
                    },
                    |repeater_id, step, is_column_repeater, rs_init| {
                        let reset_new_row =
                            if is_column_repeater { "new_row = false;" } else { "" };
                        format!(
                            "{rs_init}{maybe_bool} new_row = {new_row};
                            {{
                                auto start_offset = cells_vector.size();
                                cells_vector.resize(start_offset + self->{repeater_id}.len() * {step});
                                std::size_t i = 0;
                                self->{repeater_id}.for_each([&](const auto &sub_comp) {{
                                    auto offset = start_offset + i * {step};
                                    sub_comp->grid_layout_input_for_repeated(new_row, std::span(cells_vector).subspan(offset, {step}));
                                    {reset_new_row}
                                    ++i;
                                }});
                            }}",
                            new_row = repeater.new_row,
                        )
                    },
                );
                push_code.push_str(&repeater_loop_code);
                repeater_idx += 1;
                has_new_row_bool = true;
            }
        }
    }

    let ri = repeated_indices_var_name.as_ref().map_or(String::new(), |ri| {
        write!(
            push_code,
            "slint::cbindgen_private::Slice<int> {ri} = slint::private_api::make_slice(std::span({ri}_array));"
        )
        .unwrap();
        format!("std::array<int, {}> {ri}_array;", 2 * repeater_idx)
    });
    let rs = repeater_steps_var_name.as_ref().map_or(String::new(), |rs| {
        write!(
            push_code,
            "slint::cbindgen_private::Slice<int> {rs} = slint::private_api::make_slice(std::span({rs}_array));"
        )
        .unwrap();
        format!("std::array<int, {}> {rs}_array;", repeater_idx)
    });
    format!(
        "[&]{{ {ri} {rs} {push_code} slint::cbindgen_private::Slice<slint::cbindgen_private::GridLayoutInputData>{} = slint::private_api::make_slice(std::span(cells_vector)); return {}; }}()",
        ident(cells_variable),
        compile_expression(sub_expression, ctx)
    )
}

/// Like compile expression, but prepended with `return` if not void.
/// ret_type is the expecting type that should be returned with that return statement
fn return_compile_expression(
    expr: &llr::Expression,
    ctx: &EvaluationContext,
    ret_type: Option<&Type>,
) -> String {
    let e = compile_expression(expr, ctx);
    if ret_type == Some(&Type::Void) || ret_type == Some(&Type::Invalid) {
        e
    } else {
        let ty = expr.ty(ctx);
        if ty == Type::Invalid && ret_type.is_some() {
            // e is unreachable so it probably throws. But we still need to return something to avoid a warning
            format!("{e}; return {{}}")
        } else if ty == Type::Invalid || ty == Type::Void {
            e
        } else {
            format!("return {e}")
        }
    }
}

pub fn generate_type_aliases(file: &mut File, doc: &Document) {
    let type_aliases = doc
        .exports
        .iter()
        .filter_map(|export| match &export.1 {
            Either::Left(component) if !component.is_global() => {
                Some((&export.0.name, component.id.clone()))
            }
            Either::Right(ty) => match &ty {
                Type::Struct(s) if s.node().is_some() => {
                    Some((&export.0.name, s.name.cpp_type().unwrap()))
                }
                Type::Enumeration(en) => Some((&export.0.name, en.name.clone())),
                _ => None,
            },
            _ => None,
        })
        .filter(|(export_name, type_name)| *export_name != type_name)
        .map(|(export_name, type_name)| {
            Declaration::TypeAlias(TypeAlias {
                old_name: ident(&type_name),
                new_name: ident(export_name),
            })
        });

    file.declarations.extend(type_aliases);
}

#[cfg(feature = "bundle-translations")]
fn generate_translation(
    translations: &crate::translations::Translations,
    compilation_unit: &llr::CompilationUnit,
    declarations: &mut Vec<Declaration>,
) {
    for (idx, m) in translations.strings.iter().enumerate() {
        declarations.push(Declaration::Var(Var {
            ty: "const char8_t* const".into(),
            name: format_smolstr!("slint_translation_bundle_{idx}"),
            array_size: Some(m.len()),
            init: Some(format!(
                "{{ {} }}",
                m.iter()
                    .map(|s| match s {
                        Some(s) => format_smolstr!("u8\"{}\"", escape_string(s.as_str())),
                        None => "nullptr".into(),
                    })
                    .join(", ")
            )),
            ..Default::default()
        }));
    }
    declarations.push(Declaration::Var(Var {
        ty: "uint32_t".into(),
        name: "slint_translation_bundle_decimal_separators".into(),
        array_size: Some(translations.languages.len()),
        init: Some(format!(
            "{{ {} }}",
            translations
                .languages
                .iter()
                .map(|(_, s)| format_smolstr!("{}", *s as u32),)
                .join(", ")
        )),
        ..Default::default()
    }));
    for (idx, ms) in translations.plurals.iter().enumerate() {
        let all_strs = ms.iter().flatten().flatten();
        let all_strs_len = all_strs.clone().count();
        declarations.push(Declaration::Var(Var {
            ty: "const char8_t* const".into(),
            name: format_smolstr!("slint_translation_bundle_plural_{}_str", idx),
            array_size: Some(all_strs_len),
            init: Some(format!(
                "{{ {} }}",
                all_strs.map(|s| format_smolstr!("u8\"{}\"", escape_string(s.as_str()))).join(", ")
            )),
            ..Default::default()
        }));

        let mut count = 0;
        declarations.push(Declaration::Var(Var {
            ty: "const uint32_t".into(),
            name: format_smolstr!("slint_translation_bundle_plural_{}_idx", idx),
            array_size: Some(ms.len()),
            init: Some(format!(
                "{{ {} }}",
                ms.iter()
                    .map(|x| {
                        count += x.as_ref().map_or(0, |x| x.len());
                        count
                    })
                    .join(", ")
            )),
            ..Default::default()
        }));
    }

    if !translations.plurals.is_empty() {
        let ctx = EvaluationContext {
            compilation_unit,
            current_scope: EvaluationScope::Global(0.into()),
            generator_state: CppGeneratorContext {
                global_access: "\n#error \"language rule can't access state\";".into(),
                conditional_includes: &Default::default(),
            },
            argument_types: &[Type::Int32],
        };

        declarations.push(Declaration::Var(Var {
            ty: format_smolstr!(
                "const std::array<uintptr_t (*const)(int32_t), {}>",
                translations.plural_rules.len()
            ),
            name: "slint_translated_plural_rules".into(),
            init: Some(format!(
                "{{ {} }}",
                translations
                    .plural_rules
                    .iter()
                    .map(|s| match s {
                        Some(s) => {
                            format!(
                                "[]([[maybe_unused]] int32_t arg_0) -> uintptr_t {{ return {}; }}",
                                compile_expression(s, &ctx)
                            )
                        }
                        None => "nullptr".into(),
                    })
                    .join(", ")
            )),
            ..Default::default()
        }));
    }
}
