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

mod apply_default_properties_from_style;
mod binding_analysis;
mod border_radius;
mod check_builtin_shadowing;
mod check_drag_area;
mod check_expressions;
mod check_public_api;
mod clip;
mod collect_custom_fonts;
mod collect_globals;
mod collect_init_code;
mod collect_libraries;
mod collect_structs_and_enums;
mod collect_subcomponents;
mod compile_paths;
pub(crate) mod const_propagation;
mod deduplicate_property_read;
mod default_geometry;
mod deprecated_rotation_origin;
#[cfg(feature = "renderer-software")]
mod embed_glyphs;
mod embed_images;
mod flickable;
mod focus_handling;
pub mod generate_item_indices;
pub mod infer_aliases_types;
mod inject_debug_hooks;
pub use inject_debug_hooks::property_id;
mod inlining;
mod key_bindings;
mod lower_absolute_coordinates;
mod lower_accessibility;
mod lower_component_container;
pub(crate) mod lower_layout;
mod lower_menus;
mod lower_platform;
mod lower_popups;
mod lower_property_to_element;
mod lower_radiogroup;
mod lower_repeated_rows;
mod lower_shadows;
mod lower_states;
mod lower_tabwidget;
mod lower_text_input_interface;
mod lower_timers;
mod lower_tooltips;
pub mod materialize_fake_properties;
pub mod move_declarations;
mod optimize_useless_rectangles;
mod purity_check;
mod remove_aliases;
mod remove_constant_conditions;
mod remove_return;
mod remove_unused_properties;
mod repeater_component;
pub mod resolve_native_classes;
pub mod resolving;
mod unique_id;
mod visible;
mod windows;
mod z_order;

use crate::expression_tree::Expression;
use smol_str::SmolStr;

pub use binding_analysis::GlobalAnalysis;

pub fn ignore_debug_hooks(expr: &Expression) -> &Expression {
    let mut expr = expr;
    loop {
        match expr {
            Expression::DebugHook { expression, .. } => expr = expression.as_ref(),
            _ => return expr,
        }
    }
}

