use crate::ecs::{EditorWorld, PlacementPhase, PlacementShape};
use crate::systems::input;
use crate::systems::retained_ui::UiHandles;
use nightshade::ecs::input::resources::MouseState;
use nightshade::ecs::material::components::{AlphaMode, Material};
use nightshade::ecs::world::VISIBILITY;
use nightshade::prelude::*;

const DRAG_THRESHOLD_PIXELS: f32 = 4.0;

#[derive(Clone, Copy)]
pub enum GeneratorKind {
    Building,
    Room,
    Tower,
    Stairs,
    Columns,
    Perimeter,
    CityBlock,
    Courtyard,
    Wfc,
    WfcCity,
}

const GENERATORS: &[(GeneratorKind, &str)] = &[
    (GeneratorKind::Building, "Building"),
    (GeneratorKind::Room, "Room"),
    (GeneratorKind::Tower, "Tower"),
    (GeneratorKind::Stairs, "Stairs"),
    (GeneratorKind::Columns, "Columns"),
    (GeneratorKind::Perimeter, "Walls"),
    (GeneratorKind::CityBlock, "City block"),
    (GeneratorKind::Courtyard, "Courtyard"),
    (GeneratorKind::Wfc, "WFC level"),
    (GeneratorKind::WfcCity, "WFC city"),
];

#[derive(Default, Clone, Copy)]
pub struct GenerationHandles {
    pub footprint_x: Entity,
    pub footprint_z: Entity,
    pub stories: Entity,
    pub story_height: Entity,
    pub wall_thickness: Entity,
    pub window_spacing: Entity,
    pub seed: Entity,
    pub variation: Entity,
}

const SHAPES: &[(PlacementShape, &str)] = &[
    (PlacementShape::Cube, "Cube"),
    (PlacementShape::Plane, "Plane"),
    (PlacementShape::Cylinder, "Cylinder"),
    (PlacementShape::Cone, "Cone"),
    (PlacementShape::Sphere, "Sphere"),
    (PlacementShape::Torus, "Torus"),
];

const BLOCK_PRESETS: &[(&str, PlacementShape, [f32; 3])] = &[
    ("Floor 4x4", PlacementShape::Cube, [4.0, 0.2, 4.0]),
    ("Floor 2x2", PlacementShape::Cube, [2.0, 0.2, 2.0]),
    ("Wall 4x3", PlacementShape::Cube, [4.0, 3.0, 0.3]),
    ("Half wall", PlacementShape::Cube, [4.0, 1.5, 0.3]),
    ("Pillar", PlacementShape::Cube, [0.6, 3.0, 0.6]),
    ("Beam", PlacementShape::Cube, [4.0, 0.4, 0.4]),
    ("Cube 1m", PlacementShape::Cube, [1.0, 1.0, 1.0]),
    ("Platform", PlacementShape::Cube, [4.0, 0.4, 4.0]),
    ("Step", PlacementShape::Cube, [2.0, 0.4, 1.0]),
    ("Crate", PlacementShape::Cube, [1.0, 1.0, 1.0]),
    ("Column", PlacementShape::Cylinder, [0.6, 3.0, 0.6]),
    ("Cone marker", PlacementShape::Cone, [1.0, 1.5, 1.0]),
];

#[derive(Default, Clone)]
pub struct CreateShapeHandles {
    pub place_button: Entity,
    pub push_pull_button: Entity,
    pub play_button: Entity,
    pub spawn_button: Entity,
    pub status_label: Entity,
    pub shape_buttons: Vec<(Entity, PlacementShape)>,
    pub size_x: Entity,
    pub size_y: Entity,
    pub size_z: Entity,
    pub grid_size: Entity,
    pub grid_presets: Vec<(Entity, f32)>,
    pub orient_checkbox: Entity,
    pub preset_buttons: Vec<(Entity, usize)>,
    pub material_swatches: Vec<(Entity, usize)>,
    pub generation: GenerationHandles,
    pub generate_buttons: Vec<(Entity, GeneratorKind)>,
    pub apply_material_button: Entity,
    pub snap_grid_button: Entity,
    pub mark_brush_button: Entity,
    pub upgrade_button: Entity,
    pub show_final_button: Entity,
}

