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

//! This module contains the [`Repeater`] and [`Conditional`] types that are
//! used by generated code to instantiate items from a model using the `for`
//! syntax, and [`RepeatedItemTree`] which is the trait implemented by the
//! generated repeated components.
//!
//! The [`RepeaterInstanceOps`] trait abstracts over instance storage so the
//! update algorithm can be shared between Rust and C++ (via FFI).

use super::model_peer::{ModelChangeListener, ModelChangeListenerContainer};
use super::{Model, ModelExt, ModelRc};
use crate::item_tree::{ItemTreeVTable, TraversalOrder};
use crate::layout::Orientation;
use crate::lengths::{LogicalLength, RectLengths};
use crate::{Coord, Property};
use alloc::vec::Vec;
use core::cell::RefCell;
use core::pin::Pin;
#[allow(unused)]
use euclid::num::Floor;
use pin_project::pin_project;

type ItemTreeRc<C> = vtable::VRc<crate::item_tree::ItemTreeVTable, C>;

/// ItemTree that can be instantiated by a repeater.
pub trait RepeatedItemTree:
    crate::item_tree::ItemTree + vtable::HasStaticVTable<ItemTreeVTable> + 'static
{
    /// The data corresponding to the model
    type Data: Default + 'static;

    /// Update this ItemTree at the given index and the given data
    fn update(&self, index: usize, data: Self::Data);

    /// Called once after the ItemTree has been instantiated and update()
    /// was called once.
    fn init(&self) {}

    /// Layout this item in the listview
    ///
    /// offset_y is the `y` position where this item should be placed.
    /// it should be updated to be to the y position of the next item.
    ///
    /// Returns the minimum item width which will be used to compute the listview's content width
    fn listview_layout(self: Pin<&Self>, _offset_y: &mut LogicalLength) -> LogicalLength {
        LogicalLength::default()
    }

    /// Returns what's needed to perform the layout if this ItemTree is in a layout
    /// In case of repeated Rows, the index of a child item is set
    fn layout_item_info(
        self: Pin<&Self>,
        _orientation: Orientation,
        _child_index: Option<usize>,
    ) -> crate::layout::LayoutItemInfo {
        crate::layout::LayoutItemInfo::default()
    }

    /// Returns what's needed to perform a flexbox layout if this ItemTree is in a FlexboxLayout.
    /// Includes flex-specific properties (flex-grow, flex-shrink, flex-basis, align-self, order).
    fn flexbox_layout_item_info(
        self: Pin<&Self>,
        orientation: Orientation,
        child_index: Option<usize>,
    ) -> crate::layout::FlexboxLayoutItemInfo {
        self.layout_item_info(orientation, child_index).into()
    }

    /// Vertical flexbox info measured at the given cross-axis (container) width.
    /// A column FlexboxLayout calls this so a height-for-width instance wraps to
    /// the real width. The default ignores the width (non-height-for-width
    /// cells); the generated code overrides it for height-for-width instances.
    fn flexbox_layout_item_info_at_cross_width(
        self: Pin<&Self>,
        _flex_cross_width: f32,
    ) -> crate::layout::FlexboxLayoutItemInfo {
        self.flexbox_layout_item_info(Orientation::Vertical, None)
    }

    /// Horizontal flexbox info measured at the given cross-axis (assigned)
    /// height. A FlexboxLayout calls this so a width-for-height instance
    /// resolves to the width it really needs at that height. The default
    /// ignores the height (non-width-for-height cells); the generated code
    /// overrides it for width-for-height instances.
    fn flexbox_layout_item_info_at_cross_height(
        self: Pin<&Self>,
        _flex_cross_height: f32,
    ) -> crate::layout::FlexboxLayoutItemInfo {
        self.flexbox_layout_item_info(Orientation::Horizontal, None)
    }

    /// Fills in the grid layout input data for this ItemTree if it is in a grid layout.
    /// This will be a single GridLayoutInputData if the repeated item is a single cell,
    /// or multiple GridLayoutInputData if the repeated item is a full Row.
    /// The slice must have the exact size required (known at compile time).
    fn grid_layout_input_data(
        self: Pin<&Self>,
        _new_row: bool,
        _result: &mut [crate::layout::GridLayoutInputData],
    ) {
        crate::debug_log!(
            "Internal error in Slint: RepeatedItemTree::grid_layout_input_data() not implemented for {}",
            core::any::type_name::<Self>()
        );
        // the actual implementation is in the code generated by generate_repeated_component()
    }
}

