use nalgebra_glm::{Vec2, Vec4};

use crate::ecs::ui::builder::UiTreeBuilder;
use crate::ecs::ui::components::{DataGridColumn, DragValueConfig, SliderConfig, TextSpan};
use crate::ecs::ui::composite::CompositeWidget;
use crate::ecs::ui::resources::{FromPropertyValue, IntoPropertyValue};
use crate::ecs::ui::theme::UiTheme;
use crate::ecs::ui::types::Rect;

pub struct Ui<'t, 'w> {
    tree: &'t mut UiTreeBuilder<'w>,
    parent: freecs::Entity,
    prefix: Option<String>,
}

impl<'t, 'w> Ui<'t, 'w> {
    fn scoped<R>(&mut self, f: impl FnOnce(&mut UiTreeBuilder<'w>) -> R) -> R {
        self.tree.push_parent(self.parent);
        let result = f(&mut *self.tree);
        self.tree.pop_parent();
        result
    }

    fn container(
        &mut self,
        add_fn: impl FnOnce(&mut UiTreeBuilder<'w>) -> freecs::Entity,
        f: impl FnOnce(&mut Ui<'_, 'w>),
    ) -> freecs::Entity {
        let entity = self.scoped(add_fn);
        let content = self
            .tree
            .world_mut()
            .ui_widget_content(entity)
            .unwrap_or(entity);
        let mut child_ui = Ui {
            tree: &mut *self.tree,
            parent: content,
            prefix: self.prefix.clone(),
        };
        f(&mut child_ui);
        entity
    }

    fn full_name(&self, name: &str) -> String {
        match &self.prefix {
            Some(prefix) => format!("{prefix}{name}"),
            None => name.to_string(),
        }
    }

    fn register_named<T: IntoPropertyValue>(
        &mut self,
        name: &str,
        entity: freecs::Entity,
        initial: T,
    ) {
        if !name.is_empty() {
            let full = self.full_name(name);
            let property_id = self.tree.world_mut().ui_create_property(entity, initial);
            self.tree
                .world_mut()
                .resources
                .retained_ui
                .register_named_property(&full, property_id);
        }
    }

    pub fn tree(&mut self) -> &mut UiTreeBuilder<'w> {
        self.tree
    }

    pub fn parent(&self) -> freecs::Entity {
        self.parent
    }

    pub fn world_mut(&mut self) -> &mut crate::ecs::world::World {
        self.tree.world_mut()
    }

    pub fn theme(&self) -> &UiTheme {
        self.tree.active_theme()
    }

    pub fn set_flex_grow(&mut self, entity: freecs::Entity, weight: f32) {
        if let Some(node) = self.tree.world_mut().get_ui_layout_node_mut(entity) {
            node.flex_grow = Some(weight);
        }
    }

    pub fn set_visible(&mut self, entity: freecs::Entity, visible: bool) {
        self.tree.world_mut().ui_set_visible(entity, visible);
    }

    pub fn set_disabled(&mut self, entity: freecs::Entity, disabled: bool) {
        self.tree.world_mut().ui_set_disabled(entity, disabled);
    }

    pub fn set_disabled_recursive(&mut self, entity: freecs::Entity, disabled: bool) {
        self.tree
            .world_mut()
            .ui_set_disabled_recursive(entity, disabled);
    }

    pub fn react<T, F>(&mut self, name: &str, handler: F)
    where
        T: FromPropertyValue + 'static,
        F: FnMut(T, &mut crate::ecs::world::World) + 'static,
    {
        let full = self.full_name(name);
        self.tree.world_mut().ui_react::<T, F>(&full, handler);
    }

    pub fn scope(&mut self, name: &str, f: impl FnOnce(&mut Ui<'_, 'w>)) {
        let new_prefix = match &self.prefix {
            Some(prefix) => format!("{prefix}{name}."),
            None => format!("{name}."),
        };
        let mut child_ui = Ui {
            tree: &mut *self.tree,
            parent: self.parent,
            prefix: Some(new_prefix),
        };
        f(&mut child_ui);
    }

    pub fn react_clicked<F>(&mut self, entity: freecs::Entity, handler: F)
    where
        F: FnMut(&mut crate::ecs::world::World) + 'static,
    {
        self.tree.world_mut().ui_react_clicked(entity, handler);
    }

    pub fn react_submitted<F>(&mut self, entity: freecs::Entity, handler: F)
    where
        F: FnMut(String, &mut crate::ecs::world::World) + 'static,
    {
        self.tree.world_mut().ui_react_submitted(entity, handler);
    }

    pub fn react_confirmed<F>(&mut self, entity: freecs::Entity, handler: F)
    where
        F: FnMut(bool, &mut crate::ecs::world::World) + 'static,
    {
        self.tree.world_mut().ui_react_confirmed(entity, handler);
    }

    pub fn react_menu_selected<F>(&mut self, entity: freecs::Entity, handler: F)
    where
        F: FnMut(usize, &mut crate::ecs::world::World) + 'static,
    {
        self.tree
            .world_mut()
            .ui_react_menu_selected(entity, handler);
    }

    pub fn react_command<F>(&mut self, entity: freecs::Entity, handler: F)
    where
        F: FnMut(usize, &mut crate::ecs::world::World) + 'static,
    {
        self.tree.world_mut().ui_react_command(entity, handler);
    }

    pub fn react_tree_selected<F>(&mut self, entity: freecs::Entity, handler: F)
    where
        F: FnMut(freecs::Entity, &mut crate::ecs::world::World) + 'static,
    {
        self.tree
            .world_mut()
            .ui_react_tree_selected(entity, handler);
    }

    pub fn react_tree_context_menu<F>(&mut self, entity: freecs::Entity, handler: F)
    where
        F: FnMut(freecs::Entity, nalgebra_glm::Vec2, &mut crate::ecs::world::World) + 'static,
    {
        self.tree
            .world_mut()
            .ui_react_tree_context_menu(entity, handler);
    }

    pub fn cleanup_entity(&mut self, entity: freecs::Entity) {
        self.tree.world_mut().ui_cleanup_entity(entity);
    }

    pub fn remove_property(&mut self, name: &str) {
        let full = self.full_name(name);
        self.tree.world_mut().ui_remove_property(&full);
    }

    pub fn react_multi_select_changed<F>(&mut self, entity: freecs::Entity, handler: F)
    where
        F: FnMut(Vec<usize>, &mut crate::ecs::world::World) + 'static,
    {
        self.tree
            .world_mut()
            .ui_react_multi_select_changed(entity, handler);
    }

    pub fn react_date_changed<F>(&mut self, entity: freecs::Entity, handler: F)
    where
        F: FnMut(i32, u32, u32, &mut crate::ecs::world::World) + 'static,
    {
        self.tree.world_mut().ui_react_date_changed(entity, handler);
    }

    pub fn react_changed<F>(&mut self, entity: freecs::Entity, handler: F)
    where
        F: FnMut(&mut crate::ecs::world::World) + 'static,
    {
        self.tree.world_mut().ui_react_changed(entity, handler);
    }

    pub fn react_any<F>(&mut self, names: &[&str], handler: F)
    where
        F: FnMut(&mut crate::ecs::world::World) + 'static,
    {
        let full_names: Vec<String> = names.iter().map(|name| self.full_name(name)).collect();
        let refs: Vec<&str> = full_names.iter().map(|s| s.as_str()).collect();
        self.tree.world_mut().ui_react_any(&refs, handler);
    }

    pub fn label(&mut self, text: &str) -> freecs::Entity {
        self.scoped(|tree| tree.add_label(text))
    }

    pub fn label_with_slot(&mut self, slot: usize) -> freecs::Entity {
        self.scoped(|tree| tree.add_label_with_slot(slot))
    }

    pub fn label_colored(&mut self, text: &str, color: Vec4) -> freecs::Entity {
        self.scoped(|tree| tree.add_label_colored(text, color))
    }

    pub fn heading(&mut self, text: &str) -> freecs::Entity {
        self.scoped(|tree| tree.add_heading(text))
    }

    pub fn separator(&mut self) -> freecs::Entity {
        self.scoped(|tree| tree.add_separator())
    }

    pub fn spacing(&mut self, amount: f32) -> freecs::Entity {
        self.scoped(|tree| tree.add_spacing(amount))
    }

    pub fn spring(&mut self) -> freecs::Entity {
        self.scoped(|tree| tree.add_spring())
    }

    pub fn button(&mut self, text: &str) -> freecs::Entity {
        self.scoped(|tree| tree.add_button(text))
    }

    pub fn button_colored(&mut self, text: &str, color: Vec4) -> freecs::Entity {
        self.scoped(|tree| tree.add_button_colored(text, color))
    }

    pub fn icon_button(
        &mut self,
        texture_index: u32,
        icon_size: Vec2,
        text: &str,
    ) -> freecs::Entity {
        self.scoped(|tree| tree.add_icon_button(texture_index, icon_size, text))
    }

    pub fn button_rich(&mut self, spans: &[TextSpan]) -> freecs::Entity {
        self.scoped(|tree| tree.add_button_rich(spans))
    }

    pub fn slider(&mut self, name: &str, min: f32, max: f32, initial: f32) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_slider(min, max, initial));
        self.register_named(name, entity, initial);
        entity
    }

    pub fn slider_logarithmic(
        &mut self,
        name: &str,
        min: f32,
        max: f32,
        initial: f32,
    ) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_slider_logarithmic(min, max, initial));
        self.register_named(name, entity, initial);
        entity
    }

    pub fn slider_configured(&mut self, name: &str, config: SliderConfig<'_>) -> freecs::Entity {
        let initial = config.initial;
        let entity = self.scoped(|tree| tree.add_slider_configured(config));
        self.register_named(name, entity, initial);
        entity
    }

    pub fn canvas(&mut self, size: Vec2) -> freecs::Entity {
        self.scoped(|tree| tree.add_canvas(size))
    }

    pub fn toggle(&mut self, name: &str, initial: bool) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_toggle(initial));
        self.register_named(name, entity, initial);
        entity
    }

    pub fn checkbox(&mut self, name: &str, label: &str, initial: bool) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_checkbox(label, initial));
        self.register_named(name, entity, initial);
        entity
    }

    pub fn radio(&mut self, label: &str, group_id: u32, option_index: usize) -> freecs::Entity {
        self.scoped(|tree| tree.add_radio(label, group_id, option_index))
    }

    pub fn radio_group(&mut self, name: &str, group_id: u32) {
        if name.is_empty() {
            return;
        }
        let members = self
            .tree
            .world_mut()
            .resources
            .retained_ui
            .radio_groups
            .get(&group_id)
            .cloned()
            .unwrap_or_default();
        let Some(&first) = members.first() else {
            return;
        };
        let initial = self
            .tree
            .world_mut()
            .ui_radio_group_value(group_id)
            .unwrap_or(0);
        self.register_named(name, first, initial);
    }

    pub fn progress_bar(&mut self, name: &str, initial: f32) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_progress_bar(initial));
        self.register_named(name, entity, initial);
        entity
    }

    pub fn text_input(&mut self, name: &str, placeholder: &str) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_text_input(placeholder));
        self.register_named(name, entity, String::new());
        entity
    }

    pub fn text_input_max_length(
        &mut self,
        name: &str,
        placeholder: &str,
        max_length: usize,
    ) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_text_input_max_length(placeholder, max_length));
        self.register_named(name, entity, String::new());
        entity
    }

    pub fn text_area(&mut self, name: &str, placeholder: &str, rows: usize) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_text_area(placeholder, rows));
        self.register_named(name, entity, String::new());
        entity
    }

    pub fn text_area_with_value(
        &mut self,
        name: &str,
        placeholder: &str,
        rows: usize,
        initial_text: &str,
    ) -> freecs::Entity {
        let entity =
            self.scoped(|tree| tree.add_text_area_with_value(placeholder, rows, initial_text));
        self.register_named(name, entity, initial_text.to_string());
        entity
    }

    #[cfg(feature = "syntax_highlighting")]
    pub fn text_area_with_syntax(
        &mut self,
        name: &str,
        placeholder: &str,
        rows: usize,
        language: &str,
    ) -> freecs::Entity {
        let entity =
            self.scoped(|tree| tree.add_text_area_with_syntax(placeholder, rows, language));
        self.register_named(name, entity, String::new());
        entity
    }

    #[cfg(feature = "syntax_highlighting")]
    pub fn text_area_with_syntax_and_value(
        &mut self,
        name: &str,
        placeholder: &str,
        rows: usize,
        language: &str,
        initial_text: &str,
    ) -> freecs::Entity {
        let entity = self.scoped(|tree| {
            tree.add_text_area_with_syntax_and_value(placeholder, rows, language, initial_text)
        });
        self.register_named(name, entity, initial_text.to_string());
        entity
    }

    pub fn dropdown(&mut self, name: &str, options: &[&str], initial: usize) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_dropdown(options, initial));
        self.register_named(name, entity, initial);
        entity
    }

    pub fn dropdown_searchable(
        &mut self,
        name: &str,
        options: &[&str],
        initial: usize,
    ) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_dropdown_searchable(options, initial));
        self.register_named(name, entity, initial);
        entity
    }

    pub fn multi_select(&mut self, options: &[&str]) -> freecs::Entity {
        self.scoped(|tree| tree.add_multi_select(options))
    }

    pub fn date_picker(&mut self, year: i32, month: u32, day: u32) -> freecs::Entity {
        self.scoped(|tree| tree.add_date_picker(year, month, day))
    }

    pub fn menu(&mut self, label: &str, items: &[&str]) -> freecs::Entity {
        self.scoped(|tree| tree.add_menu(label, items))
    }

    pub fn color_picker(&mut self, name: &str, initial_color: Vec4) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_color_picker(initial_color));
        self.register_named(name, entity, initial_color);
        entity
    }

    pub fn color_picker_hsv(&mut self, name: &str, initial_color: Vec4) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_color_picker_hsv(initial_color));
        self.register_named(name, entity, initial_color);
        entity
    }

    pub fn splitter(
        &mut self,
        name: &str,
        direction: crate::ecs::ui::components::SplitDirection,
        initial_ratio: f32,
    ) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_splitter(direction, initial_ratio));
        self.register_named(name, entity, initial_ratio);
        entity
    }

    pub fn range_slider(
        &mut self,
        name: &str,
        min: f32,
        max: f32,
        initial_low: f32,
        initial_high: f32,
    ) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_range_slider(min, max, initial_low, initial_high));
        self.register_named(name, entity, Vec4::new(initial_low, initial_high, 0.0, 0.0));
        entity
    }

    pub fn selectable_label(
        &mut self,
        name: &str,
        text: &str,
        group_id: Option<u32>,
    ) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_selectable_label(text, group_id));
        self.register_named(name, entity, false);
        entity
    }

    pub fn drag_value(&mut self, name: &str, min: f32, max: f32, initial: f32) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_drag_value(min, max, initial));
        self.register_named(name, entity, initial);
        entity
    }

    pub fn drag_value_configured(
        &mut self,
        name: &str,
        config: DragValueConfig<'_>,
    ) -> freecs::Entity {
        let initial = config.initial;
        let entity = self.scoped(|tree| tree.add_drag_value_configured(config));
        self.register_named(name, entity, initial);
        entity
    }

    pub fn context_menu(&mut self, items: &[(&str, Option<&str>)]) -> freecs::Entity {
        self.scoped(|tree| tree.add_context_menu(items))
    }

    pub fn context_menu_from_builder(
        &mut self,
        builder: crate::ecs::ui::widgets::ContextMenuBuilder,
    ) -> freecs::Entity {
        self.scoped(|tree| tree.add_context_menu_from_builder(builder))
    }

    pub fn tab_bar(&mut self, name: &str, labels: &[&str], initial: usize) -> freecs::Entity {
        let entity = self.scoped(|tree| tree.add_tab_bar(labels, initial));
        self.register_named(name, entity, initial);
        entity
    }

    pub fn image_node(&mut self, texture_index: u32, size: Vec2) -> freecs::Entity {
        self.scoped(|tree| tree.add_image_node(texture_index, size))
    }

    pub fn theme_dropdown(&mut self) -> freecs::Entity {
        self.scoped(|tree| tree.add_theme_dropdown())
    }

    pub fn rich_text(&mut self, spans: &[TextSpan]) -> freecs::Entity {
        self.scoped(|tree| tree.add_rich_text(spans))
    }

    pub fn data_grid(&mut self, columns: &[DataGridColumn], pool_size: usize) -> freecs::Entity {
        self.scoped(|tree| tree.add_data_grid(columns, pool_size))
    }

    pub fn table(&mut self, headers: &[&str], widths: &[f32]) -> freecs::Entity {
        self.scoped(|tree| tree.add_table(headers, widths))
    }

    pub fn confirm_dialog(&mut self, title: &str, message: &str) -> freecs::Entity {
        self.scoped(|tree| tree.add_confirm_dialog(title, message))
    }

    pub fn command_palette(&mut self, pool_size: usize) -> freecs::Entity {
        self.scoped(|tree| tree.add_command_palette(pool_size))
    }

    pub fn property_grid(&mut self, label_width: f32) -> freecs::Entity {
        self.scoped(|tree| tree.add_property_grid(label_width))
    }

    pub fn property_row(
        &mut self,
        grid: freecs::Entity,
        parent: freecs::Entity,
        label: &str,
    ) -> freecs::Entity {
        self.tree.add_property_row(grid, parent, label)
    }

    pub fn property_section(&mut self, parent: freecs::Entity, label: &str) -> freecs::Entity {
        self.tree.add_property_section(parent, label)
    }

    pub fn tree_node(
        &mut self,
        tree_entity: freecs::Entity,
        parent_container: freecs::Entity,
        label: &str,
        depth: usize,
        user_data: u64,
    ) -> freecs::Entity {
        self.tree
            .add_tree_node(tree_entity, parent_container, label, depth, user_data)
    }

    pub fn tree_node_lazy(
        &mut self,
        tree_entity: freecs::Entity,
        parent_container: freecs::Entity,
        label: &str,
        depth: usize,
        user_data: u64,
    ) -> freecs::Entity {
        self.tree
            .add_tree_node_lazy(tree_entity, parent_container, label, depth, user_data)
    }

    pub fn composite<T: CompositeWidget>(&mut self) -> freecs::Entity {
        self.scoped(|tree| tree.add_composite::<T>())
    }

    pub fn row(&mut self, f: impl FnOnce(&mut Ui<'_, 'w>)) -> freecs::Entity {
        self.container(|tree| tree.add_row(), f)
    }

    pub fn column(&mut self, f: impl FnOnce(&mut Ui<'_, 'w>)) -> freecs::Entity {
        self.container(|tree| tree.add_column(), f)
    }

    pub fn collapsing_header(
        &mut self,
        name: &str,
        label: &str,
        default_open: bool,
        f: impl FnOnce(&mut Ui<'_, 'w>),
    ) -> freecs::Entity {
        let entity = self.container(|tree| tree.add_collapsing_header(label, default_open), f);
        self.register_named(name, entity, default_open);
        entity
    }

    pub fn scroll_area(&mut self, size: Vec2, f: impl FnOnce(&mut Ui<'_, 'w>)) -> freecs::Entity {
        self.container(|tree| tree.add_scroll_area(size), f)
    }

    pub fn scroll_area_fill(
        &mut self,
        padding: f32,
        spacing: f32,
        f: impl FnOnce(&mut Ui<'_, 'w>),
    ) -> freecs::Entity {
        self.container(|tree| tree.add_scroll_area_fill(padding, spacing), f)
    }

    pub fn floating_panel(
        &mut self,
        title: &str,
        rect: Rect,
        f: impl FnOnce(&mut Ui<'_, 'w>),
    ) -> freecs::Entity {
        self.container(|tree| tree.add_floating_panel(title, rect), f)
    }

    pub fn docked_panel_left(
        &mut self,
        title: &str,
        default_width: f32,
        f: impl FnOnce(&mut Ui<'_, 'w>),
    ) -> freecs::Entity {
        self.container(|tree| tree.add_docked_panel_left(title, default_width), f)
    }

    pub fn docked_panel_right(
        &mut self,
        title: &str,
        default_width: f32,
        f: impl FnOnce(&mut Ui<'_, 'w>),
    ) -> freecs::Entity {
        self.container(|tree| tree.add_docked_panel_right(title, default_width), f)
    }

    pub fn docked_panel_top(
        &mut self,
        title: &str,
        default_height: f32,
        f: impl FnOnce(&mut Ui<'_, 'w>),
    ) -> freecs::Entity {
        self.container(|tree| tree.add_docked_panel_top(title, default_height), f)
    }

    pub fn docked_panel_bottom(
        &mut self,
        title: &str,
        default_height: f32,
        f: impl FnOnce(&mut Ui<'_, 'w>),
    ) -> freecs::Entity {
        self.container(
            |tree| tree.add_docked_panel_bottom(title, default_height),
            f,
        )
    }

    pub fn modal_dialog(
        &mut self,
        title: &str,
        width: f32,
        height: f32,
        f: impl FnOnce(&mut Ui<'_, 'w>),
    ) -> freecs::Entity {
        self.container(|tree| tree.add_modal_dialog(title, width, height), f)
    }

    pub fn tree_view(
        &mut self,
        multi_select: bool,
        f: impl FnOnce(&mut Ui<'_, 'w>),
    ) -> freecs::Entity {
        self.container(|tree| tree.add_tree_view(multi_select), f)
    }

    pub fn tile_container(&mut self, size: Vec2) -> freecs::Entity {
        self.scoped(|tree| tree.add_tile_container(size))
    }

    pub fn build_tiles(
        &mut self,
        container: freecs::Entity,
        f: impl FnOnce(&mut crate::ecs::ui::widgets::TileBuilder<'_, 'w>),
    ) {
        self.tree.build_tiles(container, f);
    }

    pub fn enabled(&mut self, enabled: bool, f: impl FnOnce(&mut Ui<'_, 'w>)) -> freecs::Entity {
        let container = self.scoped(|tree| tree.add_column());
        let content = self
            .tree
            .world_mut()
            .ui_widget_content(container)
            .unwrap_or(container);
        let mut child_ui = Ui {
            tree: &mut *self.tree,
            parent: content,
            prefix: self.prefix.clone(),
        };
        f(&mut child_ui);
        if !enabled {
            self.tree
                .world_mut()
                .ui_set_disabled_recursive(container, true);
        }
        container
    }
}

impl<'w> UiTreeBuilder<'w> {
    pub fn build_ui(&mut self, parent: freecs::Entity, f: impl FnOnce(&mut Ui<'_, 'w>)) {
        let mut ui = Ui {
            tree: self,
            parent,
            prefix: None,
        };
        f(&mut ui);
    }
}