pub fn build_in(tree: &mut UiTreeBuilder, content: Entity) -> CreateShapeHandles {
    let theme = tree
        .world_mut()
        .resources
        .retained_ui
        .theme_state
        .active_theme();
    let font = theme.font_size;

    let mut place_button = Entity::default();
    let mut push_pull_button = Entity::default();
    let mut play_button = Entity::default();
    let mut spawn_button = Entity::default();
    let mut status_label = Entity::default();
    let mut shape_buttons: Vec<(Entity, PlacementShape)> = Vec::with_capacity(SHAPES.len());
    let mut size_x = Entity::default();
    let mut size_y = Entity::default();
    let mut size_z = Entity::default();
    let mut grid_size = Entity::default();
    let mut grid_presets: Vec<(Entity, f32)> = Vec::new();
    let mut orient_checkbox = Entity::default();
    let mut preset_buttons: Vec<(Entity, usize)> = Vec::with_capacity(BLOCK_PRESETS.len());
    let mut material_swatches: Vec<(Entity, usize)> =
        Vec::with_capacity(super::build::palette_count());
    let mut generation = GenerationHandles::default();
    let mut generate_buttons: Vec<(Entity, GeneratorKind)> = Vec::with_capacity(GENERATORS.len());
    let mut apply_material_button = Entity::default();
    let mut snap_grid_button = Entity::default();
    let mut mark_brush_button = Entity::default();
    let mut upgrade_button = Entity::default();
    let mut show_final_button = Entity::default();

    let scroll = tree.in_parent(content, |tree| tree.add_scroll_area_fill(4.0, 6.0));
    let panel_root = widget::<UiScrollAreaData>(tree.world_mut(), scroll)
        .map(|data| data.content_entity)
        .unwrap_or(scroll);
    tree.in_parent(panel_root, |tree| {
        place_button = tree.add_button("Start placing");
        push_pull_button = tree.add_button("Push/Pull faces");
        spawn_button = tree.add_button("Add player spawn");
        play_button = tree.add_button("Play (first person)");
        status_label = tree
            .add_node()
            .size(100.pct(), (18.0).px())
            .with_text("Click to stamp, or shift+drag to draw a block", font * 0.8)
            .text_left()
            .color_raw::<UiBase>(vec4(0.7, 0.7, 0.75, 1.0))
            .entity();

        section_label(tree, "Shape", font);
        let shape_row = tree
            .add_node()
            .fill_width()
            .auto_size(AutoSizeMode::Height)
            .flow(FlowDirection::Horizontal, 0.0, 4.0)
            .flow_wrap()
            .entity();
        tree.in_parent(shape_row, |tree| {
            for (shape, label) in SHAPES {
                let tooltip = format!("Draw blocks as {label}");
                let button = compact_button(tree, label, &tooltip, 64.0, font);
                shape_buttons.push((button, *shape));
            }
        });

        let grid = tree.add_property_grid(120.0);
        let section = tree.add_property_section(grid, "Size (meters)");
        size_x = size_drag(tree, grid, section, "X");
        size_y = size_drag(tree, grid, section, "Y");
        size_z = size_drag(tree, grid, section, "Z");

        let grid_section = tree.add_property_section(grid, "Grid");
        let grid_row = tree.add_property_row(grid, grid_section, "Snap");
        tree.in_parent(grid_row, |tree| {
            grid_size = tree.add_drag_value_configured(
                DragValueConfig::new(0.01, f32::MAX, 1.0)
                    .speed(0.05)
                    .precision(2),
            );
        });
        let preset_row = tree
            .add_node()
            .size(100.pct(), (24.0).px())
            .flow(FlowDirection::Horizontal, 0.0, 4.0)
            .entity();
        tree.in_parent(preset_row, |tree| {
            for value in [0.25_f32, 0.5, 1.0, 2.0] {
                let label = format!("{value}m");
                let tooltip = format!("Snap to a {value} meter grid");
                let button = compact_button(tree, &label, &tooltip, 44.0, font);
                grid_presets.push((button, value));
            }
        });

        orient_checkbox = tree.add_checkbox("Orient to surface", false);

        let library_header = tree.add_collapsing_header("Block library", false);
        let list_root = widget::<UiCollapsingHeaderData>(tree.world_mut(), library_header)
            .map(|data| data.content_entity)
            .unwrap_or(library_header);
        tree.in_parent(list_root, |tree| {
            for (index, (label, _, size)) in BLOCK_PRESETS.iter().enumerate() {
                let card = tree
                    .add_node()
                    .size(100.pct(), (38.0).px())
                    .with_rect(4.0, 1.0, vec4(0.0, 0.0, 0.0, 0.0))
                    .with_theme_border_color(ThemeColor::Border)
                    .with_theme_color::<UiBase>(ThemeColor::Background)
                    .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
                    .with_interaction()
                    .with_cursor_icon(winit::window::CursorIcon::Pointer)
                    .flow(FlowDirection::Horizontal, 6.0, 8.0)
                    .entity();
                tree.in_parent(card, |tree| {
                    let canvas = tree.add_canvas(vec2(30.0, 30.0));
                    draw_iso_box(tree.world_mut(), canvas, *size, 30.0);
                    tree.add_node()
                        .flex_grow(1.0)
                        .size((0.0).px(), (30.0).px())
                        .with_text(label, font * 0.85)
                        .text_left()
                        .fg(ThemeColor::Text)
                        .entity();
                });
                preset_buttons.push((card, index));
            }
        });

        section_label(tree, "Materials", font);
        let mat_row = tree
            .add_node()
            .fill_width()
            .auto_size(AutoSizeMode::Height)
            .flow(FlowDirection::Horizontal, 0.0, 4.0)
            .flow_wrap()
            .entity();
        tree.in_parent(mat_row, |tree| {
            for index in 0..super::build::palette_count() {
                let color = super::build::palette_color(index);
                let chip = tree
                    .add_node()
                    .size((26.0).px(), (26.0).px())
                    .with_rect(4.0, 1.0, vec4(0.0, 0.0, 0.0, 0.0))
                    .with_theme_border_color(ThemeColor::Border)
                    .with_theme_color::<UiBase>(ThemeColor::Background)
                    .with_interaction()
                    .with_tooltip(super::build::palette_label(index))
                    .with_cursor_icon(winit::window::CursorIcon::Pointer)
                    .flow(FlowDirection::Horizontal, 3.0, 0.0)
                    .entity();
                tree.in_parent(chip, |tree| {
                    tree.add_node()
                        .fill()
                        .with_rect(3.0, 0.0, vec4(0.0, 0.0, 0.0, 0.0))
                        .color_raw::<UiBase>(vec4(color[0], color[1], color[2], 1.0))
                        .entity();
                });
                material_swatches.push((chip, index));
            }
        });

        section_label(tree, "Generate", font);
        let gen_row = tree
            .add_node()
            .fill_width()
            .auto_size(AutoSizeMode::Height)
            .flow(FlowDirection::Horizontal, 0.0, 4.0)
            .flow_wrap()
            .entity();
        tree.in_parent(gen_row, |tree| {
            for (kind, label) in GENERATORS {
                let tooltip = format!("Generate a {} at the view focus", label.to_lowercase());
                let button = compact_button(tree, label, &tooltip, 64.0, font);
                generate_buttons.push((button, *kind));
            }
        });

        let settings_header = tree.add_collapsing_header("Generation settings", false);
        let settings_content = widget::<UiCollapsingHeaderData>(tree.world_mut(), settings_header)
            .map(|data| data.content_entity)
            .unwrap_or(settings_header);
        tree.in_parent(settings_content, |tree| {
            let grid = tree.add_property_grid(120.0);
            let section = tree.add_property_section(grid, "Building");
            generation.footprint_x = settings_drag(tree, grid, section, "Width", 0.5, 0.1, 1);
            generation.footprint_z = settings_drag(tree, grid, section, "Depth", 0.5, 0.1, 1);
            generation.stories = settings_drag(tree, grid, section, "Stories", 1.0, 0.1, 0);
            generation.story_height = settings_drag(tree, grid, section, "Story h", 2.0, 0.1, 1);
            generation.wall_thickness = settings_drag(tree, grid, section, "Wall", 0.1, 0.02, 2);
            generation.window_spacing = settings_drag(tree, grid, section, "Win gap", 2.0, 0.1, 1);
            generation.seed = settings_drag(tree, grid, section, "Seed", 1.0, 1.0, 0);
            generation.variation = settings_drag(tree, grid, section, "Variation", 0.0, 0.02, 2);
        });

        let advanced = tree.add_collapsing_header("Advanced", false);
        let advanced_content = widget::<UiCollapsingHeaderData>(tree.world_mut(), advanced)
            .map(|data| data.content_entity)
            .unwrap_or(advanced);
        tree.in_parent(advanced_content, |tree| {
            apply_material_button = tree.add_button("Paint selection");
            snap_grid_button = tree.add_button("Snap selection to grid");
            mark_brush_button = tree.add_button("Mark as brush");
            upgrade_button = tree.add_button("Upgrade with glTF...");
            show_final_button = tree.add_button("Toggle final / greybox");
        });
    });

    CreateShapeHandles {
        place_button,
        push_pull_button,
        play_button,
        spawn_button,
        status_label,
        shape_buttons,
        size_x,
        size_y,
        size_z,
        grid_size,
        grid_presets,
        orient_checkbox,
        preset_buttons,
        material_swatches,
        generation,
        generate_buttons,
        apply_material_button,
        snap_grid_button,
        mark_brush_button,
        upgrade_button,
        show_final_button,
    }
}