#[derive(Clone, Copy, PartialEq, Debug)]
enum RepeatedInstanceState {
    /// The item is in a clean state
    Clean,
    /// The model data is stale and needs to be refreshed
    Dirty,
}
struct RepeaterInner<C: RepeatedItemTree> {
    instances: Vec<(RepeatedInstanceState, Option<ItemTreeRc<C>>)>,
    /// ListView-specific layout state (offset, cached heights, scroll position).
    layout_state: RepeaterLayoutState,
}

impl<C: RepeatedItemTree> Default for RepeaterInner<C> {
    fn default() -> Self {
        RepeaterInner { instances: Default::default(), layout_state: Default::default() }
    }
}

/// Persistent layout state for a ListView repeater.
#[derive(Default, Clone, Debug)]
#[repr(C)]
pub struct RepeaterLayoutState {
    /// The model row index of the first instance in the collection.
    pub offset: usize,
    /// The average visible item height (cached between frames).
    pub cached_item_height: Coord,
    /// The content_y value from the previous layout pass.
    /// It is used to detect if we are scrolling up or down
    pub previous_content_y: Coord,
    /// The y position of the item at `offset`.
    pub anchor_y: Coord,
}

/// Abstraction over a repeater's instance collection so the same algorithm
/// works for both native Rust repeaters and C++ repeaters via FFI.
trait RepeaterInstanceOps {
    /// Number of currently instantiated items.
    fn len(&self) -> usize;

    /// Replace the range `position..position+remove` with `add` new empty/dirty slots.
    fn splice(&mut self, position: usize, remove: usize, add: usize);

    /// If dirty, ensure the instance is created, initialized, and updated
    /// for `row`. Returns `true` if freshly created.
    fn ensure_updated(&mut self, instance_idx: usize, row: usize) -> bool;

    /// Height of the instance, or `None` if not yet created.
    fn height(&self, instance_idx: usize) -> Option<Coord>;

    /// Call `listview_layout` on the instance.
    /// Advances `*y` to the next item position. Returns item width.
    fn listview_layout(&self, instance_idx: usize, y: &mut Coord) -> Coord;
}

/// More rows than this is presumably a bug (e.g. a division by zero) and would run out of memory
const MAX_EAGER_INSTANCE_COUNT: usize = 1 << 20;

/// Update all instances in the repeater, creating any that are missing.
fn update_all_instances(ops: &mut impl RepeaterInstanceOps, offset: usize, count: usize) {
    let count = if count > MAX_EAGER_INSTANCE_COUNT {
        crate::debug_log!("A repeater's model has {count} rows: too many to instantiate");
        0
    } else {
        count
    };
    let cur = ops.len();
    if count > cur {
        ops.splice(cur, 0, count - cur);
    } else if count < cur {
        ops.splice(count, cur - count, 0);
    }
    for i in 0..count {
        ops.ensure_updated(i, i + offset);
    }
}

