use crate::editor::{ACTIONLIST, RUSTERIX, TOOLLIST, UNDOMANAGER};
use crate::prelude::*;
use crate::undo::project_helper::{apply_palette, refresh_palette_runtime};
use rusterix::PixelSource;
use shared::project::PaletteMaterial;

const PALETTE_DOCK_PICKER: &str = "Palette Dock Picker";
const PALETTE_DOCK_HEX: &str = "Palette Dock Hex Edit";
const PALETTE_DOCK_ROUGHNESS: &str = "Palette Dock Roughness";
const PALETTE_DOCK_METALLIC: &str = "Palette Dock Metallic";
const PALETTE_DOCK_OPACITY: &str = "Palette Dock Opacity";
const PALETTE_DOCK_EMISSIVE: &str = "Palette Dock Emissive";

pub(crate) struct PaletteDockBoard {
    id: TheId,
    limiter: TheSizeLimiter,
    dim: TheDim,
    is_dirty: bool,
    palette: ThePalette,
    materials: Vec<PaletteMaterial>,
    index: usize,
    drag_index: Option<usize>,
    hovered_index: Option<usize>,
    rectangles: Vec<(usize, TheDim)>,
}

impl PaletteDockBoard {
    fn slot_is_occupied(color: &Option<TheColor>) -> bool {
        color.as_ref().is_some_and(|c| c.a > f32::EPSILON)
    }

    fn new(id: TheId) -> Self {
        let mut limiter = TheSizeLimiter::new();
        limiter.set_max_size(Vec2::new(i32::MAX, i32::MAX));
        Self {
            id,
            limiter,
            dim: TheDim::zero(),
            is_dirty: true,
            palette: ThePalette::default(),
            materials: vec![PaletteMaterial::default(); 256],
            index: 0,
            drag_index: None,
            hovered_index: None,
            rectangles: Vec::new(),
        }
    }

    pub(crate) fn set_palette(&mut self, palette: ThePalette) {
        self.palette = palette;
        self.is_dirty = true;
    }

    pub(crate) fn set_index(&mut self, index: usize) {
        self.index = index.min(self.palette.colors.len().saturating_sub(1));
        self.is_dirty = true;
    }

    pub(crate) fn set_materials(&mut self, materials: Vec<PaletteMaterial>) {
        self.materials = materials;
        self.is_dirty = true;
    }

    fn visible_count(&self) -> usize {
        self.palette
            .colors
            .iter()
            .rposition(Self::slot_is_occupied)
            .map(|i| i + 1)
            .unwrap_or(1)
    }

    fn calc_layout(&self) -> (i32, i32, i32) {
        const PAD_X: i32 = 10;
        const PAD_Y: i32 = 8;
        const SPACING: i32 = 1;
        const MIN_CELL: i32 = 14;

        let count = self.visible_count();
        let aw = (self.dim.width - PAD_X * 2).max(MIN_CELL);
        let ah = (self.dim.height - PAD_Y * 2).max(MIN_CELL);
        let max_cols = ((aw + SPACING) / (MIN_CELL + SPACING))
            .max(1)
            .min(count as i32);

        let mut best = (1, count as i32, MIN_CELL);
        for cols in 1..=max_cols {
            let rows = (count as i32 + cols - 1) / cols;
            let cell_w = (aw - (cols - 1) * SPACING) / cols;
            let cell_h = (ah - (rows - 1) * SPACING) / rows;
            let cell = cell_w.min(cell_h);
            if cell < MIN_CELL {
                continue;
            }
            if cell > best.2 || (cell == best.2 && cols > best.0) {
                best = (cols, rows, cell);
            }
        }
        best
    }
}

impl TheWidget for PaletteDockBoard {
    fn new(id: TheId) -> Self
    where
        Self: Sized,
    {
        Self::new(id)
    }

    fn id(&self) -> &TheId {
        &self.id
    }

    fn dim(&self) -> &TheDim {
        &self.dim
    }

    fn dim_mut(&mut self) -> &mut TheDim {
        &mut self.dim
    }