fn draw_iso_box(world: &mut World, canvas: Entity, size: [f32; 3], dimension: f32) {
    let max_dim = size[0].max(size[1]).max(size[2]).max(0.001);
    let scale = (dimension * 0.25) / max_dim;
    let half_x = size[0] * 0.5 * scale;
    let height = size[1] * scale;
    let half_z = size[2] * 0.5 * scale;
    let center_x = dimension * 0.5;
    let center_y = dimension * 0.66;
    let project = |x: f32, y: f32, z: f32| -> Vec2 {
        Vec2::new(center_x + (x - z), center_y + (x + z) * 0.5 - y)
    };
    let corners = [
        project(-half_x, 0.0, -half_z),
        project(half_x, 0.0, -half_z),
        project(half_x, 0.0, half_z),
        project(-half_x, 0.0, half_z),
        project(-half_x, height, -half_z),
        project(half_x, height, -half_z),
        project(half_x, height, half_z),
        project(-half_x, height, half_z),
    ];
    let edges = [
        (0, 1),
        (1, 2),
        (2, 3),
        (3, 0),
        (4, 5),
        (5, 6),
        (6, 7),
        (7, 4),
        (0, 4),
        (1, 5),
        (2, 6),
        (3, 7),
    ];
    let color = vec4(0.45, 0.78, 1.0, 0.95);
    ui_canvas_clear(world, canvas);
    for (start, end) in edges {
        ui_canvas_line(world, canvas, corners[start], corners[end], 1.0, color);
    }
}

fn compact_button(
    tree: &mut UiTreeBuilder,
    label: &str,
    tooltip: &str,
    width: f32,
    font: f32,
) -> Entity {
    let button = tree
        .add_node()
        .size((width).px(), (24.0).px())
        .with_rect(4.0, 1.0, vec4(0.0, 0.0, 0.0, 0.0))
        .with_theme_border_color(ThemeColor::Border)
        .with_theme_color::<UiBase>(ThemeColor::Background)
        .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
        .with_interaction()
        .with_tooltip(tooltip)
        .with_cursor_icon(winit::window::CursorIcon::Pointer)
        .flow(FlowDirection::Horizontal, 0.0, 0.0)
        .entity();
    tree.in_parent(button, |tree| {
        tree.add_node()
            .fill()
            .with_text(label, font * 0.8)
            .text_center()
            .fg(ThemeColor::Text)
            .entity();
    });
    button
}

fn section_label(tree: &mut UiTreeBuilder, text: &str, font: f32) {
    tree.add_node()
        .size(100.pct(), (18.0).px())
        .with_text(text, font * 0.85)
        .text_left()
        .color_raw::<UiBase>(vec4(0.8, 0.85, 1.0, 1.0))
        .entity();
}

fn settings_drag(
    tree: &mut UiTreeBuilder,
    grid: Entity,
    section: Entity,
    label: &str,
    min: f32,
    speed: f32,
    precision: usize,
) -> Entity {
    let row = tree.add_property_row(grid, section, label);
    let mut widget = Entity::default();
    tree.in_parent(row, |tree| {
        widget = tree.add_drag_value_configured(
            DragValueConfig::new(min, f32::MAX, min)
                .speed(speed)
                .precision(precision),
        );
    });
    widget
}

fn size_drag(tree: &mut UiTreeBuilder, grid: Entity, section: Entity, label: &str) -> Entity {
    let row = tree.add_property_row(grid, section, label);
    let mut widget = Entity::default();
    tree.in_parent(row, |tree| {
        widget = tree.add_drag_value_configured(
            DragValueConfig::new(0.01, f32::MAX, 1.0)
                .speed(0.1)
                .precision(2),
        );
    });
    widget
}