/// Update only the instances visible in the ListView viewport.
///
/// This is the core virtualization algorithm: it estimates which model rows
/// are visible, instantiates/updates those, lays them out, and cleans up
/// off-screen instances. Returns whether any instance was created.
fn update_visible_instances(
    ops: &mut impl RepeaterInstanceOps,
    state: &mut RepeaterLayoutState,
    row_count: usize,
    content_width: Option<Pin<&Property<LogicalLength>>>,
    content_height: Option<Pin<&Property<LogicalLength>>>,
    content_y: Pin<&Property<LogicalLength>>,
    listview_width: LogicalLength,
    listview_height: LogicalLength,
) -> bool {
    let zero = LogicalLength::default();
    let mut content_width_value = listview_width.get();
    let listview_height = listview_height.get();

    if row_count == 0 {
        ops.splice(0, ops.len(), 0);
        if let Some(content_height) = content_height {
            content_height.set(zero);
        }
        content_y.set(zero);
        if let Some(content_width) = content_width {
            content_width.set(listview_width);
        }
        return false;
    }

    let mut content_y_value = content_y.get().get();
    if !content_y.has_binding() {
        content_y_value = content_y_value.min(0 as Coord);
    }

    let mut changed = false;

    // Estimate element height from cached value or by measuring existing instances.
    let element_height = if state.cached_item_height > 0 as Coord {
        state.cached_item_height
    } else {
        let mut total_height: Coord = 0 as Coord;
        let mut count = 0usize;
        for i in 0..ops.len() {
            if let Some(h) = ops.height(i) {
                total_height += h;
                count += 1;
            }
        }

        if count > 0 {
            total_height / count as Coord
        } else {
            // No items exist yet. Create one to measure.
            state.offset = state.offset.min(row_count - 1);
            ops.splice(0, ops.len(), 1);
            changed |= ops.ensure_updated(0, state.offset);
            ops.height(0).unwrap_or(0 as Coord)
        }
    };

    if state.offset >= row_count {
        state.offset = row_count - 1;
    }

    let one_and_a_half_screen = listview_height * 3 as Coord / 2 as Coord;
    let first_item_y = state.anchor_y;
    let last_item_bottom = first_item_y + element_height * ops.len() as Coord;

    let (mut new_offset, mut new_offset_y) = if first_item_y
        > -content_y_value + one_and_a_half_screen
        || (content_height.is_some() && last_item_bottom + element_height < -content_y_value)
    {
        // Jumping more than 1.5 screens: random seek.
        ops.splice(0, ops.len(), 0);
        state.offset = ((-content_y_value / element_height).floor() as usize).min(row_count - 1);
        (state.offset, 0 as Coord)
    } else if content_y_value < state.previous_content_y {
        // Scrolled down: find the new offset by walking existing instances.
        let mut it_y = first_item_y + content_y_value;
        let mut new_off = state.offset;
        for i in 0..ops.len() {
            changed |= ops.ensure_updated(i, new_off);
            let h = ops.height(i).unwrap_or(0 as Coord);
            if it_y + h > 0 as Coord || new_off + 1 >= row_count {
                break;
            }
            it_y += h;
            new_off += 1;
        }
        (new_off, it_y)
    } else {
        // Scrolled up: will instantiate items before offset in the loop below.
        (state.offset, first_item_y + content_y_value)
    };

    let mut loop_count = 0;
    loop {
        // Fill gap before new_offset using already-instantiated items.
        while new_offset > state.offset && new_offset_y > 0 as Coord {
            new_offset -= 1;
            new_offset_y -= ops.height(new_offset - state.offset).unwrap_or(0 as Coord);
        }
        // If there is still a gap, create new instances before the current ones.
        let mut prepend_count = 0;
        while new_offset > 0 && new_offset_y > 0 as Coord {
            new_offset -= 1;
            ops.splice(0, 0, 1);
            changed |= ops.ensure_updated(0, new_offset);
            new_offset_y -= ops.height(0).unwrap_or(0 as Coord);
            prepend_count += 1;
        }
        if prepend_count > 0 {
            state.offset = new_offset;
        }
        debug_assert!(new_offset >= state.offset && new_offset <= state.offset + ops.len());

        // Layout items until we fill the view, starting with already-instantiated ones.
        let mut y = new_offset_y;
        let mut idx = new_offset;
        let instances_begin = new_offset - state.offset;
        for i in instances_begin..ops.len() {
            if idx >= row_count {
                break;
            }
            changed |= ops.ensure_updated(i, idx);
            content_width_value = content_width_value.max(ops.listview_layout(i, &mut y));
            idx += 1;
            if y >= listview_height {
                break;
            }
        }

        // Create more items until there is no more room.
        while y < listview_height && idx < row_count {
            let i = ops.len();
            ops.splice(i, 0, 1);
            changed |= ops.ensure_updated(i, idx);
            content_width_value = content_width_value.max(ops.listview_layout(i, &mut y));
            idx += 1;
        }

        if y < listview_height && content_y_value < 0 as Coord && loop_count < 3 {
            debug_assert!(idx >= row_count);
            // Reached end of model with room to spare. Scroll up.
            content_y_value += listview_height - y;
            loop_count += 1;
            continue;
        }

        // Clean up instances that are not shown.
        if new_offset != state.offset {
            let remove_count = new_offset - state.offset;
            ops.splice(0, remove_count, 0);
            state.offset = new_offset;
        }
        let keep = idx - new_offset;
        if ops.len() > keep {
            ops.splice(keep, ops.len() - keep, 0);
        }

        if ops.len() == 0 {
            break;
        }

        // Recompute coordinates for the scrollbar.
        state.cached_item_height = (y - new_offset_y) / ops.len() as Coord;
        state.anchor_y = state.cached_item_height * state.offset as Coord;
        if let Some(content_height) = content_height {
            content_height.set(LogicalLength::new(state.cached_item_height * row_count as Coord));
        }
        if let Some(content_width) = content_width {
            content_width.set(LogicalLength::new(content_width_value));
        }
        let new_content_y = -state.anchor_y + new_offset_y;
        // Important: Use get_internal here, the content_y may have a binding on it (especially
        // a physical animation).
        // We must not yet trigger a re-evaluation of that binding, as we have already updated the
        // content_width and content_height, but the content_y is not yet consistent.
        // So the physics animations limit value may be inconsistent.
        if new_content_y != content_y.get_internal().get() {
            // If a physics animation is ongoing (e.g. due to a flick), we should not interrupt it.
            // The physics animation implements intercept_set, and is therefore not interrupted by
            // a call to set() - so it's okay to just use a normal set here.
            content_y.set(LogicalLength::new(new_content_y));
        }
        state.previous_content_y = new_content_y;

        break;
    }

    changed
}