    fn set_dim(&mut self, dim: TheDim, _ctx: &mut TheContext) {
        if self.dim != dim {
            self.dim = dim;
            self.is_dirty = true;
        }
    }

    fn limiter(&self) -> &TheSizeLimiter {
        &self.limiter
    }

    fn limiter_mut(&mut self) -> &mut TheSizeLimiter {
        &mut self.limiter
    }

    fn needs_redraw(&mut self) -> bool {
        self.is_dirty
    }

    fn on_event(&mut self, event: &TheEvent, ctx: &mut TheContext) -> bool {
        match event {
            TheEvent::MouseDown(coord) => {
                for (index, rect) in &self.rectangles {
                    if rect.contains(*coord) {
                        ctx.ui.set_focus(self.id());
                        self.drag_index = Some(*index);
                        self.index = *index;
                        ctx.ui.send(TheEvent::PaletteIndexChanged(
                            self.id.clone(),
                            *index as u16,
                        ));
                        self.is_dirty = true;
                        return true;
                    }
                }
            }
            TheEvent::Hover(coord) => {
                let mut hovered = None;
                for (index, rect) in &self.rectangles {
                    if rect.contains(*coord) {
                        hovered = Some(*index);
                        break;
                    }
                }
                if hovered != self.hovered_index {
                    self.hovered_index = hovered;
                    let text = hovered
                        .map(|index| {
                            crate::undo::project_helper::palette_status_text(
                                index,
                                self.palette.colors.get(index).and_then(|c| c.as_ref()),
                                self.materials.get(index),
                            )
                        })
                        .unwrap_or_default();
                    ctx.ui.send(TheEvent::SetStatusText(self.id.clone(), text));
                    self.is_dirty = true;
                    return true;
                }
            }
            TheEvent::MouseUp(coord) => {
                if let Some(from) = self.drag_index.take() {
                    for (to, rect) in &self.rectangles {
                        if rect.contains(*coord) {
                            if from != *to {
                                ctx.ui.send(TheEvent::PaletteEntriesSwapped(
                                    self.id.clone(),
                                    from as u16,
                                    *to as u16,
                                ));
                            }
                            break;
                        }
                    }
                }
            }
            TheEvent::LostHover(_id) => {
                self.hovered_index = None;
                ctx.ui
                    .send(TheEvent::SetStatusText(self.id.clone(), String::new()));
            }
            TheEvent::KeyCodeDown(TheValue::KeyCode(TheKeyCode::Delete)) => {
                ctx.ui.send(TheEvent::Custom(
                    TheId::named("Palette Dock Delete Entry"),
                    TheValue::Int(self.index as i32),
                ));
                return true;
            }
            _ => {}
        }
        false
    }

    fn draw(
        &mut self,
        buffer: &mut TheRGBABuffer,
        style: &mut Box<dyn TheStyle>,
        ctx: &mut TheContext,
    ) {
        if !self.dim.is_valid() {
            return;
        }

        let utuple = self.dim.to_buffer_utuple();
        let stride = buffer.stride();
        ctx.draw.rect(
            buffer.pixels_mut(),
            &utuple,
            stride,
            style.theme().color(ListLayoutBackground),
        );

        let (columns, rows, item_width) = self.calc_layout();
        let item_spacing = 1usize;
        let mut index = 0usize;
        let count = self.visible_count();
        self.rectangles.clear();

        let mut y_off = 8usize;
        for _ in 0..rows {
            let mut x_off = 10usize;
            for _ in 0..columns {
                if index >= count {
                    break;
                }
                let outer_rect = (
                    utuple.0 + x_off,
                    utuple.1 + y_off,
                    item_width as usize,
                    item_width as usize,
                );
                let inner_border_rect = (
                    utuple.0 + x_off + 1,
                    utuple.1 + y_off + 1,
                    (item_width as usize).saturating_sub(2),
                    (item_width as usize).saturating_sub(2),
                );
                let fill_rect = (
                    utuple.0 + x_off + 2,
                    utuple.1 + y_off + 2,
                    (item_width as usize).saturating_sub(4),
                    (item_width as usize).saturating_sub(4),
                );
                if self.index == index {
                    ctx.draw
                        .rect_outline(buffer.pixels_mut(), &outer_rect, stride, &WHITE);
                }
                ctx.draw
                    .rect_outline(buffer.pixels_mut(), &inner_border_rect, stride, &BLACK);
                if let Some(Some(color)) = self.palette.colors.get(index) {
                    ctx.draw.rect(
                        buffer.pixels_mut(),
                        &fill_rect,
                        stride,
                        &color.to_u8_array(),
                    );
                }
                self.rectangles.push((
                    index,
                    TheDim::new(x_off as i32, y_off as i32, item_width, item_width),
                ));
                index += 1;
                x_off += item_width as usize + item_spacing;
            }
            y_off += item_width as usize + item_spacing;
            if index >= count {
                break;
            }
        }
        self.is_dirty = false;
    }