pub fn poll(editor_world: &mut EditorWorld, world: &mut World, handles: &UiHandles) {
    let handles = handles.create_shape.clone();
    let mut toggle_active = false;
    let mut start_placing = false;
    let mut generate: Option<GeneratorKind> = None;
    let mut toggle_push_pull = false;
    let mut toggle_play = false;
    let mut add_spawn = false;
    for event in ui_events(world) {
        match event {
            UiEvent::ButtonClicked(entity) => {
                if *entity == handles.place_button {
                    toggle_active = true;
                }
                if *entity == handles.push_pull_button {
                    toggle_push_pull = true;
                }
                if *entity == handles.play_button {
                    toggle_play = true;
                }
                if *entity == handles.spawn_button {
                    add_spawn = true;
                }
                for (button, kind) in &handles.generate_buttons {
                    if *button == *entity {
                        generate = Some(*kind);
                    }
                }
                for (button, shape) in &handles.shape_buttons {
                    if *button == *entity {
                        editor_world.resources.placement.shape = *shape;
                    }
                }
                for (button, value) in &handles.grid_presets {
                    if *button == *entity {
                        editor_world.resources.snap.translation_step = *value;
                    }
                }
                for (button, index) in &handles.preset_buttons {
                    if *button == *entity {
                        let (_, shape, size) = BLOCK_PRESETS[*index];
                        editor_world.resources.placement.shape = shape;
                        editor_world.resources.placement.size =
                            Vec3::new(size[0], size[1], size[2]);
                        start_placing = true;
                    }
                }
                for (button, index) in &handles.material_swatches {
                    if *button == *entity {
                        super::build::set_active_palette(editor_world, *index);
                        editor_world
                            .resources
                            .ui_interaction
                            .actions
                            .push(super::Action::ApplyGreyboxToSelection);
                    }
                }
                let actions = &mut editor_world.resources.ui_interaction.actions;
                if *entity == handles.apply_material_button {
                    actions.push(super::Action::ApplyGreyboxToSelection);
                } else if *entity == handles.snap_grid_button {
                    actions.push(super::Action::SnapSelectionToGrid);
                } else if *entity == handles.mark_brush_button {
                    actions.push(super::Action::MarkSelectionAsBrush);
                } else if *entity == handles.upgrade_button {
                    actions.push(super::Action::UpgradeSelectionWithGltf);
                } else if *entity == handles.show_final_button {
                    actions.push(super::Action::ToggleShowFinal);
                }
            }
            UiEvent::DragValueChanged { entity, value } => {
                let value = *value;
                let entity = *entity;
                let generation = handles.generation;
                if entity == handles.grid_size {
                    editor_world.resources.snap.translation_step = value.max(0.01);
                } else if entity == generation.footprint_x {
                    editor_world.resources.generation.footprint_x = value.max(0.5);
                } else if entity == generation.footprint_z {
                    editor_world.resources.generation.footprint_z = value.max(0.5);
                } else if entity == generation.stories {
                    editor_world.resources.generation.stories = value.round().max(1.0) as u32;
                } else if entity == generation.story_height {
                    editor_world.resources.generation.story_height = value.max(2.0);
                } else if entity == generation.wall_thickness {
                    editor_world.resources.generation.wall_thickness = value.max(0.05);
                } else if entity == generation.window_spacing {
                    editor_world.resources.generation.window_spacing = value.max(1.0);
                } else if entity == generation.seed {
                    editor_world.resources.generation.seed = value.round().max(1.0) as u32;
                } else if entity == generation.variation {
                    editor_world.resources.generation.variation = value.clamp(0.0, 1.0);
                } else if entity == handles.size_x {
                    editor_world.resources.placement.size.x = value.max(0.01);
                } else if entity == handles.size_y {
                    editor_world.resources.placement.size.y = value.max(0.01);
                } else if entity == handles.size_z {
                    editor_world.resources.placement.size.z = value.max(0.01);
                }
            }
            UiEvent::CheckboxChanged { entity, value } if *entity == handles.orient_checkbox => {
                editor_world.resources.placement.orient_to_normal = *value;
            }
            _ => {}
        }
    }
    if toggle_active {
        let active = !editor_world.resources.placement.active;
        set_active(editor_world, world, active);
    }
    if start_placing {
        set_active(editor_world, world, true);
    }
    if toggle_push_pull {
        let active = !editor_world.resources.push_pull.active;
        if active {
            set_active(editor_world, world, false);
        }
        editor_world.resources.push_pull.active = active;
        let button = editor_world
            .resources
            .ui_handles
            .create_shape
            .push_pull_button;
        ui_button_set_text(
            world,
            button,
            if active {
                "Stop push/pull"
            } else {
                "Push/Pull faces"
            },
        );
    }
    if let Some(kind) = generate {
        run_generator(editor_world, world, kind);
    }
    if add_spawn {
        crate::systems::play::add_player_spawn(editor_world, world);
    }
    if toggle_play {
        crate::systems::play::toggle_and_sync_button(editor_world, world);
    }
    update_status(editor_world, world);
}