/// Adapter implementing [`RepeaterInstanceOps`] for the native Rust repeater.
struct RustRepeaterOps<'a, C: RepeatedItemTree> {
    inner: &'a RefCell<RepeaterInner<C>>,
    init: &'a dyn Fn() -> ItemTreeRc<C>,
    model: &'a ModelRc<C::Data>,
}

impl<C: RepeatedItemTree> RepeaterInstanceOps for RustRepeaterOps<'_, C> {
    fn len(&self) -> usize {
        self.inner.borrow().instances.len()
    }

    fn splice(&mut self, position: usize, remove: usize, add: usize) {
        self.inner.borrow_mut().instances.splice(
            position..position + remove,
            core::iter::repeat_with(|| (RepeatedInstanceState::Dirty, None)).take(add),
        );
    }

    fn ensure_updated(&mut self, instance_idx: usize, row: usize) -> bool {
        let (created, instance) = {
            let mut inner = self.inner.borrow_mut();
            let c = &mut inner.instances[instance_idx];
            if c.0 != RepeatedInstanceState::Dirty {
                return false;
            }
            let created = c.1.is_none();
            if created {
                c.1 = Some((self.init)());
            }
            c.1.as_ref().unwrap().update(row, self.model.row_data(row).unwrap_or_default());
            c.0 = RepeatedInstanceState::Clean;
            (created, c.1.as_ref().unwrap().clone())
        };
        if created {
            crate::properties::evaluate_no_tracking(|| instance.init());
        }
        crate::item_tree::ensure_item_tree_instantiated(&vtable::VRc::into_dyn(instance));
        created
    }

    fn height(&self, instance_idx: usize) -> Option<Coord> {
        self.inner.borrow().instances[instance_idx]
            .1
            .as_ref()
            .map(|x| x.as_pin_ref().item_geometry(0).height_length().get())
    }

    fn listview_layout(&self, instance_idx: usize, y: &mut Coord) -> Coord {
        let inner = self.inner.borrow();
        let mut y_len = LogicalLength::new(*y);
        let w = inner.instances[instance_idx]
            .1
            .as_ref()
            .unwrap()
            .as_pin_ref()
            .listview_layout(&mut y_len);
        *y = y_len.get();
        w.get()
    }
}

/// This struct is put in a component when using the `for` syntax
/// It helps instantiating the ItemTree `T`
#[pin_project]
pub struct RepeaterTracker<T: RepeatedItemTree> {
    inner: RefCell<RepeaterInner<T>>,
    #[pin]
    model: Property<ModelRc<T::Data>>,
    #[pin]
    /// Set to true when the model becomes dirty.
    is_dirty: Property<bool>,
    #[pin]
    /// Marked dirty by `ensure_updated` when instances are added or
    /// removed.  Layout and visit code register this as a dependency so
    /// they re-evaluate only after the update pass materializes the
    /// change, not when the model first becomes dirty.
    instance_generation: Property<()>,
    /// Only used for the list view to track if the scrollbar has changed and item needs to be laid out again.
    #[pin]
    listview_geometry_tracker: crate::properties::PropertyTracker,
}