    fn as_any(&mut self) -> &mut dyn std::any::Any {
        self
    }
}

pub struct PaletteDock {
    nodeui: TheNodeUI,
}

impl PaletteDock {
    fn make_opaque(color: &mut Option<TheColor>) {
        if let Some(color) = color {
            color.a = 1.0;
        }
    }

    fn normalize_palette(project: &mut Project) -> bool {
        let mut changed = false;
        project.ensure_palette_materials_len();
        for (index, color) in project.palette.colors.iter_mut().enumerate() {
            if !PaletteDockBoard::slot_is_occupied(color) && color.is_some() {
                *color = None;
                if let Some(material) = project.palette_materials.get_mut(index) {
                    *material = PaletteMaterial::default();
                }
                changed = true;
            }
        }
        changed
    }

    fn append_index(project: &Project) -> Option<usize> {
        let end = project
            .palette
            .colors
            .iter()
            .rposition(PaletteDockBoard::slot_is_occupied)
            .map(|i| i + 1)
            .unwrap_or(0);
        (end < project.palette.colors.len()).then_some(end)
    }

    fn build_nodeui() -> TheNodeUI {
        let mut nodeui = TheNodeUI::default();
        nodeui.add_item(TheNodeUIItem::Text(
            PALETTE_DOCK_HEX.into(),
            "Hex".into(),
            "".into(),
            "".into(),
            None,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
            PALETTE_DOCK_ROUGHNESS.into(),
            fl!("roughness").into(),
            "".into(),
            0.5,
            0.0..=1.0,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
            PALETTE_DOCK_METALLIC.into(),
            fl!("metallic").into(),
            "".into(),
            0.0,
            0.0..=1.0,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
            PALETTE_DOCK_OPACITY.into(),
            fl!("opacity").into(),
            "".into(),
            1.0,
            0.0..=1.0,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
            PALETTE_DOCK_EMISSIVE.into(),
            fl!("emissive").into(),
            "".into(),
            0.0,
            0.0..=1.0,
            false,
        ));
        nodeui
    }

    fn palette_undo_atom(
        prev_palette: ThePalette,
        prev_materials: Vec<PaletteMaterial>,
        project: &Project,
    ) -> ProjectUndoAtom {
        ProjectUndoAtom::PaletteEdit(
            prev_palette,
            prev_materials,
            project.palette.clone(),
            project.palette_materials.clone(),
        )
    }

    fn sync_widgets(&mut self, ui: &mut TheUI, ctx: &mut TheContext, project: &Project) {
        let index = project.palette.current_index as usize;
        if let Some(widget) = ui.get_widget(PALETTE_DOCK_PICKER)
            && let Some(board) = widget.as_any().downcast_mut::<PaletteDockBoard>()
        {
            board.set_palette(project.palette.clone());
            board.set_materials(project.palette_materials.clone());
            board.set_index(index);
        }
        let text = project.palette[index]
            .as_ref()
            .map(TheColor::to_hex)
            .unwrap_or_default();
        self.nodeui.set_text_value(PALETTE_DOCK_HEX, text);
        let material = project
            .palette_materials
            .get(index)
            .cloned()
            .unwrap_or_default();
        self.nodeui
            .set_f32_value(PALETTE_DOCK_ROUGHNESS, material.roughness);
        self.nodeui
            .set_f32_value(PALETTE_DOCK_METALLIC, material.metallic);
        self.nodeui
            .set_f32_value(PALETTE_DOCK_OPACITY, material.opacity);
        self.nodeui
            .set_f32_value(PALETTE_DOCK_EMISSIVE, material.emissive);

        if let Some(layout) = ui.get_text_layout("Palette Dock Inspector Layout") {
            self.nodeui.apply_to_text_layout(layout);
            ctx.ui.relayout = true;
        }
    }

