use crate::prelude::*;
use rusterix::DungeonTileKind;
use rusterix::Value;
use rusterix::map::dungeon::DungeonDoorOpenMode;
use rusterix::rebuild_generated_geometry;

const DUNGEON_VIEW_ID: &str = "Dungeon Dock View";
const DUNGEON_SETTINGS_TOML: &str = "Dungeon Dock Settings TOML";
const DUNGEON_CELL_MIN: i32 = 72;
const DUNGEON_CELL_MAX: i32 = 132;
const DUNGEON_GAP: i32 = 12;
const DUNGEON_MARGIN: i32 = 12;
const DUNGEON_SETTINGS_WIDTH: i32 = 280;

#[derive(Clone, Copy)]
struct DungeonPalettePlacement {
    kind: DungeonTileKind,
    rect: Vec4<i32>,
}

pub struct DungeonDock {
    placements: Vec<DungeonPalettePlacement>,
    hovered: Option<DungeonTileKind>,
    scroll_y: i32,
    content_height: i32,
}

impl DungeonDock {
    fn default_render_toml() -> String {
        "[render]\ntransition_seconds = 1.0\nsun_enabled = false\nshadow_enabled = true\nfog_density = 5.0\nfog_color = \"#000000\"\n".to_string()
    }

    fn legacy_render_toml(map: &Map, server_ctx: &ServerContext) -> String {
        format!(
            "[render]\ntransition_seconds = {}\nsun_enabled = {}\nshadow_enabled = {}\nfog_density = {}\nfog_color = \"{}\"\n",
            map.properties.get_float_default(
                "dungeon_render_transition_seconds",
                server_ctx.curr_dungeon_render_transition_seconds,
            ),
            map.properties.get_bool_default(
                "dungeon_render_sun_enabled",
                server_ctx.curr_dungeon_render_sun_enabled,
            ),
            map.properties.get_bool_default(
                "dungeon_render_shadow_enabled",
                server_ctx.curr_dungeon_render_shadow_enabled,
            ),
            map.properties.get_float_default(
                "dungeon_render_fog_density",
                server_ctx.curr_dungeon_render_fog_density,
            ),
            map.properties.get_str_default(
                "dungeon_render_fog_color",
                server_ctx.curr_dungeon_render_fog_color.clone(),
            ),
        )
    }

    fn load_settings_from_map(project: &Project, server_ctx: &mut ServerContext) {
        if let Some(map) = project.get_map(server_ctx) {
            server_ctx.curr_dungeon_floor_base = map
                .properties
                .get_float_default("dungeon_floor_base", server_ctx.curr_dungeon_floor_base);
            server_ctx.curr_dungeon_height = map
                .properties
                .get_float_default("dungeon_height", server_ctx.curr_dungeon_height)
                .max(0.1);
            server_ctx.curr_dungeon_create_floor = map
                .properties
                .get_bool_default("dungeon_create_floor", server_ctx.curr_dungeon_create_floor);
            server_ctx.curr_dungeon_create_ceiling = map.properties.get_bool_default(
                "dungeon_create_ceiling",
                server_ctx.curr_dungeon_create_ceiling,
            );
            server_ctx.curr_dungeon_standalone = map.properties.get_bool_default(
                "dungeon_standalone_default",
                server_ctx.curr_dungeon_standalone,
            );
            server_ctx.curr_dungeon_tile_span = map
                .properties
                .get_int_default("dungeon_tile_span", server_ctx.curr_dungeon_tile_span)
                .max(1);
            server_ctx.curr_dungeon_tile_depth = map
                .properties
                .get_float_default("dungeon_tile_depth", server_ctx.curr_dungeon_tile_depth)
                .max(0.05);
            server_ctx.curr_dungeon_tile_height = map
                .properties
                .get_float_default("dungeon_tile_height", server_ctx.curr_dungeon_tile_height)
                .max(0.5);
            server_ctx.curr_dungeon_tile_open_mode = map.properties.get_int_default(
                "dungeon_tile_open_mode",
                server_ctx.curr_dungeon_tile_open_mode,
            );
            server_ctx.curr_dungeon_tile_item = map.properties.get_str_default(
                "dungeon_tile_item",
                server_ctx.curr_dungeon_tile_item.clone(),
            );
            let legacy_target = map
                .properties
                .get_float_default("dungeon_stair_target_floor_base", f32::NAN);
            server_ctx.curr_dungeon_stair_target_floor_base = map.properties.get_float_default(
                "dungeon_stair_delta",
                if legacy_target.is_finite() {
                    legacy_target - server_ctx.curr_dungeon_floor_base
                } else {
                    server_ctx.curr_dungeon_stair_target_floor_base
                },
            );
            server_ctx.curr_dungeon_stair_steps = map
                .properties
                .get_int_default("dungeon_stair_steps", server_ctx.curr_dungeon_stair_steps)
                .max(1);
            server_ctx.curr_dungeon_stair_tile_id = map.properties.get_str_default(
                "dungeon_stair_tile_id",
                server_ctx.curr_dungeon_stair_tile_id.clone(),
            );
            server_ctx.curr_dungeon_stair_tile_mode = map.properties.get_int_default(
                "dungeon_stair_tile_mode",
                server_ctx.curr_dungeon_stair_tile_mode,
            );
            server_ctx.curr_dungeon_render_toml = if let Some(render_toml) =
                map.properties.get_str("dungeon_render_toml")
            {
                render_toml.to_string()
            } else if map.properties.get("dungeon_render_transition_seconds").is_some()
                || map.properties.get("dungeon_render_sun_enabled").is_some()
                || map.properties.get("dungeon_render_shadow_enabled").is_some()
                || map.properties.get("dungeon_render_fog_density").is_some()
                || map.properties.get("dungeon_render_fog_color").is_some()
            {
                Self::legacy_render_toml(map, server_ctx)
            } else {
                server_ctx.curr_dungeon_render_toml.clone()
            };
        }
    }