pub async fn run_passes(
    doc: &mut crate::object_tree::Document,
    type_loader: &mut crate::typeloader::TypeLoader,
    keep_raw: bool,
    diag: &mut crate::diagnostics::BuildDiagnostics,
) -> Option<crate::typeloader::TypeLoader> {
    let style_metrics = {
        // Ignore import errors
        let mut build_diags_to_ignore = crate::diagnostics::BuildDiagnostics::default();
        // Import from style-base.slint instead of std-widgets.slint to avoid loading
        // the entire widget library just to get StyleMetrics.
        type_loader
            .import_component("style-base.slint", "StyleMetrics", &mut build_diags_to_ignore)
            .await
            .unwrap_or_else(|| panic!("can't load style metrics"))
    };

    let palette = {
        // Ignore import errors
        let mut build_diags_to_ignore = crate::diagnostics::BuildDiagnostics::default();
        // Import from style-base.slint instead of std-widgets.slint to avoid loading
        // the entire widget library just to get Palette.
        type_loader
            .import_component("style-base.slint", "Palette", &mut build_diags_to_ignore)
            .await
            .unwrap_or_else(|| panic!("can't load palette"))
    };

    let global_type_registry = type_loader.global_type_registry.clone();
    // The shared symbol-name counters, handed to the passes that generate names.
    let symbol_counters = type_loader.symbol_counters.clone();

    run_import_passes(doc, type_loader, diag);
    check_public_api::check_public_api(doc, &type_loader.compiler_config, diag);

    let raw_type_loader =
        keep_raw.then(|| crate::typeloader::snapshot_with_extra_doc(type_loader, doc).unwrap());

    // Inject debug hooks early — before any lowering or inlining — so source element identity
    // is preserved and hooks can be attributed to the correct source location.
    if let Some(random_state) = &type_loader.compiler_config.debug_hooks {
        for root_component in doc.exported_roots() {
            inject_debug_hooks::inject_debug_hooks(&root_component, random_state);
        }
    }

    collect_libraries::collect_libraries(doc);
    collect_subcomponents::collect_subcomponents(doc);
    lower_tooltips::lower_tooltips(doc, type_loader, diag).await;
    lower_tabwidget::lower_tabwidget(doc, type_loader, diag).await;
    lower_radiogroup::lower_radiogroup(doc, type_loader, diag).await;
    lower_menus::lower_menus(doc, type_loader, diag).await;
    lower_component_container::lower_component_container(doc, type_loader, diag);
    collect_subcomponents::collect_subcomponents(doc);

    doc.visit_all_used_components(|component| {
        apply_default_properties_from_style::apply_default_properties_from_style(
            component,
            &style_metrics,
            &palette,
            diag,
        );
        lower_states::lower_states(component, diag);
        lower_text_input_interface::lower_text_input_interface(component);
        compile_paths::compile_paths(component, &doc.local_registry, diag);
        repeater_component::process_repeater_components(component);
        lower_popups::lower_popups(component, &doc.local_registry, diag);
        collect_init_code::collect_init_code(component);
        lower_timers::lower_timers(component, diag);
    });

    inlining::inline(doc, inlining::InlineSelection::InlineOnlyRequiredComponents, diag);
    collect_subcomponents::collect_subcomponents(doc);

    for root_component in doc.exported_roots() {
        focus_handling::call_focus_on_init(&root_component);
        windows::ensure_window(&root_component, &doc.local_registry, &style_metrics, diag);
    }
    if let Some(popup_menu_impl) = &doc.popup_menu_impl {
        focus_handling::call_focus_on_init(popup_menu_impl);
    }

    doc.visit_all_used_components(|component| {
        border_radius::handle_border_radius(component, diag);
        check_drag_area::check_drag_area(component, diag);
        deprecated_rotation_origin::handle_rotation_origin(component, diag);
        flickable::handle_flickable(component, &global_type_registry.borrow());
        lower_layout::lower_layouts(component, type_loader, &style_metrics, diag);
        default_geometry::default_geometry(component, diag, &symbol_counters);
        lower_layout::optimize_single_cell_layouts(component);
        lower_layout::synthesize_layoutinfo_v_with_constraint(component);
        lower_layout::synthesize_layoutinfo_h_with_constraint(component);
        lower_absolute_coordinates::lower_absolute_coordinates(component);
        z_order::reorder_by_z_order(component, diag);
        lower_property_to_element::lower_property_to_element(
            component,
            core::iter::once("opacity"),
            core::iter::empty(),
            None,
            &SmolStr::new_static("Opacity"),
            &global_type_registry.borrow(),
            diag,
        );
        lower_property_to_element::lower_property_to_element(
            component,
            core::iter::once("cache-rendering-hint"),
            core::iter::empty(),
            None,
            &SmolStr::new_static("Layer"),
            &global_type_registry.borrow(),
            diag,
        );
        visible::handle_visible(component, &global_type_registry.borrow(), diag);
        lower_shadows::lower_shadow_properties(component, &doc.local_registry, diag);
        lower_property_to_element::lower_transform_properties(
            component,
            &global_type_registry.borrow(),
            diag,
        );
        clip::handle_clip(component, &global_type_registry.borrow(), diag);
        if type_loader.compiler_config.accessibility {
            lower_accessibility::lower_accessibility_properties(component, diag);
        }
        lower_repeated_rows::lower_repeated_rows(component, &global_type_registry.borrow());
        materialize_fake_properties::materialize_fake_properties(component);
        lower_layout::check_popup_layout(component);
    });
    for root_component in doc.exported_roots() {
        lower_layout::check_window_layout(&root_component);
    }
    collect_globals::collect_globals(doc, diag);
    // Must be done before passes that rely on `NamedReference::is_constant`.
    collect_globals::mark_library_globals(doc);

    // Debug hooks rely on full inlining: the synthetic hooks injected on component-instance
    // elements are only upgraded with the component's real default bindings when the component
    // is inlined (see `BindingExpression::merge_with`). Without inlining they would shadow the
    // definition's defaults at runtime. This overrides a `SLINT_INLINING=false` env override.
    if type_loader.compiler_config.inline_all_elements
        || type_loader.compiler_config.debug_hooks.is_some()
    {
        inlining::inline(doc, inlining::InlineSelection::InlineAllComponents, diag);
        doc.used_types.borrow_mut().sub_components.clear();
    }

    let global_analysis =
        binding_analysis::binding_analysis(doc, &type_loader.compiler_config, diag);
    unique_id::assign_unique_id(doc);

    doc.visit_all_used_components(|component| {
        key_bindings::warn_duplicates(component, diag);
        lower_platform::lower_platform(component, type_loader);

        // Don't perform the empty rectangle removal when debug info is requested, because the resulting
        // item tree ends up with a hierarchy where certain items have children that aren't child elements
        // but siblings or sibling children. We need a new data structure to perform a correct element tree
        // traversal.
        // Also keep the rectangles when debug hooks are enabled: their (synthetic) hooks are
        // what makes the elements live-editable, and removing the element would drop them.
        if !type_loader.compiler_config.debug_info
            && type_loader.compiler_config.debug_hooks.is_none()
        {
            optimize_useless_rectangles::optimize_useless_rectangles(component);
        }
        move_declarations::move_declarations(component);
    });

    remove_aliases::remove_aliases(doc, diag);
    remove_return::remove_return(doc, &symbol_counters);

    doc.visit_all_used_components(|component| {
        if !diag.has_errors() {
            // binding loop causes panics in const_propagation
            const_propagation::const_propagation(component, &global_analysis);
            remove_constant_conditions::remove_constant_conditions(component);
        }
        deduplicate_property_read::deduplicate_property_read(component);
        if !component.is_global() && !component.is_interface() {
            resolve_native_classes::resolve_native_classes(component);
        }
    });

    remove_unused_properties::remove_unused_properties(doc);

    // With debug hooks enabled, every synthetic hook must by now either have been upgraded
    // (by a pass computing the property's value or by inlining merging the definition's
    // default) or sit on a property that exists at runtime. An orphan would abort the
    // interpreter at instantiation ("unknown property ..."); catch it here with a source
    // location instead.
    if type_loader.compiler_config.debug_hooks.is_some() && !diag.has_errors() {
        doc.visit_all_used_components(|component| {
            inject_debug_hooks::validate_no_orphan_synthetic_hooks(component);
        });
    }

    // collect globals once more: After optimizations we might have less globals
    collect_globals::collect_globals(doc, diag);
    collect_structs_and_enums::collect_structs_and_enums(doc);

    doc.visit_all_used_components(|component| {
        if !component.is_global() {
            generate_item_indices::generate_item_indices(component);
        }
    });

    // The fonts (system + imported) used to embed glyphs and rasterize SVG text are
    // shared between `embed_images` and `embed_glyphs`, so the system is scanned once.
    #[cfg(feature = "renderer-software")]
    let font_collection = (type_loader.compiler_config.embed_resources
        == crate::EmbedResourcesKind::EmbedTextures)
        .then(|| {
            let custom = embed_glyphs::read_custom_fonts(
                std::iter::once(&*doc).chain(type_loader.all_documents()),
                diag,
            );
            embed_glyphs::shared_font_collection(custom)
        });
    #[cfg(not(feature = "renderer-software"))]
    let font_collection: Option<embed_images::SharedFontCollection> = None;

    embed_images::embed_images(
        doc,
        type_loader.compiler_config.embed_resources,
        type_loader.compiler_config.const_scale_factor.unwrap_or(1.),
        &type_loader.compiler_config.resource_url_mapper,
        font_collection.as_ref(),
        diag,
    )
    .await;

    #[cfg(feature = "bundle-translations")]
    if let Some(path) = &type_loader.compiler_config.translation_path_bundle {
        match crate::translations::TranslationsBuilder::load_translations(
            path,
            type_loader.compiler_config.translation_domain.as_deref().unwrap_or(""),
        ) {
            Ok(builder) => {
                doc.translation_builder = Some(builder);
            }
            Err(err) => {
                diag.push_error(
                    format!("Cannot load bundled translation: {err}"),
                    doc.node.as_ref().expect("Unexpected empty document"),
                );
            }
        }
    }

    match type_loader.compiler_config.embed_resources {
        #[cfg(feature = "renderer-software")]
        crate::EmbedResourcesKind::EmbedTextures => {
            let mut characters_seen = std::collections::HashSet::new();

            let sf = type_loader.compiler_config.const_scale_factor.unwrap_or(1.) as f64;

            // Include at least the default font sizes used in the MCU backend
            let mut font_pixel_sizes = vec![(12. * sf) as i16];
            use i_slint_common::sharedfontique::fontique;
            let mut font_weights = vec![fontique::FontWeight::NORMAL.value() as u16];
            doc.visit_all_used_components(|component| {
                embed_glyphs::collect_font_sizes_used(component, sf, &mut font_pixel_sizes);
                embed_glyphs::collect_font_weights_used(component, &mut font_weights);
                embed_glyphs::scan_string_literals(component, &mut characters_seen);
            });

            // This is not perfect, as this includes translations that may not be used.
            #[cfg(feature = "bundle-translations")]
            if let Some(translation_builder) = doc.translation_builder.as_ref() {
                translation_builder.collect_characters_seen(&mut characters_seen);
            }

            embed_glyphs::embed_glyphs(
                doc,
                &type_loader.compiler_config,
                font_pixel_sizes,
                font_weights,
                characters_seen,
                font_collection.as_ref().expect("EmbedTextures builds the shared font collection"),
                diag,
            );
        }
        _ => {
            // Create font registration calls for custom fonts, unless we're embedding pre-rendered glyphs
            collect_custom_fonts::collect_custom_fonts(
                doc,
                std::iter::once(&*doc).chain(type_loader.all_documents()),
                type_loader.compiler_config.embed_resources
                    == crate::EmbedResourcesKind::EmbedAllResources,
            );
        }
    };

    raw_type_loader
}

/// Run the passes on imported documents
pub fn run_import_passes(
    doc: &crate::object_tree::Document,
    type_loader: &crate::typeloader::TypeLoader,
    diag: &mut crate::diagnostics::BuildDiagnostics,
) {
    infer_aliases_types::resolve_aliases(doc, diag, &type_loader.symbol_counters);
    resolving::resolve_expressions(doc, type_loader, diag);
    purity_check::purity_check(doc, diag);
    focus_handling::replace_forward_focus_bindings_with_focus_functions(doc, diag);
    check_expressions::check_expressions(doc, diag);
    check_builtin_shadowing::check_builtin_shadowing(doc, diag);
    windows::warn_about_child_windows(doc, diag);
    unique_id::check_unique_id(doc, diag);
}