    fn current_selection_tool_type(project: &Project, server_ctx: &ServerContext) -> MapToolType {
        if let Some(map) = project.get_map(server_ctx) {
            if !map.selected_vertices.is_empty() {
                MapToolType::Vertex
            } else if !map.selected_linedefs.is_empty() {
                MapToolType::Linedef
            } else if !map.selected_sectors.is_empty() {
                MapToolType::Sector
            } else {
                MapToolType::Sector
            }
        } else {
            MapToolType::Sector
        }
    }

    fn apply_current_palette_color(
        &self,
        project: &mut Project,
        ctx: &mut TheContext,
        server_ctx: &mut ServerContext,
    ) {
        let source = PixelSource::PaletteIndex(project.palette.current_index);
        server_ctx.curr_map_tool_type = Self::current_selection_tool_type(project, server_ctx);

        let mut undo_atom: Option<ProjectUndoAtom> = None;
        let mut needs_scene_redraw = false;

        if let Some(map) = project.get_map_mut(server_ctx) {
            let prev = map.clone();
            if crate::actions::apply_builder_hud_material_to_selection(
                map,
                server_ctx,
                server_ctx.selected_hud_icon_index,
                Some(source.clone()),
            ) {
                undo_atom = Some(ProjectUndoAtom::MapEdit(
                    server_ctx.pc,
                    Box::new(prev),
                    Box::new(map.clone()),
                ));
                needs_scene_redraw = true;
            } else {
                let mut changed = false;
                for sector_id in map.selected_sectors.clone() {
                    let mut source_key = "source";
                    if server_ctx.pc.is_screen() && server_ctx.selected_hud_icon_index == 1 {
                        source_key = "ceiling_source";
                    }
                    changed |= crate::utils::apply_surface_source_to_sector(
                        map,
                        sector_id,
                        source_key,
                        &crate::utils::SurfaceApplySource::Direct(source.clone()),
                        None,
                    );
                }
                if changed {
                    map.update_surfaces();
                    RUSTERIX.write().unwrap().set_dirty();
                    undo_atom = Some(ProjectUndoAtom::MapEdit(
                        server_ctx.pc,
                        Box::new(prev),
                        Box::new(map.clone()),
                    ));
                    needs_scene_redraw = true;
                }
            }
        }

        if needs_scene_redraw {
            crate::utils::scenemanager_render_map(project, server_ctx);
            TOOLLIST
                .write()
                .unwrap()
                .update_geometry_overlay_3d(project, server_ctx);
        }
        if let Some(undo_atom) = undo_atom {
            UNDOMANAGER.write().unwrap().add_undo(undo_atom, ctx);
            ctx.ui.send(TheEvent::Custom(
                TheId::named("Update Minimap"),
                TheValue::Empty,
            ));
        }
    }