    fn store_settings_to_map(project: &mut Project, server_ctx: &ServerContext) {
        if let Some(map) = project.get_map_mut(server_ctx) {
            map.properties.set(
                "dungeon_floor_base",
                Value::Float(server_ctx.curr_dungeon_floor_base),
            );
            map.properties.set(
                "dungeon_height",
                Value::Float(server_ctx.curr_dungeon_height),
            );
            map.properties.set(
                "dungeon_create_floor",
                Value::Bool(server_ctx.curr_dungeon_create_floor),
            );
            map.properties.set(
                "dungeon_create_ceiling",
                Value::Bool(server_ctx.curr_dungeon_create_ceiling),
            );
            map.properties.set(
                "dungeon_standalone_default",
                Value::Bool(server_ctx.curr_dungeon_standalone),
            );
            map.properties.set(
                "dungeon_tile_span",
                Value::Int(server_ctx.curr_dungeon_tile_span.max(1)),
            );
            map.properties.set(
                "dungeon_tile_depth",
                Value::Float(server_ctx.curr_dungeon_tile_depth.max(0.05)),
            );
            map.properties.set(
                "dungeon_tile_height",
                Value::Float(server_ctx.curr_dungeon_tile_height.max(0.5)),
            );
            map.properties.set(
                "dungeon_tile_open_mode",
                Value::Int(server_ctx.curr_dungeon_tile_open_mode),
            );
            map.properties.set(
                "dungeon_tile_item",
                Value::Str(server_ctx.curr_dungeon_tile_item.clone()),
            );
            map.properties.set(
                "dungeon_stair_delta",
                Value::Float(server_ctx.curr_dungeon_stair_target_floor_base),
            );
            map.properties.set(
                "dungeon_stair_steps",
                Value::Int(server_ctx.curr_dungeon_stair_steps.max(1)),
            );
            map.properties.set(
                "dungeon_stair_tile_id",
                Value::Str(server_ctx.curr_dungeon_stair_tile_id.clone()),
            );
            map.properties.set(
                "dungeon_stair_tile_mode",
                Value::Int(server_ctx.curr_dungeon_stair_tile_mode),
            );
            map.properties.set(
                "dungeon_render_toml",
                Value::Str(server_ctx.curr_dungeon_render_toml.clone()),
            );
        }
    }

