//! Serializable UI scene records.
//!
//! A `UiSceneRecord` is a tree of plain-data nodes describing widgets that
//! [`apply_record`] can spawn into a world via the existing
//! [`UiTreeBuilder`](crate::ecs::ui::builder::UiTreeBuilder). The shape is
//! deliberately a flat `enum` (variant data, not a trait hierarchy) so apps
//! can ship UI as data: edit a `.json`, `apply_record(world, &record)`, get
//! back a `HashMap<String, Entity>` of named handles.
//!
//! This is the bridge to a future UI editor: any tree the editor produces is
//! a `UiSceneRecord` it serialises, and the running app rebuilds from it.
//!
//! Only a curated subset of widgets is represented today (label, heading,
//! separator, spacer, button, checkbox, toggle, slider, text input, dropdown,
//! docked / floating panels, plain flow / boundary nodes). Apps can extend
//! the enum with new variants or wrap the result in their own loader.

use std::collections::HashMap;

use nalgebra_glm::{Vec2, Vec4};
use serde::{Deserialize, Serialize};

use crate::ecs::text::components::{TextAlignment, VerticalAlignment};
use crate::ecs::ui::builder::UiTreeBuilder;
use crate::ecs::ui::layout_types::{FlowAlignment, FlowDirection, FlowLayout};
use crate::ecs::ui::types::{Anchor, Rect};
use crate::ecs::ui::units::{Ab, Rl};
use crate::ecs::world::World;

use crate::prelude::*;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct UiSceneRecord {
    pub root: Vec<UiSceneNode>,
}

#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
pub struct FlowSpec {
    pub direction: FlowDirection,
    pub padding: f32,
    pub spacing: f32,
    #[serde(default)]
    pub alignment: FlowAlignment,
    #[serde(default)]
    pub cross_alignment: FlowAlignment,
    #[serde(default)]
    pub wrap: bool,
}

#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum SizeSpec {
    Absolute(f32, f32),
    Relative(f32, f32),
    Mixed { rel: (f32, f32), abs: (f32, f32) },
}