impl<T: RepeatedItemTree> ModelChangeListener for RepeaterTracker<T> {
    /// Notify the peers that a specific row was changed
    fn row_changed(self: Pin<&Self>, row: usize) {
        let mut inner = self.inner.borrow_mut();
        let inner = &mut *inner;
        if let Some(c) = inner.instances.get_mut(row.wrapping_sub(inner.layout_state.offset)) {
            if !self.model.is_dirty() {
                if let Some(comp) = c.1.as_ref() {
                    let model = self.project_ref().model.get_untracked();
                    comp.update(row, model.row_data(row).unwrap_or_default());
                    c.0 = RepeatedInstanceState::Clean;
                }
            } else {
                c.0 = RepeatedInstanceState::Dirty;
            }
        }
    }
    /// Notify the peers that rows were added
    fn row_added(self: Pin<&Self>, mut index: usize, mut count: usize) {
        let mut inner = self.inner.borrow_mut();
        if index < inner.layout_state.offset {
            if index + count <= inner.layout_state.offset {
                // Entirely before the visible range: shift the offset.
                inner.layout_state.offset += count;
                self.is_dirty.set(true);
                for c in inner.instances.iter_mut() {
                    c.0 = RepeatedInstanceState::Dirty;
                }
                return;
            }
            count -= inner.layout_state.offset - index;
            index = 0;
        } else {
            index -= inner.layout_state.offset;
        }
        if count == 0 || index > inner.instances.len() {
            return;
        }
        self.is_dirty.set(true);
        inner.instances.splice(
            index..index,
            core::iter::repeat_n((RepeatedInstanceState::Dirty, None), count),
        );
        for c in inner.instances[index + count..].iter_mut() {
            // Because all the indexes are dirty
            c.0 = RepeatedInstanceState::Dirty;
        }
    }
    /// Notify the peers that rows were removed
    fn row_removed(self: Pin<&Self>, mut index: usize, mut count: usize) {
        let mut inner = self.inner.borrow_mut();
        if index < inner.layout_state.offset {
            if index + count <= inner.layout_state.offset {
                // Entirely before the visible range: shift the offset.
                inner.layout_state.offset -= count;
                self.is_dirty.set(true);
                for c in inner.instances.iter_mut() {
                    c.0 = RepeatedInstanceState::Dirty;
                }
                return;
            }
            count -= inner.layout_state.offset - index;
            inner.layout_state.offset = index;
            index = 0;
        } else {
            index -= inner.layout_state.offset;
        }
        if count == 0 || index >= inner.instances.len() {
            return;
        }
        if (index + count) > inner.instances.len() {
            count = inner.instances.len() - index;
        }
        self.is_dirty.set(true);
        inner.instances.drain(index..(index + count));
        for c in inner.instances[index..].iter_mut() {
            // Because all the indexes are dirty
            c.0 = RepeatedInstanceState::Dirty;
        }
    }

    fn reset(self: Pin<&Self>) {
        self.is_dirty.set(true);
        self.inner.borrow_mut().instances.clear();
    }
}

impl<C: RepeatedItemTree> Default for RepeaterTracker<C> {
    fn default() -> Self {
        Self {
            inner: Default::default(),
            model: Property::new_named(ModelRc::default(), "i_slint_core::Repeater::model"),
            is_dirty: Property::new_named(false, "i_slint_core::Repeater::is_dirty"),
            instance_generation: Property::new_named(
                (),
                "i_slint_core::Repeater::instance_generation",
            ),
            listview_geometry_tracker: Default::default(),
        }
    }
}