    fn settings_nodeui(server_ctx: &ServerContext) -> TheNodeUI {
        let mut nodeui = TheNodeUI::default();
        nodeui.add_item(TheNodeUIItem::OpenTree("Dungeon".into()));
        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
            "Floor Base".into(),
            "Floor Base".into(),
            "Default floor base for newly painted dungeon cells.".into(),
            server_ctx.curr_dungeon_floor_base,
            -64.0..=64.0,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
            "Height".into(),
            "Height".into(),
            "Wall and ceiling height above the floor base for newly painted cells.".into(),
            server_ctx.curr_dungeon_height,
            0.1..=64.0,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::Checkbox(
            "Floors".into(),
            "Floors".into(),
            "Generate floor surfaces for conceptual dungeon tiles.".into(),
            server_ctx.curr_dungeon_create_floor,
        ));
        nodeui.add_item(TheNodeUIItem::Checkbox(
            "Ceilings".into(),
            "Ceilings".into(),
            "Generate ceiling surfaces for conceptual dungeon tiles.".into(),
            server_ctx.curr_dungeon_create_ceiling,
        ));
        nodeui.add_item(TheNodeUIItem::Checkbox(
            "Standalone".into(),
            "Standalone".into(),
            "Newly painted cells stay standalone and will not merge with adjacent dungeon geometry.".into(),
            server_ctx.curr_dungeon_standalone,
        ));
        nodeui.add_item(TheNodeUIItem::CloseTree);
        if server_ctx.curr_dungeon_tile.is_door() {
            nodeui.add_item(TheNodeUIItem::OpenTree("Tile".into()));
            nodeui.add_item(TheNodeUIItem::IntEditSlider(
                "Door Width".into(),
                "Door Width".into(),
                "Door opening span in tiles for newly painted door tiles.".into(),
                server_ctx.curr_dungeon_tile_span.max(1),
                1..=8,
                false,
            ));
            nodeui.add_item(TheNodeUIItem::FloatEditSlider(
                "Door Depth".into(),
                "Door Depth".into(),
                "Conceptual door depth hint for newly painted door tiles.".into(),
                server_ctx.curr_dungeon_tile_depth.max(0.05),
                0.05..=4.0,
                false,
            ));
            nodeui.add_item(TheNodeUIItem::FloatEditSlider(
                "Door Height".into(),
                "Door Height".into(),
                "Door opening height for newly painted door tiles.".into(),
                server_ctx.curr_dungeon_tile_height.max(0.5),
                0.5..=8.0,
                false,
            ));
            nodeui.add_item(TheNodeUIItem::Selector(
                "Door Open Mode".into(),
                "Open Mode".into(),
                "How the generated door should open in gameplay.".into(),
                DungeonDoorOpenMode::all()
                    .iter()
                    .map(|s| (*s).to_string())
                    .collect(),
                server_ctx.curr_dungeon_tile_open_mode,
            ));
            nodeui.add_item(TheNodeUIItem::Text(
                "Item".into(),
                "Item".into(),
                "Item handler class written into generated dungeon door sectors.".into(),
                server_ctx.curr_dungeon_tile_item.clone(),
                Some("Door Handler".into()),
                false,
            ));
            nodeui.add_item(TheNodeUIItem::CloseTree);
        } else if server_ctx.curr_dungeon_tile.is_stair() {
            nodeui.add_item(TheNodeUIItem::OpenTree("Steps".into()));
            nodeui.add_item(TheNodeUIItem::FloatEditSlider(
                "steps_floor_delta".into(),
                "Floor Delta".into(),
                "Relative floor-base change reached by the stair tile. Negative goes down.".into(),
                server_ctx.curr_dungeon_stair_target_floor_base,
                -64.0..=64.0,
                false,
            ));
            nodeui.add_item(TheNodeUIItem::IntEditSlider(
                "steps_steps".into(),
                "Steps".into(),
                "Number of generated steps inside the stair tile.".into(),
                server_ctx.curr_dungeon_stair_steps.max(1),
                1..=16,
                false,
            ));
            nodeui.add_item(TheNodeUIItem::Selector(
                "steps_tile_mode".into(),
                "Tile Mode".into(),
                "How the stair tile source is mapped onto generated stair geometry.".into(),
                vec!["Repeat".into(), "Scale".into()],
                if server_ctx.curr_dungeon_stair_tile_mode == 1 {
                    0
                } else {
                    1
                },
            ));
            nodeui.add_item(TheNodeUIItem::Text(
                "steps_tile_id".into(),
                "Tile Id".into(),
                "Default tile id applied to generated stair geometry.".into(),
                server_ctx.curr_dungeon_stair_tile_id.clone(),
                Some(String::new()),
                false,
            ));
            nodeui.add_item(TheNodeUIItem::CloseTree);
        }
        nodeui
    }

    fn settings_toml(server_ctx: &ServerContext) -> String {
        let mut text = nodeui_to_toml(&Self::settings_nodeui(server_ctx)).trim_end().to_string();
        let render = if server_ctx.curr_dungeon_render_toml.trim().is_empty() {
            Self::default_render_toml()
        } else {
            server_ctx.curr_dungeon_render_toml.trim().to_string()
        };
        if !text.is_empty() {
            text.push_str("\n\n");
        }
        text.push_str(&render);
        text.push('\n');
        text
    }

    fn sync_settings_ui(&self, ui: &mut TheUI, _ctx: &mut TheContext, server_ctx: &ServerContext) {
        if let Some(widget) = ui.get_widget(DUNGEON_SETTINGS_TOML)
            && let Some(edit) = widget.as_text_area_edit()
        {
            let toml_text = Self::settings_toml(server_ctx);
            if edit.text() != toml_text {
                let previous = edit.get_state();
                edit.set_text(toml_text);

                let mut state = edit.get_state();
                let row_max = state.rows.len().saturating_sub(1);
                let row = previous.cursor.row.min(row_max);
                let col_max = state
                    .rows
                    .get(row)
                    .map(|line| line.chars().count())
                    .unwrap_or(0);

                state.cursor.row = row;
                state.cursor.column = previous.cursor.column.min(col_max);
                state.selection.reset();
                TheTextAreaEditTrait::set_state(edit, state);
            }
        }
    }

    fn clip_rect(
        buffer: &TheRGBABuffer,
        rect: Vec4<i32>,
        inset: i32,
    ) -> Option<(usize, usize, usize, usize)> {
        let x0 = (rect.x + inset).clamp(0, buffer.dim().width);
        let y0 = (rect.y + inset).clamp(0, buffer.dim().height);
        let x1 = (rect.x + rect.z - inset).clamp(0, buffer.dim().width);
        let y1 = (rect.y + rect.w - inset).clamp(0, buffer.dim().height);

        if x1 <= x0 || y1 <= y0 {
            return None;
        }

        Some((
            x0 as usize,
            y0 as usize,
            (x1 - x0) as usize,
            (y1 - y0) as usize,
        ))
    }

    fn palette() -> &'static [DungeonTileKind] {
        DungeonTileKind::all()
    }

    fn draw_panel_rect(
        draw: &TheDraw2D,
        buffer: &mut TheRGBABuffer,
        rect: Vec4<i32>,
        color: &[u8; 4],
    ) {
        if let Some(clipped) = Self::clip_rect(buffer, rect, 0) {
            let stride = buffer.stride();
            draw.rect(buffer.pixels_mut(), &clipped, stride, color);
        }
    }

    fn draw_preview(
        &self,
        draw: &TheDraw2D,
        buffer: &mut TheRGBABuffer,
        rect: Vec4<i32>,
        kind: DungeonTileKind,
        selected: bool,
        hovered: bool,
    ) {
        let stride = buffer.stride();
        let card_color = if selected {
            [64, 76, 92, 255]
        } else if hovered {
            [48, 54, 64, 255]
        } else {
            [36, 40, 48, 255]
        };
        if let Some(inner) = Self::clip_rect(buffer, rect, 0) {
            draw.rect(buffer.pixels_mut(), &inner, stride, &card_color);
        }

        let outline = if selected {
            [255, 255, 255, 255]
        } else if hovered {
            [210, 210, 210, 255]
        } else {
            [84, 84, 84, 255]
        };
        if let Some(inner) = Self::clip_rect(buffer, rect, 0) {
            draw.rect_outline(buffer.pixels_mut(), &inner, stride, &outline);
        }

        let preview = Vec4::new(rect.x + 10, rect.y + 10, rect.z - 20, rect.w - 20);
        let floor_color = [150, 150, 150, 255];
        let floor_outline = [205, 205, 205, 255];
        let wall_color = [255, 255, 255, 255];
        let door_color = [240, 196, 92, 255];
        let stair_color = [182, 220, 255, 255];
        let wall = (preview.z.min(preview.w) / 8).clamp(4, 8);
        let floor_rect = preview;

        Self::draw_panel_rect(draw, buffer, floor_rect, &floor_color);
        if let Some(inner) = Self::clip_rect(buffer, floor_rect, 0) {
            draw.rect_outline(buffer.pixels_mut(), &inner, stride, &floor_outline);
        }
        if kind.has_north() {
            Self::draw_panel_rect(
                draw,
                buffer,
                Vec4::new(preview.x, preview.y, preview.z, wall),
                &wall_color,
            );
        }
        if kind.has_south() {
            Self::draw_panel_rect(
                draw,
                buffer,
                Vec4::new(preview.x, preview.y + preview.w - wall, preview.z, wall),
                &wall_color,
            );
        }
        if kind.has_west() {
            Self::draw_panel_rect(
                draw,
                buffer,
                Vec4::new(preview.x, preview.y, wall, preview.w),
                &wall_color,
            );
        }
        if kind.has_east() {
            Self::draw_panel_rect(
                draw,
                buffer,
                Vec4::new(preview.x + preview.z - wall, preview.y, wall, preview.w),
                &wall_color,
            );
        }
        if kind.has_door_north() {
            Self::draw_panel_rect(
                draw,
                buffer,
                Vec4::new(preview.x + preview.z / 4, preview.y, preview.z / 2, wall),
                &door_color,
            );
        }
        if kind.has_door_south() {
            Self::draw_panel_rect(
                draw,
                buffer,
                Vec4::new(
                    preview.x + preview.z / 4,
                    preview.y + preview.w - wall,
                    preview.z / 2,
                    wall,
                ),
                &door_color,
            );
        }
        if kind.has_door_west() {
            Self::draw_panel_rect(
                draw,
                buffer,
                Vec4::new(preview.x, preview.y + preview.w / 4, wall, preview.w / 2),
                &door_color,
            );
        }
        if kind.has_door_east() {
            Self::draw_panel_rect(
                draw,
                buffer,
                Vec4::new(
                    preview.x + preview.z - wall,
                    preview.y + preview.w / 4,
                    wall,
                    preview.w / 2,
                ),
                &door_color,
            );
        }
        if kind.is_stair() {
            let bands = 4;
            let band_gap = (preview.z.min(preview.w) / 20).max(2);
            for i in 0..bands {
                let t = i as i32;
                if kind.has_stair_north() {
                    let y = preview.y + preview.w - ((t + 1) * preview.w / (bands + 1));
                    Self::draw_panel_rect(
                        draw,
                        buffer,
                        Vec4::new(
                            preview.x + band_gap,
                            y,
                            preview.z - band_gap * 2,
                            wall.max(3),
                        ),
                        &stair_color,
                    );
                } else if kind.has_stair_south() {
                    let y = preview.y + (t + 1) * preview.w / (bands + 1);
                    Self::draw_panel_rect(
                        draw,
                        buffer,
                        Vec4::new(
                            preview.x + band_gap,
                            y,
                            preview.z - band_gap * 2,
                            wall.max(3),
                        ),
                        &stair_color,
                    );
                } else if kind.has_stair_east() {
                    let x = preview.x + (t + 1) * preview.z / (bands + 1);
                    Self::draw_panel_rect(
                        draw,
                        buffer,
                        Vec4::new(
                            x,
                            preview.y + band_gap,
                            wall.max(3),
                            preview.w - band_gap * 2,
                        ),
                        &stair_color,
                    );
                } else if kind.has_stair_west() {
                    let x = preview.x + preview.z - ((t + 1) * preview.z / (bands + 1));
                    Self::draw_panel_rect(
                        draw,
                        buffer,
                        Vec4::new(
                            x,
                            preview.y + band_gap,
                            wall.max(3),
                            preview.w - band_gap * 2,
                        ),
                        &stair_color,
                    );
                }
            }
        }
    }

    fn render_palette(&mut self, ui: &mut TheUI, ctx: &mut TheContext, server_ctx: &ServerContext) {
        let Some(render_view) = ui.get_render_view(DUNGEON_VIEW_ID) else {
            return;
        };
        let dim = *render_view.dim();
        if dim.width <= 0 || dim.height <= 0 {
            return;
        }
        if dim.width < 120 || dim.height < 80 {
            *render_view.render_buffer_mut() =
                TheRGBABuffer::new(TheDim::new(0, 0, dim.width, dim.height));
            render_view.render_buffer_mut().fill(BLACK);
            render_view.set_needs_redraw(true);
            ctx.ui.redraw_all = true;
            return;
        }

        *render_view.render_buffer_mut() =
            TheRGBABuffer::new(TheDim::new(0, 0, dim.width, dim.height));
        let buffer = render_view.render_buffer_mut();
        buffer.fill(BLACK);
        self.placements.clear();
        self.content_height = 0;

        let palette = Self::palette();
        let cols = ((dim.width - DUNGEON_MARGIN * 2 + DUNGEON_GAP)
            / (DUNGEON_CELL_MIN + DUNGEON_GAP))
            .max(1)
            .min(palette.len() as i32);
        let cell = ((dim.width - DUNGEON_MARGIN * 2 - (cols - 1) * DUNGEON_GAP) / cols)
            .clamp(DUNGEON_CELL_MIN, DUNGEON_CELL_MAX);
        for (index, kind) in palette.iter().copied().enumerate() {
            let col = index as i32 % cols;
            let row = index as i32 / cols;
            let rect = Vec4::new(
                DUNGEON_MARGIN + col * (cell + DUNGEON_GAP),
                DUNGEON_MARGIN + row * (cell + DUNGEON_GAP) - self.scroll_y,
                cell,
                cell,
            );
            self.placements.push(DungeonPalettePlacement { kind, rect });
            self.content_height = self
                .content_height
                .max(DUNGEON_MARGIN * 2 + (row + 1) * cell + row * DUNGEON_GAP);

            if rect.x >= dim.width
                || rect.y >= dim.height
                || rect.x + rect.z <= 0
                || rect.y + rect.w <= 0
            {
                continue;
            }

            self.draw_preview(
                &ctx.draw,
                buffer,
                rect,
                kind,
                server_ctx.curr_dungeon_tile == kind,
                self.hovered == Some(kind),
            );
        }

        render_view.set_needs_redraw(true);
        ctx.ui.redraw_all = true;
    }

    fn kind_at(&self, coord: Vec2<i32>) -> Option<DungeonTileKind> {
        self.placements
            .iter()
            .find(|placement| {
                coord.x >= placement.rect.x
                    && coord.x < placement.rect.x + placement.rect.z
                    && coord.y >= placement.rect.y
                    && coord.y < placement.rect.y + placement.rect.w
            })
            .map(|placement| placement.kind)
    }
}