fn run_generator(editor_world: &mut EditorWorld, world: &mut World, kind: GeneratorKind) {
    use super::build::{
        BuildCommand, BuildingParams, CityBlockParams, ColumnsParams, CourtyardParams,
        PerimeterParams, StairsParams, TowerParams, WfcCityParams, WfcParams,
    };
    let focus = crate::systems::camera::focus_point(editor_world, world);
    let step = super::build::snap_step(editor_world);
    let proto = super::build::PROTOTYPE_MATERIAL_COUNT;
    let wall_material = editor_world.resources.build.active_palette % proto;
    let floor_material = (wall_material + 1) % proto;
    let stair_material = (wall_material + 3) % proto;
    let settings = &editor_world.resources.generation;
    let footprint = Vec2::new(settings.footprint_x.max(2.0), settings.footprint_z.max(2.0));
    let stories = settings.stories.max(1);
    let story_height = settings.story_height.max(2.0);
    let wall_thickness = settings.wall_thickness.max(0.1);
    let floor_thickness = settings.floor_thickness.max(0.1);
    let window_spacing = settings.window_spacing.max(2.0);
    let seed = settings.seed.max(1);
    let variation = settings.variation;
    let anchor = |footprint: Vec2| {
        super::build::snap_translation(
            Vec3::new(
                focus.x - footprint.x * 0.5,
                0.0,
                focus.z - footprint.y * 0.5,
            ),
            step,
        )
    };
    let building = |footprint: Vec2, stories: u32| BuildingParams {
        min_corner: anchor(footprint),
        footprint,
        stories,
        story_height,
        wall_thickness,
        floor_thickness,
        window_spacing,
        floor_material,
        wall_material,
        stair_material,
    };
    let command = match kind {
        GeneratorKind::Building => BuildCommand::Building(building(footprint, stories)),
        GeneratorKind::Room => BuildCommand::Building(building(footprint, 1)),
        GeneratorKind::Tower => BuildCommand::Tower(TowerParams {
            min_corner: anchor(footprint),
            footprint,
            stories: stories.max(3),
            story_height,
            shrink_per_story: 0.4,
            floor_thickness,
            floor_material,
            wall_material,
        }),
        GeneratorKind::Stairs => {
            let stair_footprint = Vec2::new(footprint.x.min(4.0), footprint.y);
            BuildCommand::Stairs(StairsParams {
                min_corner: anchor(stair_footprint),
                width: stair_footprint.x,
                steps: (story_height / 0.25).round().max(4.0) as u32,
                rise: 0.25,
                run: 0.35,
                material: stair_material,
            })
        }
        GeneratorKind::Columns => BuildCommand::Columns(ColumnsParams {
            min_corner: anchor(footprint),
            footprint,
            spacing: window_spacing,
            column: 0.6,
            height: story_height,
            floor_thickness,
            floor_material,
            column_material: wall_material,
        }),
        GeneratorKind::Perimeter => BuildCommand::Perimeter(PerimeterParams {
            min_corner: anchor(footprint),
            footprint,
            height: story_height,
            thickness: wall_thickness,
            gate_width: 3.0,
            material: wall_material,
        }),
        GeneratorKind::CityBlock => {
            let street = step.max(2.0);
            let span = Vec2::new(
                footprint.x * 3.0 + street * 2.0,
                footprint.y * 3.0 + street * 2.0,
            );
            BuildCommand::CityBlock(CityBlockParams {
                min_corner: anchor(span),
                columns: 3,
                rows: 3,
                lot: footprint,
                street,
                max_stories: stories.max(2),
                story_height,
                wall_thickness,
                floor_thickness,
                window_spacing,
                seed,
                variation,
                floor_material,
                wall_material,
            })
        }
        GeneratorKind::Courtyard => BuildCommand::Courtyard(CourtyardParams {
            min_corner: anchor(footprint),
            footprint,
            wing_depth: (footprint.x.min(footprint.y) * 0.3).max(3.0),
            stories,
            story_height,
            wall_thickness,
            floor_thickness,
            window_spacing,
            floor_material,
            wall_material,
        }),
        GeneratorKind::Wfc => {
            let cell = window_spacing.max(3.0);
            let span = Vec2::new(cell * 6.0, cell * 6.0);
            BuildCommand::Wfc(WfcParams {
                min_corner: anchor(span),
                columns: 6,
                rows: 6,
                cell,
                wall_height: story_height,
                wall_thickness,
                floor_thickness,
                seed,
                floor_material,
                wall_material,
            })
        }
        GeneratorKind::WfcCity => {
            let cell = footprint.x.max(8.0) + 2.0;
            let span = Vec2::new(cell * 10.0, cell * 10.0);
            let count = super::build::palette_count();
            BuildCommand::WfcCity(WfcCityParams {
                min_corner: anchor(span),
                columns: 10,
                rows: 10,
                cell,
                road_width: 2.0,
                max_stories: stories.max(2) + 4,
                story_height,
                wall_thickness,
                floor_thickness,
                window_spacing,
                seed,
                road_material: 1 % count,
                park_material: 2 % count,
                floor_material: 6 % count,
                wall_material,
            })
        }
    };
    super::build::apply_command(editor_world, world, command);
}

fn sync_drag(world: &mut World, entity: Entity, value: f32) {
    if let Some(data) = world.ui.get_ui_drag_value_mut(entity)
        && !data.editing
        && (data.value - value).abs() > f32::EPSILON
    {
        data.value = value;
    }
}

pub fn sync(editor_world: &EditorWorld, world: &mut World) {
    let handles = &editor_world.resources.ui_handles.create_shape;
    sync_drag(
        world,
        handles.grid_size,
        editor_world.resources.snap.translation_step,
    );
    let settings = &editor_world.resources.generation;
    let generation = handles.generation;
    sync_drag(world, generation.footprint_x, settings.footprint_x);
    sync_drag(world, generation.footprint_z, settings.footprint_z);
    sync_drag(world, generation.stories, settings.stories as f32);
    sync_drag(world, generation.story_height, settings.story_height);
    sync_drag(world, generation.wall_thickness, settings.wall_thickness);
    sync_drag(world, generation.window_spacing, settings.window_spacing);
    sync_drag(world, generation.seed, settings.seed as f32);
    sync_drag(world, generation.variation, settings.variation);

    let (accent, surface) = {
        let theme = world.resources.retained_ui.theme_state.active_theme();
        (theme.accent_color, theme.background_color)
    };
    let step = editor_world.resources.snap.translation_step;
    let active_shape = editor_world.resources.placement.shape;
    let active_palette = editor_world.resources.build.active_palette;
    for (button, shape) in &handles.shape_buttons {
        let color = if *shape == active_shape {
            accent
        } else {
            surface
        };
        set_node_fill(world, *button, color);
    }
    for (card, index) in &handles.material_swatches {
        let color = if *index == active_palette {
            accent
        } else {
            surface
        };
        set_node_fill(world, *card, color);
    }
    for (button, value) in &handles.grid_presets {
        let color = if (*value - step).abs() < 1.0e-4 {
            accent
        } else {
            surface
        };
        set_node_fill(world, *button, color);
    }
    let placing = editor_world.resources.placement.active;
    set_node_fill(
        world,
        handles.place_button,
        if placing { accent } else { surface },
    );
    set_node_fill(
        world,
        handles.push_pull_button,
        if editor_world.resources.push_pull.active {
            accent
        } else {
            surface
        },
    );
}

fn set_node_fill(world: &mut World, entity: Entity, color: Vec4) {
    if let Some(node_color) = world.ui.get_ui_node_color_mut(entity) {
        node_color.colors[0] = Some(color);
    }
}

pub fn open(editor_world: &mut EditorWorld, world: &mut World) {
    let tab_bar = editor_world.resources.ui_handles.tree.tab_bar;
    ui_tab_bar_set_value(world, tab_bar, 1);
}

pub fn stop(editor_world: &mut EditorWorld, world: &mut World) {
    if editor_world.resources.placement.active {
        set_active(editor_world, world, false);
    }
}

fn set_active(editor_world: &mut EditorWorld, world: &mut World, active: bool) {
    editor_world.resources.placement.active = active;
    editor_world.resources.placement.phase = PlacementPhase::Hover;
    editor_world.resources.placement.press_screen = None;
    if active {
        editor_world.resources.snap.enabled = true;
        editor_world.resources.push_pull.active = false;
        let button = editor_world
            .resources
            .ui_handles
            .create_shape
            .push_pull_button;
        ui_button_set_text(world, button, "Push/Pull faces");
    } else {
        clear_ghost(editor_world, world);
    }
    let button = editor_world.resources.ui_handles.create_shape.place_button;
    ui_button_set_text(
        world,
        button,
        if active {
            "Stop placing"
        } else {
            "Start placing"
        },
    );
}