#[pin_project]
pub struct Repeater<C: RepeatedItemTree>(#[pin] ModelChangeListenerContainer<RepeaterTracker<C>>);

impl<C: RepeatedItemTree> Default for Repeater<C> {
    fn default() -> Self {
        Self(Default::default())
    }
}

impl<C: RepeatedItemTree + 'static> Repeater<C> {
    fn data(self: Pin<&Self>) -> Pin<&RepeaterTracker<C>> {
        self.project_ref().0.get()
    }

    /// Register the model and dirty flag as dependencies of the current
    /// tracking scope (e.g. the redraw tracker) so it is notified when the
    /// model or its data changes.
    pub fn track_model_changes(self: Pin<&Self>) {
        self.data().project_ref().model.register_as_dependency();
        self.data().project_ref().is_dirty.register_as_dependency();
    }

    /// Register the instance generation as a dependency of the current
    /// tracking scope. This is for layout and visit code that should
    /// re-evaluate only after `ensure_updated` has materialized instance
    /// changes, not when the model first becomes dirty.
    pub fn track_instance_changes(self: Pin<&Self>) {
        self.data().project_ref().instance_generation.register_as_dependency();
    }

    fn model(self: Pin<&Self>) -> ModelRc<C::Data> {
        let model = self.data().project_ref().model;

        if model.is_dirty() {
            let old_model = model.get_internal();
            let m = model.get();
            if old_model != m {
                *self.data().inner.borrow_mut() = RepeaterInner::default();
                self.data().is_dirty.set(true);
                let peer = self.project_ref().0.model_peer();
                m.model_tracker().attach_peer(peer);
            }
            m
        } else {
            model.get()
        }
    }

    /// Call this function to make sure that the model is updated.
    /// The init function is the function to create a ItemTree.
    /// Returns `true` if instances were actually created or removed.
    /// Also recurses into child instances to ensure they are instantiated.
    pub fn ensure_updated(self: Pin<&Self>, init: impl Fn() -> ItemTreeRc<C>) -> bool {
        let model = self.model();
        let changed = if self.data().project_ref().is_dirty.get() {
            let count = model.row_count();
            let offset = self.0.inner.borrow().layout_state.offset;
            let mut ops = RustRepeaterOps { inner: &self.0.inner, init: &init, model: &model };
            self.data().is_dirty.set(false);
            update_all_instances(&mut ops, offset, count);
            self.data().instance_generation.mark_dirty();
            true
        } else {
            false
        };
        self.ensure_children_instantiated() || changed
    }

    /// Recurse into child instances to ensure they are instantiated.
    fn ensure_children_instantiated(&self) -> bool {
        let mut changed = false;
        for instance in self.instances_vec() {
            changed |=
                crate::item_tree::ensure_item_tree_instantiated(&vtable::VRc::into_dyn(instance));
        }
        changed
    }

    /// Register the ListView content properties as dependencies so that
    /// scrolling triggers a redraw.  Model dependencies are registered by
    /// [`Self::visit`], so this only covers the content geometry.
    pub fn track_changes_listview(
        self: Pin<&Self>,
        content_width: Option<Pin<&Property<LogicalLength>>>,
        content_height: Option<Pin<&Property<LogicalLength>>>,
        content_y: Pin<&Property<LogicalLength>>,
        listview_width: LogicalLength,
        listview_height: Pin<&Property<LogicalLength>>,
    ) {
        if let Some(content_width) = content_width {
            content_width.register_as_dependency();
        }
        if let Some(content_height) = content_height {
            content_height.register_as_dependency();
        }
        content_y.register_as_dependency();
        // listview_width is passed as a value, not a property, so it cannot
        // be registered as a dependency. Kept in the signature for symmetry
        // with ensure_updated_listview.
        let _ = listview_width;
        listview_height.register_as_dependency();
    }

    /// Same as `Self::ensure_updated` but for a ListView.
    /// Returns `true` if any instances were created or any child changed.
    pub fn ensure_updated_listview(
        self: Pin<&Self>,
        init: impl Fn() -> ItemTreeRc<C>,
        content_width: Option<Pin<&Property<LogicalLength>>>,
        content_height: Option<Pin<&Property<LogicalLength>>>,
        content_y: Pin<&Property<LogicalLength>>,
        listview_width: LogicalLength,
        listview_height: Pin<&Property<LogicalLength>>,
    ) -> bool {
        self.data().project_ref().is_dirty.set(false);

        let model = self.model();
        let row_count = model.row_count();

        let data = self.data();
        let mut layout_state = data.inner.borrow().layout_state.clone();
        let mut ops = RustRepeaterOps { inner: &data.inner, init: &init, model: &model };
        let changed = update_visible_instances(
            &mut ops,
            &mut layout_state,
            row_count,
            content_width,
            content_height,
            content_y,
            listview_width,
            listview_height.get(),
        );
        data.inner.borrow_mut().layout_state = layout_state;

        if changed {
            self.data().instance_generation.mark_dirty();
        }
        self.ensure_children_instantiated() || changed
    }

    /// Sets the data directly in the model
    pub fn model_set_row_data(self: Pin<&Self>, row: usize, data: C::Data) {
        let model = self.model();
        model.set_row_data(row, data);
    }

    /// Read a row from the model, registering a dependency on it when
    /// called from a binding evaluation.
    pub fn model_row_data(self: Pin<&Self>, row: usize) -> Option<C::Data> {
        self.model().row_data_tracked(row)
    }

    /// Set the model binding
    pub fn set_model_binding(&self, binding: impl Fn() -> ModelRc<C::Data> + 'static) {
        self.0.model.set_binding(binding);
    }

    /// Call the visitor for the root of each instance.
    /// Also registers model dependencies so the current tracking scope
    /// (e.g. the redraw tracker) is notified when the model changes.
    pub fn visit(
        self: Pin<&Self>,
        order: TraversalOrder,
        mut visitor: crate::item_tree::ItemVisitorRefMut,
    ) -> crate::item_tree::VisitChildrenResult {
        self.track_model_changes();
        // We can't keep self.inner borrowed because the event might modify the model
        let count = self.0.inner.borrow().instances.len() as u32;
        for i in 0..count {
            let i = if order == TraversalOrder::BackToFront { i } else { count - i - 1 };
            let c = self.0.inner.borrow().instances.get(i as usize).and_then(|c| c.1.clone());
            if let Some(c) = c
                && c.as_pin_ref().visit_children_item(-1, order, visitor.borrow_mut()).has_aborted()
            {
                return crate::item_tree::VisitChildrenResult::abort(i, 0);
            }
        }
        crate::item_tree::VisitChildrenResult::CONTINUE
    }

    /// Return the amount of instances currently in the repeater
    pub fn len(&self) -> usize {
        self.0.inner.borrow().instances.len()
    }

    /// Return the range of indices used by this Repeater.
    ///
    /// Two values are necessary here since the Repeater can start to insert the data from its
    /// model at an offset.
    pub fn range(&self) -> core::ops::Range<usize> {
        let inner = self.0.inner.borrow();
        core::ops::Range {
            start: inner.layout_state.offset,
            end: inner.layout_state.offset + inner.instances.len(),
        }
    }

    /// Return the instance for the given model index.
    /// The index should be within [`Self::range()`]
    pub fn instance_at(&self, index: usize) -> Option<ItemTreeRc<C>> {
        let inner = self.0.inner.borrow();
        inner.instances.get(index.checked_sub(inner.layout_state.offset)?).and_then(|c| c.1.clone())
    }

    /// Return true if the Repeater as empty
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Returns a vector containing all instances
    pub fn instances_vec(&self) -> Vec<ItemTreeRc<C>> {
        self.0.inner.borrow().instances.iter().flat_map(|x| x.1.clone()).collect()
    }
}