impl Dock for DungeonDock {
    fn new() -> Self
    where
        Self: Sized,
    {
        Self {
            placements: Vec::new(),
            hovered: None,
            scroll_y: 0,
            content_height: 0,
        }
    }

    fn setup(&mut self, _ctx: &mut TheContext) -> TheCanvas {
        let mut canvas = TheCanvas::new();

        let mut toolbar_canvas = TheCanvas::default();
        toolbar_canvas.set_widget(TheTraybar::new(TheId::empty()));

        let mut toolbar_hlayout = TheHLayout::new(TheId::empty());
        toolbar_hlayout.set_background_color(None);
        toolbar_hlayout.set_margin(Vec4::new(10, 1, 5, 1));
        toolbar_hlayout.set_padding(3);

        let mut title = TheText::new(TheId::named("Dungeon Dock Title"));
        title.set_text("Dungeon".to_string());
        title.set_text_size(12.5);
        toolbar_hlayout.add_widget(Box::new(title));

        let spacer = TheSpacer::new(TheId::empty());
        toolbar_hlayout.add_widget(Box::new(spacer));
        toolbar_canvas.set_layout(toolbar_hlayout);
        canvas.set_top(toolbar_canvas);

        let mut center = TheCanvas::new();
        let mut palette_canvas = TheCanvas::new();
        palette_canvas.set_widget(TheRenderView::new(TheId::named(DUNGEON_VIEW_ID)));
        center.set_center(palette_canvas);

        let mut settings_canvas = TheCanvas::new();
        let mut textedit = TheTextAreaEdit::new(TheId::named(DUNGEON_SETTINGS_TOML));
        if let Some(bytes) = crate::Embedded::get("parser/TOML.sublime-syntax")
            && let Ok(source) = std::str::from_utf8(bytes.data.as_ref())
        {
            textedit.add_syntax_from_string(source);
            textedit.set_code_type("TOML");
        }
        if let Some(bytes) = crate::Embedded::get("parser/gruvbox-dark.tmTheme")
            && let Ok(source) = std::str::from_utf8(bytes.data.as_ref())
        {
            textedit.add_theme_from_string(source);
            textedit.set_code_theme("Gruvbox Dark");
        }
        textedit.set_continuous(true);
        textedit.display_line_number(false);
        textedit.use_global_statusbar(true);
        textedit.set_font_size(13.5);
        textedit.limiter_mut().set_max_width(DUNGEON_SETTINGS_WIDTH);
        textedit.limiter_mut().set_min_width(DUNGEON_SETTINGS_WIDTH);
        settings_canvas.set_widget(textedit);
        center.set_right(settings_canvas);
        canvas.set_center(center);

        canvas
    }