fn update_status(editor_world: &EditorWorld, world: &mut World) {
    let label = editor_world.resources.ui_handles.create_shape.status_label;
    let text = if editor_world.resources.placement.active {
        match editor_world.resources.placement.phase {
            PlacementPhase::Hover => "Click to stamp, or shift+drag to draw a footprint",
            PlacementPhase::Footprint { .. } => "Drag the footprint, release to set it",
            PlacementPhase::Height { .. } => "Move to set height, click to place (Esc cancels)",
        }
    } else if editor_world.resources.push_pull.active {
        "Drag a block face along its normal to resize (Esc cancels)"
    } else {
        "Pick a shape, then press Start placing"
    };
    ui_set_text(world, label, text);
}

fn set_status_text(editor_world: &EditorWorld, world: &mut World, text: &str) {
    let label = editor_world.resources.ui_handles.create_shape.status_label;
    ui_set_text(world, label, text);
}

pub fn tick(editor_world: &mut EditorWorld, world: &mut World) {
    if !editor_world.resources.placement.active {
        clear_ghost(editor_world, world);
        editor_world.resources.placement.phase = PlacementPhase::Hover;
        editor_world.resources.placement.press_screen = None;
        return;
    }

    let mouse = *nightshade::ecs::input::access::mouse_for_active(world);
    let in_viewport = input::mouse_in_active_viewport(world);
    let ui_block = input::ui_capturing(world);
    let usable = in_viewport && !ui_block;

    let just_pressed = mouse.state.contains(MouseState::LEFT_JUST_PRESSED);
    let just_released = mouse.state.contains(MouseState::LEFT_JUST_RELEASED);
    let held = mouse.state.contains(MouseState::LEFT_CLICKED);
    let shift = world
        .resources
        .input
        .keyboard
        .is_key_pressed(KeyCode::ShiftLeft)
        || world
            .resources
            .input
            .keyboard
            .is_key_pressed(KeyCode::ShiftRight);
    let cancel = world
        .resources
        .input
        .keyboard
        .is_key_pressed(KeyCode::Escape);

    let surface = if usable {
        surface_under_cursor(editor_world, world, mouse.position)
    } else {
        None
    };

    if cancel {
        editor_world.resources.placement.phase = PlacementPhase::Hover;
        editor_world.resources.placement.press_screen = None;
        update_ghost(editor_world, world, None);
        return;
    }

    let step = super::build::snap_step(editor_world);
    let size = editor_world.resources.placement.size;
    let shape = editor_world.resources.placement.shape;
    let orient = editor_world.resources.placement.orient_to_normal;

    let mut preview: Option<GhostTransform> = None;

    match editor_world.resources.placement.phase {
        PlacementPhase::Hover => {
            if usable && just_pressed {
                editor_world.resources.placement.press_screen = Some(mouse.position);
            }
            let press = editor_world.resources.placement.press_screen;
            let moved = press.is_some_and(|start| {
                held && nalgebra_glm::distance(&start, &mouse.position) > DRAG_THRESHOLD_PIXELS
            });
            if moved
                && shift
                && !orient
                && let Some((hit, _)) = surface
            {
                let corner = snap_footprint_point(editor_world, world, hit, step);
                editor_world.resources.placement.phase = PlacementPhase::Footprint {
                    start_corner: corner,
                };
                preview = Some(aabb_to_ghost(shape, corner, corner));
            } else if just_released && press.is_some() {
                let was_click = press.is_some_and(|start| {
                    nalgebra_glm::distance(&start, &mouse.position) <= DRAG_THRESHOLD_PIXELS
                });
                editor_world.resources.placement.press_screen = None;
                if was_click && let Some((hit, normal)) = surface {
                    let ghost = stamp_ghost(shape, hit, normal, size, orient, step);
                    commit(editor_world, world, ghost);
                }
            } else if let Some((hit, normal)) = surface {
                preview = Some(stamp_ghost(shape, hit, normal, size, orient, step));
            }
        }
        PlacementPhase::Footprint { start_corner } => {
            let opposite =
                footprint_corner(editor_world, world, mouse.position, start_corner.y, step)
                    .unwrap_or(start_corner);
            let (min, max) = footprint_bounds(start_corner, opposite);
            preview = Some(aabb_to_ghost(
                shape,
                min,
                footprint_slab_top(min, max, step),
            ));
            set_status_text(
                editor_world,
                world,
                &format!("{:.2} x {:.2} m", max.x - min.x, max.z - min.z),
            );
            if just_released {
                editor_world.resources.placement.press_screen = None;
                let area = (max.x - min.x) * (max.z - min.z);
                if area < step * step * 0.25 {
                    editor_world.resources.placement.phase = PlacementPhase::Hover;
                } else {
                    editor_world.resources.placement.phase = PlacementPhase::Height {
                        footprint_min: min,
                        footprint_max: max,
                        height: step,
                    };
                }
            }
        }
        PlacementPhase::Height {
            footprint_min,
            footprint_max,
            height,
        } => {
            let height =
                height_from_cursor(world, mouse.position, footprint_min, footprint_max, step)
                    .unwrap_or(height);
            let top = Vec3::new(footprint_max.x, footprint_min.y + height, footprint_max.z);
            preview = Some(aabb_to_ghost(shape, footprint_min, top));
            set_status_text(
                editor_world,
                world,
                &format!(
                    "{:.2} x {:.2} x {:.2} m",
                    footprint_max.x - footprint_min.x,
                    height,
                    footprint_max.z - footprint_min.z,
                ),
            );
            if usable && just_pressed {
                editor_world.resources.placement.phase = PlacementPhase::Hover;
                editor_world.resources.placement.press_screen = None;
                let ghost = aabb_to_ghost(shape, footprint_min, top);
                commit(editor_world, world, ghost);
                preview = None;
            } else {
                editor_world.resources.placement.phase = PlacementPhase::Height {
                    footprint_min,
                    footprint_max,
                    height,
                };
            }
        }
    }

    update_ghost(editor_world, world, preview);
}