#[pin_project]
pub struct Conditional<C: RepeatedItemTree> {
    #[pin]
    model: Property<bool>,
    #[pin]
    instance_generation: Property<()>,
    instance: RefCell<Option<ItemTreeRc<C>>>,
}

impl<C: RepeatedItemTree> Default for Conditional<C> {
    fn default() -> Self {
        Self {
            model: Property::new_named(false, "i_slint_core::Conditional::model"),
            instance_generation: Property::new_named(
                (),
                "i_slint_core::Conditional::instance_generation",
            ),
            instance: RefCell::new(None),
        }
    }
}

impl<C: RepeatedItemTree + 'static> Conditional<C> {
    /// Register the condition as a dependency of the current tracking scope
    /// (e.g. the redraw tracker) so it is notified when the condition changes.
    pub fn track_model_changes(self: Pin<&Self>) {
        self.project_ref().model.register_as_dependency();
    }

    /// Register the instance generation as a dependency of the current
    /// tracking scope. Layout code uses this to re-evaluate only after
    /// `ensure_updated` materializes instance changes.
    pub fn track_instance_changes(self: Pin<&Self>) {
        self.project_ref().instance_generation.register_as_dependency();
    }

    /// Call this function to make sure that the model is updated.
    /// The init function is the function to create a ItemTree.
    /// Returns `true` if the instance was created or removed, or any child changed.
    pub fn ensure_updated(self: Pin<&Self>, init: impl Fn() -> ItemTreeRc<C>) -> bool {
        let model = self.project_ref().model.get();

        let changed = if !model {
            self.instance.take().is_some()
        } else if self.instance.borrow().is_none() {
            let i = init();
            self.instance.replace(Some(i.clone()));
            i.init();
            true
        } else {
            false
        };
        if changed {
            self.instance_generation.mark_dirty();
        }
        if let Some(instance) = self.instance.borrow().as_ref() {
            crate::item_tree::ensure_item_tree_instantiated(&vtable::VRc::into_dyn(
                instance.clone(),
            )) || changed
        } else {
            changed
        }
    }

    /// Set the model binding
    pub fn set_model_binding(&self, binding: impl Fn() -> bool + 'static) {
        self.model.set_binding(binding);
    }

    /// Call the visitor for the root of each instance.
    /// Also registers model dependencies so the current tracking scope
    /// (e.g. the redraw tracker) is notified when the condition changes.
    pub fn visit(
        self: Pin<&Self>,
        order: TraversalOrder,
        mut visitor: crate::item_tree::ItemVisitorRefMut,
    ) -> crate::item_tree::VisitChildrenResult {
        self.track_model_changes();
        // We can't keep self.inner borrowed because the event might modify the model
        let instance = self.instance.borrow().clone();
        if let Some(c) = instance
            && c.as_pin_ref().visit_children_item(-1, order, visitor.borrow_mut()).has_aborted()
        {
            return crate::item_tree::VisitChildrenResult::abort(0, 0);
        }

        crate::item_tree::VisitChildrenResult::CONTINUE
    }

    /// Return the amount of instances (1 if the conditional is active, 0 otherwise)
    pub fn len(&self) -> usize {
        self.instance.borrow().is_some() as usize
    }

    /// Return the range of indices used by this Conditional.
    ///
    /// Similar to Repeater::range, but the range is always [0, 1] if the Conditional is active.
    pub fn range(&self) -> core::ops::Range<usize> {
        0..self.len()
    }

    /// Return the instance for the given model index.
    /// The index should be within [`Self::range()`]
    pub fn instance_at(&self, index: usize) -> Option<ItemTreeRc<C>> {
        if index != 0 {
            return None;
        }
        self.instance.borrow().clone()
    }

    /// Return true if the Repeater as empty
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Returns a vector containing all instances
    pub fn instances_vec(&self) -> Vec<ItemTreeRc<C>> {
        self.instance.borrow().clone().into_iter().collect()
    }
}