    fn clear_current_palette_color(
        &self,
        project: &mut Project,
        ctx: &mut TheContext,
        server_ctx: &mut ServerContext,
    ) {
        server_ctx.curr_map_tool_type = Self::current_selection_tool_type(project, server_ctx);

        let mut cleared_action_slot = false;
        let mut undo_atom: Option<ProjectUndoAtom> = None;
        let mut needs_scene_redraw = false;

        if let Some(map) = project.get_map_mut(server_ctx) {
            let prev = map.clone();
            if crate::actions::apply_builder_hud_material_to_selection(
                map,
                server_ctx,
                server_ctx.selected_hud_icon_index,
                None,
            ) {
                undo_atom = Some(ProjectUndoAtom::MapEdit(
                    server_ctx.pc,
                    Box::new(prev),
                    Box::new(map.clone()),
                ));
                needs_scene_redraw = true;
                cleared_action_slot = true;
            }
        }

        if !cleared_action_slot
            && server_ctx.get_map_context() == MapContext::Region
            && let Some(map) = project.get_map(server_ctx)
            && let Some(action_id) = server_ctx.curr_action_id
            && let Some(action) = ACTIONLIST.write().unwrap().get_action_by_id_mut(action_id)
            && action.hud_material_slots(map, server_ctx).is_some()
            && action.clear_hud_material_slot(map, server_ctx, server_ctx.selected_hud_icon_index)
        {
            ctx.ui.send(TheEvent::Custom(
                TheId::named("Refresh Action Parameters"),
                TheValue::Empty,
            ));
            cleared_action_slot = true;
        }

        if !cleared_action_slot && let Some(map) = project.get_map_mut(server_ctx) {
            let mut changed = false;
            let prev = map.clone();
            for sector_id in map.selected_sectors.clone() {
                let mut source_key = "source";
                if server_ctx.pc.is_screen() && server_ctx.selected_hud_icon_index == 1 {
                    source_key = "ceiling_source";
                }
                changed |= crate::utils::clear_surface_source_on_sector(map, sector_id, source_key);
            }

            if changed {
                map.update_surfaces();
                RUSTERIX.write().unwrap().set_dirty();
                undo_atom = Some(ProjectUndoAtom::MapEdit(
                    server_ctx.pc,
                    Box::new(prev),
                    Box::new(map.clone()),
                ));
                needs_scene_redraw = true;
            }
        }

        if needs_scene_redraw {
            crate::utils::scenemanager_render_map(project, server_ctx);
            TOOLLIST
                .write()
                .unwrap()
                .update_geometry_overlay_3d(project, server_ctx);
        }
        if let Some(undo_atom) = undo_atom {
            UNDOMANAGER.write().unwrap().add_undo(undo_atom, ctx);
            ctx.ui.send(TheEvent::Custom(
                TheId::named("Update Minimap"),
                TheValue::Empty,
            ));
        }
    }
}

impl Dock for PaletteDock {
    fn new() -> Self
    where
        Self: Sized,
    {
        Self {
            nodeui: Self::build_nodeui(),
        }
    }

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

        let mut top_canvas = TheCanvas::default();
        top_canvas.set_widget(TheTraybar::new(TheId::empty()));
        let mut top_layout = TheHLayout::new(TheId::empty());
        top_layout.set_background_color(None);
        top_layout.set_margin(Vec4::new(10, 1, 5, 1));
        top_layout.set_padding(3);

        let mut apply = TheTraybarButton::new(TheId::named("Palette Dock Apply Color"));
        apply.set_text(fl!("palette_apply_color"));
        apply.set_status_text(&fl!("status_palette_apply_color"));
        let mut new_button = TheTraybarButton::new(TheId::named("Palette Dock New"));
        new_button.set_text(fl!("new"));
        top_layout.add_widget(Box::new(new_button));

        let mut clone_button = TheTraybarButton::new(TheId::named("Palette Dock Clone"));
        clone_button.set_text(fl!("action_duplicate"));
        top_layout.add_widget(Box::new(clone_button));

        top_layout.add_widget(Box::new(apply));

        let mut clear = TheTraybarButton::new(TheId::named("Palette Dock Clear Color"));
        clear.set_text(fl!("clear"));
        clear.set_status_text(&fl!("status_tiles_clear_tile"));
        top_layout.add_widget(Box::new(clear));
        top_layout.set_reverse_index(Some(2));
        top_canvas.set_layout(top_layout);
        canvas.set_top(top_canvas);

        let mut center = TheCanvas::new();

        let mut picker_canvas = TheCanvas::new();
        picker_canvas.set_widget(PaletteDockBoard::new(TheId::named(PALETTE_DOCK_PICKER)));
        center.set_center(picker_canvas);