fn update_ghost(
    editor_world: &mut EditorWorld,
    world: &mut World,
    preview: Option<GhostTransform>,
) {
    let shape = editor_world.resources.placement.shape;
    if editor_world.resources.placement.ghost_shape != Some(shape) {
        clear_ghost(editor_world, world);
    }

    let Some(transform) = preview else {
        if let Some(ghost) = editor_world.resources.placement.ghost_entity {
            set_entity_visible(world, ghost, false);
        }
        clear_ghost_outline(world);
        return;
    };

    let ghost = match editor_world.resources.placement.ghost_entity {
        Some(ghost) => ghost,
        None => {
            let ghost = spawn_ghost(world, shape);
            editor_world.resources.placement.ghost_entity = Some(ghost);
            editor_world.resources.placement.ghost_shape = Some(shape);
            ghost
        }
    };

    set_entity_visible(world, ghost, true);
    if let Some(local) = world.core.get_local_transform_mut(ghost) {
        local.translation = transform.translation;
        local.scale = transform.scale;
        local.rotation = transform.rotation;
    }
    mark_local_transform_dirty(world, ghost);
    world
        .resources
        .editor_selection
        .bounding_volume_selected_entity = None;
    world.resources.editor_selection.selected_entities = vec![ghost];
}

fn clear_ghost_outline(world: &mut World) {
    world.resources.editor_selection.selected_entities.clear();
}

fn spawn_ghost(world: &mut World, shape: PlacementShape) -> Entity {
    let entity = super::build::spawn_shape(world, shape, Vec3::zeros());
    world.core.add_components(entity, VISIBILITY);
    let name = register_ghost_material(world);
    world.core.set_material_ref(entity, MaterialRef::new(name));
    entity
}

fn register_ghost_material(world: &mut World) -> String {
    let name = "greybox_ghost".to_string();
    let material = Material {
        base_color: [0.4, 0.75, 1.0, 0.4],
        alpha_mode: AlphaMode::Blend,
        unlit: true,
        ..Default::default()
    };
    material_registry_insert(
        &mut world.resources.assets.material_registry,
        name.clone(),
        material,
    );
    if let Some(&index) = world
        .resources
        .assets
        .material_registry
        .registry
        .name_to_index
        .get(&name)
    {
        registry_add_reference(
            &mut world.resources.assets.material_registry.registry,
            index,
        );
    }
    name
}

fn set_entity_visible(world: &mut World, entity: Entity, visible: bool) {
    world.core.add_components(entity, VISIBILITY);
    world.core.set_visibility(entity, Visibility { visible });
}

fn clear_ghost(editor_world: &mut EditorWorld, world: &mut World) {
    if let Some(ghost) = editor_world.resources.placement.ghost_entity.take() {
        despawn_recursive_immediate(world, ghost);
    }
    editor_world.resources.placement.ghost_shape = None;
    clear_ghost_outline(world);
}

#[derive(Clone, Copy)]
struct GhostTransform {
    translation: Vec3,
    scale: Vec3,
    rotation: Quat,
}

fn aabb_to_ghost(shape: PlacementShape, min: Vec3, max: Vec3) -> GhostTransform {
    let block = super::build::block_from_aabb(shape, min, max, 0);
    GhostTransform {
        translation: block.translation,
        scale: block.scale,
        rotation: block.rotation,
    }
}

/// Build the ghost for a single click stamp. Without orient the block rests
/// flush against the picked face (its near face at the hit coordinate along the
/// dominant normal axis) with the footprint snapped to the global grid. With
/// orient the block tilts to the surface normal instead.
fn stamp_ghost(
    shape: PlacementShape,
    hit: Vec3,
    normal: Vec3,
    size: Vec3,
    orient: bool,
    step: f32,
) -> GhostTransform {
    if orient {
        let full = super::build::shape_full_extent(shape);
        let half_y = if full.y > 1.0e-6 { size.y * 0.5 } else { 0.0 };
        return GhostTransform {
            translation: hit + nalgebra_glm::normalize(&normal) * half_y,
            scale: Vec3::new(
                super::build::axis_scale(size.x, full.x),
                super::build::axis_scale(size.y, full.y),
                super::build::axis_scale(size.z, full.z),
            ),
            rotation: rotation_y_to_normal(normal),
        };
    }
    let (min, max) = stamp_aabb(hit, normal, size, step);
    aabb_to_ghost(shape, min, max)
}

fn stamp_aabb(hit: Vec3, normal: Vec3, size: Vec3, step: f32) -> (Vec3, Vec3) {
    let normal = if normal.magnitude() < 1.0e-6 {
        Vec3::new(0.0, 1.0, 0.0)
    } else {
        nalgebra_glm::normalize(&normal)
    };
    let dominant = if normal.x.abs() >= normal.y.abs() && normal.x.abs() >= normal.z.abs() {
        0
    } else if normal.y.abs() >= normal.z.abs() {
        1
    } else {
        2
    };
    let mut min = Vec3::zeros();
    let mut max = Vec3::zeros();
    for axis in 0..3 {
        if axis == dominant {
            if normal[axis] >= 0.0 {
                min[axis] = hit[axis];
                max[axis] = hit[axis] + size[axis];
            } else {
                max[axis] = hit[axis];
                min[axis] = hit[axis] - size[axis];
            }
        } else {
            let low = snap_to_grid_scalar(hit[axis] - size[axis] * 0.5, step);
            min[axis] = low;
            max[axis] = low + size[axis];
        }
    }
    (min, max)
}

fn snap_to_grid_scalar(value: f32, step: f32) -> f32 {
    if step <= 0.0 {
        value
    } else {
        (value / step).round() * step
    }
}

fn snap_point_xz(point: Vec3, step: f32) -> Vec3 {
    Vec3::new(
        snap_to_grid_scalar(point.x, step),
        point.y,
        snap_to_grid_scalar(point.z, step),
    )
}

