// 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 pass lowers `ComponentContainer`s.
//!
//! The `ComponentContainer` is lowered to something like this:
//!
//! ```slint
//!     ComponentContainer {
//!         if false: Empty {}
//!     }
//! ```
//!
//! The Repeater that is inserted is where the ComponentContainer will embed
//! its `ItemTree` under. It is never evaluated as a conditional, so we should
//! be able to use some invalid expression, but that triggers asserts in later
//! compiler passes.
//!
//! The Repeater that is marked as `is_component_placeholder`, so that
//! we can generate the expected code later.
//!
//! The `Empty` behind the conditional `Repeater` is never used.

use crate::diagnostics::BuildDiagnostics;
use crate::langtype::ElementType;
use crate::typeloader::TypeLoader;
use crate::{expression_tree, object_tree::*};
use std::cell::RefCell;
use std::rc::Rc;

pub fn lower_component_container(
    doc: &Document,
    type_loader: &mut TypeLoader,
    diag: &mut BuildDiagnostics,
) {
    let empty_type = type_loader.global_type_registry.borrow().empty_type();

    doc.visit_all_used_components(|component| {
        recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
            if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "ComponentContainer") {
                diagnose_component_container(elem, diag);
                process_component_container(elem, &empty_type);
            }
        })
    });
}

fn diagnose_component_container(element: &ElementRc, diag: &mut BuildDiagnostics) {
    let elem = element.borrow();
    if !elem.children.is_empty() {
        diag.push_error("ComponentContainers may not have children".into(), &*element.borrow());
    }
    for (name, cip) in &*elem.enclosing_component.upgrade().unwrap().child_insertion_points.borrow()
    {
        if Rc::ptr_eq(&cip.parent, element) {
            diag.push_error(
                format!("{} cannot appear in a ComponentContainer", slot_error_subject(name)),
                &*element.borrow(),
            );
        }
    }
}

fn process_component_container(element: &ElementRc, empty_type: &ElementType) {
    let suffix = element.borrow().id.clone();

    let component = Rc::new_cyclic(|component_weak| {
        let root_element = Rc::new(RefCell::new(Element {
            id: smol_str::format_smolstr!("component_container_internal_{}", suffix),
            base_type: empty_type.clone(),
            enclosing_component: component_weak.clone(),
            ..Default::default()
        }));

        Component {
            id: smol_str::format_smolstr!("ComponentContainerInternal_{}", suffix),
            root_element,
            ..Default::default()
        }
    });

    let mut elem = element.borrow_mut();

    let embedded_element = Element::make_rc(Element {
        base_type: ElementType::Component(component.clone()),
        id: smol_str::format_smolstr!("component_container_placeholder_{}", suffix),
        debug: elem.debug.clone(),
        enclosing_component: elem.enclosing_component.clone(),
        default_fill_parent: (true, true),
        inline_depth: elem.inline_depth,
        repeated: Some(RepeatedElementInfo {
            model: expression_tree::Expression::BoolLiteral(false),
            model_data_id: Default::default(),
            index_id: Default::default(),
            is_conditional_element: true,
            is_listview: None,
        }),
        is_component_placeholder: true,
        ..Default::default()
    });
    elem.children.push(embedded_element);
}