        let mut inspector_canvas = TheCanvas::new();
        inspector_canvas.limiter_mut().set_min_width(300);
        inspector_canvas.limiter_mut().set_max_width(300);
        let mut inspector = TheTextLayout::new(TheId::named("Palette Dock Inspector Layout"));
        inspector.limiter_mut().set_min_width(300);
        inspector.limiter_mut().set_max_width(300);
        inspector.set_text_margin(20);
        inspector.set_text_align(TheHorizontalAlign::Right);
        inspector_canvas.set_layout(inspector);
        center.set_right(inspector_canvas);

        canvas.set_center(center);
        canvas
    }

    fn activate(
        &mut self,
        ui: &mut TheUI,
        ctx: &mut TheContext,
        project: &Project,
        _server_ctx: &mut ServerContext,
    ) {
        self.sync_widgets(ui, ctx, project);
    }

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

    fn handle_event(
        &mut self,
        event: &TheEvent,
        ui: &mut TheUI,
        ctx: &mut TheContext,
        project: &mut Project,
        server_ctx: &mut ServerContext,
    ) -> bool {
        if Self::normalize_palette(project) {
            apply_palette(ui, ctx, server_ctx, project);
            refresh_palette_runtime(project);
        }
        match event {
            TheEvent::PaletteEntriesSwapped(id, from, to) if id.name == PALETTE_DOCK_PICKER => {
                let from = *from as usize;
                let to = *to as usize;
                if from < project.palette.colors.len()
                    && to < project.palette.colors.len()
                    && from != to
                {
                    let prev = project.palette.clone();
                    let prev_materials = project.palette_materials.clone();
                    project.palette.colors.swap(from, to);
                    project.palette_materials.swap(from, to);
                    Self::make_opaque(&mut project.palette.colors[from]);
                    Self::make_opaque(&mut project.palette.colors[to]);
                    project.palette.current_index = to as u16;
                    apply_palette(ui, ctx, server_ctx, project);
                    refresh_palette_runtime(project);
                    self.sync_widgets(ui, ctx, project);
                    UNDOMANAGER
                        .write()
                        .unwrap()
                        .add_undo(Self::palette_undo_atom(prev, prev_materials, project), ctx);
                }
                true
            }
            TheEvent::PaletteIndexChanged(id, index) if id.name == PALETTE_DOCK_PICKER => {
                project.palette.current_index = *index;
                project.ensure_palette_materials_len();
                apply_palette(ui, ctx, server_ctx, project);
                self.sync_widgets(ui, ctx, project);
                true
            }
            TheEvent::ValueChanged(id, TheValue::Text(text)) if id.name == PALETTE_DOCK_HEX => {
                let color = TheColor::from_hex(text);
                let index = project.palette.current_index as usize;
                project.ensure_palette_materials_len();
                if project.palette[index] != Some(color.clone()) {
                    let prev = project.palette.clone();
                    let prev_materials = project.palette_materials.clone();
                    project.palette[index] = Some(color);
                    apply_palette(ui, ctx, server_ctx, project);
                    refresh_palette_runtime(project);
                    self.sync_widgets(ui, ctx, project);
                    UNDOMANAGER
                        .write()
                        .unwrap()
                        .add_undo(Self::palette_undo_atom(prev, prev_materials, project), ctx);
                }
                true
            }
            TheEvent::ValueChanged(id, value)
                if matches!(
                    id.name.as_str(),
                    PALETTE_DOCK_ROUGHNESS
                        | PALETTE_DOCK_METALLIC
                        | PALETTE_DOCK_OPACITY
                        | PALETTE_DOCK_EMISSIVE
                ) =>
            {
                let index = project.palette.current_index as usize;
                project.ensure_palette_materials_len();
                let prev = project.palette.clone();
                let prev_materials = project.palette_materials.clone();
                let material = &mut project.palette_materials[index];
                let Some(value) = (match value {
                    TheValue::Float(v) => Some(*v),
                    TheValue::FloatRange(v, _) => Some(*v),
                    TheValue::Text(text) => text.parse::<f32>().ok(),
                    _ => None,
                }) else {
                    return false;
                };
                let value = value.clamp(0.0, 1.0);
                let changed = match id.name.as_str() {
                    PALETTE_DOCK_ROUGHNESS => {
                        let changed = (material.roughness - value).abs() > f32::EPSILON;
                        material.roughness = value;
                        changed
                    }
                    PALETTE_DOCK_METALLIC => {
                        let changed = (material.metallic - value).abs() > f32::EPSILON;
                        material.metallic = value;
                        changed
                    }
                    PALETTE_DOCK_OPACITY => {
                        let changed = (material.opacity - value).abs() > f32::EPSILON;
                        material.opacity = value;
                        changed
                    }
                    PALETTE_DOCK_EMISSIVE => {
                        let changed = (material.emissive - value).abs() > f32::EPSILON;
                        material.emissive = value;
                        changed
                    }
                    _ => false,
                };
                if changed {
                    refresh_palette_runtime(project);
                    UNDOMANAGER
                        .write()
                        .unwrap()
                        .add_undo(Self::palette_undo_atom(prev, prev_materials, project), ctx);
                }
                true
            }
            TheEvent::StateChanged(id, TheWidgetState::Clicked) => {
                if id.name == "Palette Dock New" {
                    if let Some(index) = Self::append_index(project) {
                        let prev = project.palette.clone();
                        let prev_materials = project.palette_materials.clone();
                        project.ensure_palette_materials_len();
                        project.palette.colors[index] = Some(TheColor::from_u8(0, 0, 0, 255));
                        if let Some(material) = project.palette_materials.get_mut(index) {
                            *material = PaletteMaterial::default();
                        }
                        project.palette.current_index = index as u16;
                        apply_palette(ui, ctx, server_ctx, project);
                        refresh_palette_runtime(project);
                        self.sync_widgets(ui, ctx, project);
                        UNDOMANAGER
                            .write()
                            .unwrap()
                            .add_undo(Self::palette_undo_atom(prev, prev_materials, project), ctx);
                    }
                    true
                } else if id.name == "Palette Dock Clone" {
                    if let Some(index) = Self::append_index(project) {
                        let prev = project.palette.clone();
                        let prev_materials = project.palette_materials.clone();
                        let src = project.palette.current_index as usize;
                        project.ensure_palette_materials_len();
                        project.palette.colors[index] = project.palette.colors[src].clone();
                        Self::make_opaque(&mut project.palette.colors[index]);
                        if let Some(src_material) = project.palette_materials.get(src).cloned()
                            && let Some(dst_material) = project.palette_materials.get_mut(index)
                        {
                            *dst_material = src_material;
                        }
                        project.palette.current_index = index as u16;
                        apply_palette(ui, ctx, server_ctx, project);
                        refresh_palette_runtime(project);
                        self.sync_widgets(ui, ctx, project);
                        UNDOMANAGER
                            .write()
                            .unwrap()
                            .add_undo(Self::palette_undo_atom(prev, prev_materials, project), ctx);
                    }
                    true
                } else if id.name == "Palette Dock Apply Color" {
                    self.apply_current_palette_color(project, ctx, server_ctx);
                    true
                } else if id.name == "Palette Dock Clear Color" {
                    self.clear_current_palette_color(project, ctx, server_ctx);
                    true
                } else {
                    false
                }
            }
            TheEvent::Custom(id, TheValue::Int(index))
                if id.name == "Palette Dock Delete Entry" =>
            {
                let index = *index as usize;
                if index < project.palette.colors.len() {
                    let prev = project.palette.clone();
                    let prev_materials = project.palette_materials.clone();
                    project.ensure_palette_materials_len();
                    project.palette.colors[index] = None;
                    if let Some(material) = project.palette_materials.get_mut(index) {
                        *material = PaletteMaterial::default();
                    }
                    if project.palette.current_index as usize == index {
                        project.palette.current_index = index.saturating_sub(1) as u16;
                    }
                    apply_palette(ui, ctx, server_ctx, project);
                    refresh_palette_runtime(project);
                    self.sync_widgets(ui, ctx, project);
                    UNDOMANAGER
                        .write()
                        .unwrap()
                        .add_undo(Self::palette_undo_atom(prev, prev_materials, project), ctx);
                }
                true
            }
            _ => false,
        }
    }
}