/// Snap a footprint point to the nearest existing block corner within a
/// threshold (vertex snapping), falling back to the grid otherwise.
fn snap_footprint_point(editor_world: &EditorWorld, world: &World, point: Vec3, step: f32) -> Vec3 {
    let ghost = editor_world.resources.placement.ghost_entity;
    let threshold = (step * 0.5).max(0.25);
    let mut best: Option<Vec3> = None;
    let mut best_distance = threshold;
    for entity in world
        .core
        .query_entities(nightshade::ecs::world::BOUNDING_VOLUME)
    {
        if Some(entity) == ghost || editor_world.resources.editor_scene.is_scaffolding(entity) {
            continue;
        }
        let (Some(bounding_volume), Some(global)) = (
            world.core.get_bounding_volume(entity),
            world.core.get_global_transform(entity),
        ) else {
            continue;
        };
        for corner in bounding_volume.transform(&global.0).obb.get_corners() {
            let distance = ((corner.x - point.x).powi(2) + (corner.z - point.z).powi(2)).sqrt();
            if distance < best_distance {
                best_distance = distance;
                best = Some(Vec3::new(corner.x, point.y, corner.z));
            }
        }
    }
    best.unwrap_or_else(|| snap_point_xz(point, step))
}

fn footprint_corner(
    editor_world: &EditorWorld,
    world: &World,
    mouse: Vec2,
    base_y: f32,
    step: f32,
) -> Option<Vec3> {
    let point = get_ground_position_from_screen(world, mouse, base_y)?;
    Some(snap_footprint_point(
        editor_world,
        world,
        Vec3::new(point.x, base_y, point.z),
        step,
    ))
}

fn footprint_bounds(a: Vec3, b: Vec3) -> (Vec3, Vec3) {
    let min = Vec3::new(a.x.min(b.x), a.y, a.z.min(b.z));
    let max = Vec3::new(a.x.max(b.x), a.y, a.z.max(b.z));
    (min, max)
}

fn footprint_slab_top(min: Vec3, max: Vec3, step: f32) -> Vec3 {
    Vec3::new(max.x, min.y + step.max(0.05), max.z)
}

fn height_from_cursor(world: &World, mouse: Vec2, min: Vec3, max: Vec3, step: f32) -> Option<f32> {
    let center = Vec3::new((min.x + max.x) * 0.5, min.y, (min.z + max.z) * 0.5);
    let camera_entity = world.resources.active_camera?;
    let camera_transform = world.core.get_global_transform(camera_entity)?;
    let forward = camera_transform.forward_vector();
    let mut normal = Vec3::new(forward.x, 0.0, forward.z);
    if normal.magnitude() < 1.0e-3 {
        normal = Vec3::new(0.0, 0.0, 1.0);
    }
    let normal = nalgebra_glm::normalize(&normal);
    let ray = PickingRay::from_screen_position(world, mouse)?;
    let plane_distance = -nalgebra_glm::dot(&normal, &center);
    let hit = ray.intersect_plane(normal, plane_distance)?;
    let raw = hit.y - min.y;
    Some(snap_to_grid_scalar(raw, step).max(step))
}

fn surface_under_cursor(
    editor_world: &EditorWorld,
    world: &World,
    mouse_position: Vec2,
) -> Option<(Vec3, Vec3)> {
    let ghost = editor_world.resources.placement.ghost_entity;
    let hit = pick_entities(world, mouse_position, PickingOptions::default())
        .into_iter()
        .find(|result| {
            Some(result.entity) != ghost
                && !editor_world
                    .resources
                    .editor_scene
                    .is_scaffolding(result.entity)
        });
    if let Some(hit) = hit {
        let normal = obb_face_normal(world, hit.entity, hit.world_position);
        return Some((hit.world_position, normal));
    }
    let ground = get_ground_position_from_screen(world, mouse_position, 0.0)?;
    Some((ground, Vec3::new(0.0, 1.0, 0.0)))
}

fn obb_face_normal(world: &World, entity: Entity, point: Vec3) -> Vec3 {
    let up = Vec3::new(0.0, 1.0, 0.0);
    let Some(bounding_volume) = world.core.get_bounding_volume(entity) else {
        return up;
    };
    let Some(global) = world.core.get_global_transform(entity) else {
        return up;
    };
    let obb = bounding_volume.transform(&global.0).obb;
    let inverse_orientation = obb.orientation.conjugate();
    let local = nalgebra_glm::quat_rotate_vec3(&inverse_orientation, &(point - obb.center));
    let ratio = Vec3::new(
        local.x / obb.half_extents.x.max(1.0e-6),
        local.y / obb.half_extents.y.max(1.0e-6),
        local.z / obb.half_extents.z.max(1.0e-6),
    );
    let local_normal = if ratio.x.abs() >= ratio.y.abs() && ratio.x.abs() >= ratio.z.abs() {
        Vec3::new(ratio.x.signum(), 0.0, 0.0)
    } else if ratio.y.abs() >= ratio.z.abs() {
        Vec3::new(0.0, ratio.y.signum(), 0.0)
    } else {
        Vec3::new(0.0, 0.0, ratio.z.signum())
    };
    nalgebra_glm::quat_rotate_vec3(&obb.orientation, &local_normal)
}

fn rotation_y_to_normal(normal: Vec3) -> Quat {
    let up = Vec3::new(0.0, 1.0, 0.0);
    let normal = nalgebra_glm::normalize(&normal);
    let dot = nalgebra_glm::dot(&up, &normal);
    if dot > 0.9999 {
        return Quat::identity();
    }
    if dot < -0.9999 {
        return nalgebra_glm::quat_angle_axis(std::f32::consts::PI, &Vec3::new(1.0, 0.0, 0.0));
    }
    let axis = nalgebra_glm::normalize(&nalgebra_glm::cross(&up, &normal));
    nalgebra_glm::quat_angle_axis(dot.acos(), &axis)
}

fn commit(editor_world: &mut EditorWorld, world: &mut World, transform: GhostTransform) {
    let block = super::build::PlacedBlock {
        shape: editor_world.resources.placement.shape,
        translation: transform.translation,
        scale: transform.scale,
        rotation: transform.rotation,
        material: editor_world.resources.build.active_palette,
        visible: true,
    };
    super::build::apply_command(
        editor_world,
        world,
        super::build::BuildCommand::PlaceBlocks(vec![block]),
    );
}