    fn activate(
        &mut self,
        ui: &mut TheUI,
        ctx: &mut TheContext,
        project: &Project,
        server_ctx: &mut ServerContext,
    ) {
        Self::load_settings_from_map(project, server_ctx);
        self.sync_settings_ui(ui, ctx, server_ctx);
        ctx.ui.send(TheEvent::Custom(
            TheId::named("Render Dungeon Palette"),
            TheValue::Empty,
        ));
    }

    fn handle_event(
        &mut self,
        event: &TheEvent,
        ui: &mut TheUI,
        ctx: &mut TheContext,
        _project: &mut Project,
        server_ctx: &mut ServerContext,
    ) -> bool {
        let mut redraw = false;

        match event {
            TheEvent::Custom(id, _) if id.name == "Render Dungeon Palette" => {
                self.render_palette(ui, ctx, server_ctx);
                redraw = true;
            }
            TheEvent::Resize => {
                let view_height = ui
                    .get_render_view(DUNGEON_VIEW_ID)
                    .map(|view| view.dim().height)
                    .unwrap_or(0);
                let max_scroll = (self.content_height - view_height).max(0);
                self.scroll_y = self.scroll_y.clamp(0, max_scroll);
                self.render_palette(ui, ctx, server_ctx);
                redraw = true;
            }
            TheEvent::RenderViewHoverChanged(id, coord) if id.name == DUNGEON_VIEW_ID => {
                self.hovered = self.kind_at(*coord);
                if let Some(kind) = self.hovered {
                    ctx.ui.send(TheEvent::SetStatusText(
                        id.clone(),
                        kind.label().to_string(),
                    ));
                }
                self.render_palette(ui, ctx, server_ctx);
                redraw = true;
            }
            TheEvent::RenderViewLostHover(id) if id.name == DUNGEON_VIEW_ID => {
                self.hovered = None;
                self.render_palette(ui, ctx, server_ctx);
                redraw = true;
            }
            TheEvent::RenderViewClicked(id, coord) if id.name == DUNGEON_VIEW_ID => {
                if let Some(kind) = self.kind_at(*coord) {
                    server_ctx.curr_dungeon_tile = kind;
                    self.sync_settings_ui(ui, ctx, server_ctx);
                    self.render_palette(ui, ctx, server_ctx);
                    redraw = true;
                }
            }
            TheEvent::RenderViewScrollBy(id, delta) if id.name == DUNGEON_VIEW_ID => {
                if !ui.ctrl && !ui.logo {
                    let view_height = ui
                        .get_render_view(DUNGEON_VIEW_ID)
                        .map(|view| view.dim().height)
                        .unwrap_or(0);
                    let max_scroll = (self.content_height - view_height).max(0);
                    self.scroll_y = (self.scroll_y + delta.y).clamp(0, max_scroll);
                    self.render_palette(ui, ctx, server_ctx);
                    if let Some(render_view) = ui.get_render_view(DUNGEON_VIEW_ID) {
                        render_view.set_needs_redraw(true);
                    }
                    ctx.ui.redraw_all = true;
                    redraw = true;
                }
            }
            TheEvent::ValueChanged(id, value) if id.name == DUNGEON_SETTINGS_TOML => {
                if let Some(source) = value.to_string() {
                    let mut nodeui = Self::settings_nodeui(server_ctx);
                    let doc = toml::from_str::<toml::Value>(&source);
                    if doc.is_ok() && apply_toml_to_nodeui(&mut nodeui, &source).is_ok() {
                        let mut rebuild_geometry = false;
                        let mut refresh_preview = false;
                        for (key, val) in nodeui_to_value_pairs(&nodeui) {
                            match (key.as_str(), val) {
                                ("Floor Base", TheValue::Float(v)) => {
                                    refresh_preview |=
                                        (server_ctx.curr_dungeon_floor_base - v).abs() > 0.0001;
                                    server_ctx.curr_dungeon_floor_base = v;
                                }
                                ("Height", TheValue::Float(v)) => {
                                    let v = v.max(0.1);
                                    refresh_preview |=
                                        (server_ctx.curr_dungeon_height - v).abs() > 0.0001;
                                    server_ctx.curr_dungeon_height = v;
                                }
                                ("Floors", TheValue::Bool(v)) => {
                                    rebuild_geometry |= server_ctx.curr_dungeon_create_floor != v;
                                    server_ctx.curr_dungeon_create_floor = v;
                                }
                                ("Ceilings", TheValue::Bool(v)) => {
                                    rebuild_geometry |= server_ctx.curr_dungeon_create_ceiling != v;
                                    server_ctx.curr_dungeon_create_ceiling = v;
                                }
                                ("Standalone", TheValue::Bool(v)) => {
                                    server_ctx.curr_dungeon_standalone = v;
                                }
                                ("Door Width", TheValue::Int(v)) => {
                                    server_ctx.curr_dungeon_tile_span = v.max(1);
                                }
                                ("Door Depth", TheValue::Float(v)) => {
                                    server_ctx.curr_dungeon_tile_depth = v.max(0.05);
                                }
                                ("Door Height", TheValue::Float(v)) => {
                                    server_ctx.curr_dungeon_tile_height = v.max(0.5);
                                }
                                ("Door Open Mode", TheValue::Int(v)) => {
                                    server_ctx.curr_dungeon_tile_open_mode = v.clamp(0, 5);
                                }
                                ("steps_floor_delta", TheValue::Float(v)) => {
                                    server_ctx.curr_dungeon_stair_target_floor_base = v;
                                }
                                ("steps_steps", TheValue::Int(v)) => {
                                    server_ctx.curr_dungeon_stair_steps = v.max(1);
                                }
                                ("steps_tile_mode", TheValue::Int(v)) => {
                                    server_ctx.curr_dungeon_stair_tile_mode =
                                        if v == 0 { 1 } else { 0 };
                                }
                                _ => {}
                            }
                        }
                        if server_ctx.curr_dungeon_tile.is_stair()
                            && let Some(tile_id) = nodeui.get_text_value("steps_tile_id")
                        {
                            server_ctx.curr_dungeon_stair_tile_id = tile_id.trim().to_string();
                        }
                        if let Some(item) = nodeui.get_text_value("Item") {
                            server_ctx.curr_dungeon_tile_item = item.trim().to_string();
                        }
                        if let Ok(doc) = doc
                            && let Some(render_table) =
                                doc.get("render").and_then(toml::Value::as_table)
                        {
                            let render_body =
                                toml::to_string_pretty(render_table).unwrap_or_default();
                            server_ctx.curr_dungeon_render_toml =
                                format!("[render]\n{}", render_body);
                        }

                        Self::store_settings_to_map(_project, server_ctx);

                        if refresh_preview {
                            let layer = _project
                                .get_map_mut(server_ctx)
                                .map(|map| map.dungeon.ensure_active_layer_mut());
                            if let Some(layer) = layer {
                                layer.floor_base = server_ctx.curr_dungeon_floor_base;
                                layer.height = server_ctx.curr_dungeon_height;
                            }
                            crate::editor::RUSTERIX.write().unwrap().set_dirty();
                            ctx.ui.redraw_all = true;
                            redraw = true;
                        }

                        if rebuild_geometry && let Some(map) = _project.get_map_mut(server_ctx) {
                            rebuild_generated_geometry(
                                map,
                                server_ctx.curr_dungeon_create_floor,
                                server_ctx.curr_dungeon_create_ceiling,
                            );
                            map.changed += 1;
                            crate::utils::scenemanager_render_map(_project, server_ctx);
                            crate::editor::RUSTERIX.write().unwrap().set_dirty();
                            redraw = true;
                        }
                    }
                }
            }
            _ => {}
        }

        redraw
    }

    fn supports_actions(&self) -> bool {
        false
    }
}