impl SizeSpec {
    fn as_value(&self) -> crate::ecs::ui::units::UiValue<Vec2> {
        match *self {
            SizeSpec::Absolute(x, y) => Ab(Vec2::new(x, y)).into(),
            SizeSpec::Relative(x, y) => Rl(Vec2::new(x, y)).into(),
            SizeSpec::Mixed { rel, abs } => {
                Rl(Vec2::new(rel.0, rel.1)) + Ab(Vec2::new(abs.0, abs.1))
            }
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum UiSceneNode {
    Boundary {
        #[serde(default)]
        id: Option<String>,
        min: SizeSpec,
        max: SizeSpec,
        #[serde(default)]
        flow: Option<FlowSpec>,
        #[serde(default)]
        children: Vec<UiSceneNode>,
    },
    Window {
        #[serde(default)]
        id: Option<String>,
        position: SizeSpec,
        size: SizeSpec,
        anchor: Anchor,
        #[serde(default)]
        flow: Option<FlowSpec>,
        #[serde(default)]
        children: Vec<UiSceneNode>,
    },
    FlowChild {
        #[serde(default)]
        id: Option<String>,
        size: SizeSpec,
        #[serde(default)]
        flow: Option<FlowSpec>,
        #[serde(default)]
        children: Vec<UiSceneNode>,
    },
    DockedPanel {
        #[serde(default)]
        id: Option<String>,
        title: String,
        side: DockSide,
        size: f32,
        #[serde(default)]
        children: Vec<UiSceneNode>,
    },
    FloatingPanel {
        #[serde(default)]
        id: Option<String>,
        title: String,
        rect: [f32; 4],
        #[serde(default)]
        visible: Option<bool>,
        #[serde(default)]
        children: Vec<UiSceneNode>,
    },
    Heading {
        #[serde(default)]
        id: Option<String>,
        text: String,
    },
    Label {
        #[serde(default)]
        id: Option<String>,
        text: String,
        #[serde(default)]
        font_size: Option<f32>,
        #[serde(default)]
        color: Option<[f32; 4]>,
        #[serde(default)]
        alignment: Option<TextAlignment>,
        #[serde(default)]
        vertical_alignment: Option<VerticalAlignment>,
    },
    Separator,
    Spacer {
        height: f32,
    },
    Button {
        #[serde(default)]
        id: Option<String>,
        text: String,
    },
    Checkbox {
        #[serde(default)]
        id: Option<String>,
        label: String,
        #[serde(default)]
        checked: bool,
    },
    Toggle {
        #[serde(default)]
        id: Option<String>,
        label: String,
        #[serde(default)]
        value: bool,
    },
    Slider {
        #[serde(default)]
        id: Option<String>,
        label: String,
        min: f32,
        max: f32,
        value: f32,
    },
    TextInput {
        #[serde(default)]
        id: Option<String>,
        placeholder: String,
    },
    Dropdown {
        #[serde(default)]
        id: Option<String>,
        options: Vec<String>,
        #[serde(default)]
        selected: usize,
    },
    ScrollArea {
        #[serde(default)]
        id: Option<String>,
        #[serde(default = "default_padding")]
        padding: f32,
        #[serde(default = "default_spacing")]
        spacing: f32,
        #[serde(default)]
        children: Vec<UiSceneNode>,
    },
}

fn default_padding() -> f32 {
    8.0
}
fn default_spacing() -> f32 {
    4.0
}

#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum DockSide {
    Left,
    Right,
    Top,
    Bottom,
}

/// Apply a [`UiSceneRecord`] to `world`, spawning all described widgets.
/// Returns a map from each node's optional `id` to the spawned `Entity`,
/// which apps can use to wire poll/update behaviour.
pub fn apply_record(world: &mut World, record: &UiSceneRecord) -> HashMap<String, freecs::Entity> {
    let mut handles: HashMap<String, freecs::Entity> = HashMap::new();
    {
        let mut tree = UiTreeBuilder::new(world);
        for node in &record.root {
            apply_node(&mut tree, node, &mut handles);
        }
        tree.finish();
    }
    handles
}

fn apply_node(
    tree: &mut UiTreeBuilder,
    node: &UiSceneNode,
    handles: &mut HashMap<String, freecs::Entity>,
) {
    match node {
        UiSceneNode::Boundary {
            id,
            min,
            max,
            flow,
            children,
        } => {
            let entity = tree
                .add_node()
                .boundary(min.as_value(), max.as_value())
                .entity();
            apply_flow(tree, entity, flow);
            register(handles, id, entity);
            let mut scope = tree.children_of(entity);
            for child in children {
                apply_node(&mut scope, child, handles);
            }
        }
        UiSceneNode::Window {
            id,
            position,
            size,
            anchor,
            flow,
            children,
        } => {
            let entity = tree
                .add_node()
                .window(position.as_value(), size.as_value(), *anchor)
                .entity();
            apply_flow(tree, entity, flow);
            register(handles, id, entity);
            let mut scope = tree.children_of(entity);
            for child in children {
                apply_node(&mut scope, child, handles);
            }
        }
        UiSceneNode::FlowChild {
            id,
            size,
            flow,
            children,
        } => {
            let entity = tree.add_node().flow_child(size.as_value()).entity();
            apply_flow(tree, entity, flow);
            register(handles, id, entity);
            let mut scope = tree.children_of(entity);
            for child in children {
                apply_node(&mut scope, child, handles);
            }
        }
        UiSceneNode::DockedPanel {
            id,
            title,
            side,
            size,
            children,
        } => {
            let panel_id = id.as_deref().unwrap_or(title);
            let entity = match side {
                DockSide::Left => tree.add_docked_panel_left(panel_id, title, *size),
                DockSide::Right => tree.add_docked_panel_right(panel_id, title, *size),
                DockSide::Top => tree.add_docked_panel_top(panel_id, title, *size),
                DockSide::Bottom => tree.add_docked_panel_bottom(panel_id, title, *size),
            };
            register(handles, id, entity);
            let mut scope = tree.children_of_widget(entity);
            for child in children {
                apply_node(&mut scope, child, handles);
            }
        }
        UiSceneNode::FloatingPanel {
            id,
            title,
            rect,
            visible,
            children,
        } => {
            let panel_id = id.as_deref().unwrap_or(title);
            let entity = tree.add_floating_panel(
                panel_id,
                title,
                Rect {
                    min: Vec2::new(rect[0], rect[1]),
                    max: Vec2::new(rect[2], rect[3]),
                },
            );
            if let Some(visible) = visible {
                ui_set_visible(tree.world_mut(), entity, *visible);
            }
            register(handles, id, entity);
            let mut scope = tree.children_of_widget(entity);
            for child in children {
                apply_node(&mut scope, child, handles);
            }
        }
        UiSceneNode::Heading { id, text } => {
            let entity = tree.add_heading(text);
            register(handles, id, entity);
        }
        UiSceneNode::Label {
            id,
            text,
            font_size,
            color,
            alignment,
            vertical_alignment,
        } => {
            let theme_font = tree
                .world_mut()
                .resources
                .retained_ui
                .theme_state
                .active_theme()
                .font_size;
            let mut builder = tree
                .add_node()
                .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 18.0)))
                .with_text(text, font_size.unwrap_or(theme_font))
                .with_text_alignment(
                    alignment.unwrap_or(TextAlignment::Left),
                    vertical_alignment.unwrap_or(VerticalAlignment::Middle),
                );
            if let Some(rgba) = color {
                builder = builder.color_raw::<crate::ecs::ui::state::UiBase>(Vec4::new(
                    rgba[0], rgba[1], rgba[2], rgba[3],
                ));
            }
            let entity = builder.entity();
            register(handles, id, entity);
        }
        UiSceneNode::Separator => {
            tree.add_separator();
        }
        UiSceneNode::Spacer { height } => {
            tree.add_node()
                .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, *height)))
                .entity();
        }
        UiSceneNode::Button { id, text } => {
            let entity = tree.add_button(text);
            register(handles, id, entity);
        }
        UiSceneNode::Checkbox { id, label, checked } => {
            let entity = tree.add_checkbox(label, *checked);
            register(handles, id, entity);
        }
        UiSceneNode::Toggle { id, label, value } => {
            let _ = label;
            let entity = tree.add_toggle(*value);
            register(handles, id, entity);
        }
        UiSceneNode::Slider {
            id,
            label,
            min,
            max,
            value,
        } => {
            let _ = label;
            let entity = tree.add_slider(*min, *max, *value);
            register(handles, id, entity);
        }
        UiSceneNode::TextInput { id, placeholder } => {
            let entity = tree.add_text_input(placeholder);
            register(handles, id, entity);
        }
        UiSceneNode::Dropdown {
            id,
            options,
            selected,
        } => {
            let option_refs: Vec<&str> = options.iter().map(String::as_str).collect();
            let entity = tree.add_dropdown(&option_refs, *selected);
            register(handles, id, entity);
        }
        UiSceneNode::ScrollArea {
            id,
            padding,
            spacing,
            children,
        } => {
            let entity = tree.add_scroll_area_fill(*padding, *spacing);
            register(handles, id, entity);
            let mut scope = tree.children_of_widget(entity);
            for child in children {
                apply_node(&mut scope, child, handles);
            }
        }
    }
}

fn apply_flow(tree: &mut UiTreeBuilder, entity: freecs::Entity, flow: &Option<FlowSpec>) {
    let Some(spec) = flow else {
        return;
    };
    if let Some(node) = tree.world_mut().ui.get_ui_layout_node_mut(entity) {
        node.flow_layout = Some(FlowLayout {
            direction: spec.direction,
            padding: spec.padding,
            spacing: spec.spacing,
            alignment: spec.alignment,
            cross_alignment: spec.cross_alignment,
            wrap: spec.wrap,
        });
    }
}

fn register(
    handles: &mut HashMap<String, freecs::Entity>,
    id: &Option<String>,
    entity: freecs::Entity,
) {
    if let Some(id) = id {
        handles.insert(id.clone(), entity);
    }
}