#[cfg(feature = "ffi")]
mod ffi {
    #![allow(unsafe_code)]

    use super::*;

    /// C++ callback table for [`RepeaterInstanceOps`], including the opaque
    /// user_data pointer that is passed to each callback.
    #[repr(C)]
    pub struct RepeaterInstanceOpsVTable {
        pub user_data: *mut core::ffi::c_void,
        pub len: unsafe extern "C" fn(user_data: *mut core::ffi::c_void) -> usize,
        pub splice: unsafe extern "C" fn(
            user_data: *mut core::ffi::c_void,
            position: usize,
            remove: usize,
            add: usize,
        ),
        pub ensure_updated: unsafe extern "C" fn(
            user_data: *mut core::ffi::c_void,
            instance_idx: usize,
            row: usize,
        ) -> bool,
        /// Height of instance, or NaN if not yet created.
        pub height:
            unsafe extern "C" fn(user_data: *mut core::ffi::c_void, instance_idx: usize) -> Coord,
        pub listview_layout: Option<
            unsafe extern "C" fn(
                user_data: *mut core::ffi::c_void,
                instance_idx: usize,
                y: &mut Coord,
            ) -> Coord,
        >,
        pub init: unsafe extern "C" fn(user_data: *mut core::ffi::c_void, instance_idx: usize),
    }

    impl RepeaterInstanceOps for RepeaterInstanceOpsVTable {
        fn len(&self) -> usize {
            unsafe { (self.len)(self.user_data) }
        }
        fn splice(&mut self, position: usize, remove: usize, add: usize) {
            unsafe { (self.splice)(self.user_data, position, remove, add) }
        }
        fn ensure_updated(&mut self, instance_idx: usize, row: usize) -> bool {
            let created = unsafe { (self.ensure_updated)(self.user_data, instance_idx, row) };
            if created {
                unsafe { (self.init)(self.user_data, instance_idx) };
            }
            created
        }
        fn height(&self, instance_idx: usize) -> Option<Coord> {
            let h = unsafe { (self.height)(self.user_data, instance_idx) };
            if h.is_nan() { None } else { Some(h) }
        }
        fn listview_layout(&self, instance_idx: usize, y: &mut Coord) -> Coord {
            self.listview_layout
                .map_or(0 as Coord, |f| unsafe { f(self.user_data, instance_idx, y) })
        }
    }

    #[unsafe(no_mangle)]
    pub extern "C" fn slint_repeater_ensure_updated(
        ops: &mut RepeaterInstanceOpsVTable,
        offset: usize,
        count: usize,
    ) {
        update_all_instances(ops, offset, count);
    }

    #[unsafe(no_mangle)]
    pub extern "C" fn slint_repeater_ensure_updated_listview(
        ops: &mut RepeaterInstanceOpsVTable,
        state: &mut RepeaterLayoutState,
        row_count: usize,
        content_width: Option<Pin<&Property<LogicalLength>>>,
        content_height: Option<Pin<&Property<LogicalLength>>>,
        content_y: Pin<&Property<LogicalLength>>,
        listview_width: LogicalLength,
        listview_height: LogicalLength,
    ) -> bool {
        update_visible_instances(
            ops,
            state,
            row_count,
            content_width,
            content_height,
            content_y,
            listview_width,
            listview_height,
        )
    }
}
