use std::path::PathBuf;

use editor_ui::inspector::{ComponentCardOptions, ComponentCardViewModel};
use editor_ui::materials::{
    material_input_schema_editor, material_slot_panel, material_surface_options_ui,
    materials_section, MaterialHealth, MaterialSlotAction, MaterialThumbnailPresentation,
    MaterialsSectionViewModel,
};
use editor_ui::renderer::{
    renderer_array_header, renderer_material_slot_row, renderer_material_slots_header,
    renderer_panel, renderer_panel_header, renderer_properties_header,
    RendererPanelHeaderViewModel,
};
use egui_phosphor_icons::icons;
use material_schema::{standard_lit_input_schema, MaterialShaderKind};

use crate::fixtures::{FixtureId, FixtureModel};
use crate::reference::{
    difference_image, ComparisonMode, DifferenceStats, GalleryPreset, ReferenceImage,
};
use crate::reload::RuntimeDocuments;

pub struct UiGalleryApp {
    preset: GalleryPreset,
    comparison: ComparisonMode,
    overlay_opacity: f32,
    fixture_id: FixtureId,
    fixture: FixtureModel,
    fixture_textures: Vec<egui::TextureHandle>,
    documents: RuntimeDocuments,
    references: Vec<(GalleryPreset, ReferenceImage)>,
    reference_error: Option<String>,
    last_live_rect: Option<egui::Rect>,
    last_reference_rect: Option<egui::Rect>,
    difference_texture: Option<egui::TextureHandle>,
    difference_stats: Option<DifferenceStats>,
    difference_dirty: bool,
    capture: Option<CaptureRequest>,
    renderer_open: [bool; 3],
}

#[derive(Clone, Copy)]
struct CaptureRequest {
    preset: GalleryPreset,
    live_rect: egui::Rect,
    reference_rect: egui::Rect,
    pixels_per_point: f32,
}

impl UiGalleryApp {
    pub fn new(creation: &eframe::CreationContext<'_>) -> Self {
        creation
            .egui_ctx
            .set_fonts(editor_ui::fonts::font_definitions());
        editor_ui::theme::apply_editor_theme(&creation.egui_ctx);
        let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .join("../..")
            .canonicalize()
            .unwrap_or_else(|_| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.."));
        let mut documents = RuntimeDocuments::new(&root);
        documents.poll(0.0);
        if let Ok(palette) = documents.theme_value.palette() {
            editor_ui::theme::apply_editor_theme_with_palette(&creation.egui_ctx, palette);
        }
        let fixture_textures = make_fixture_textures(&creation.egui_ctx);
        let (material_thumbnail, texture_thumbnails) = fixture_thumbnail_ids(&fixture_textures);
        let fixture_id = FixtureId::Reference;
        let fixture = FixtureModel::new(
            fixture_id,
            &documents.fixture_value,
            material_thumbnail,
            texture_thumbnails,
        );
        let mut references = Vec::new();
        let mut reference_error = None;
        for preset in GalleryPreset::ALL {
            match ReferenceImage::load(&creation.egui_ctx, &root, preset) {
                Ok(reference) => references.push((preset, reference)),
                Err(error) => reference_error = Some(error),
            }
        }
        Self {
            preset: GalleryPreset::Materials620,
            comparison: ComparisonMode::SideBySide,
            overlay_opacity: 0.5,
            fixture_id,
            fixture,
            fixture_textures,
            documents,
            references,
            reference_error,
            last_live_rect: None,
            last_reference_rect: None,
            difference_texture: None,
            difference_stats: None,
            difference_dirty: true,
            capture: None,
            renderer_open: [true, false, false],
        }
    }

    fn rebuild_fixture(&mut self, ctx: &egui::Context) {
        let (material_thumbnail, texture_thumbnails) =
            fixture_thumbnail_ids(&self.fixture_textures);
        self.fixture = FixtureModel::new(
            self.fixture_id,
            &self.documents.fixture_value,
            material_thumbnail,
            texture_thumbnails,
        );
        ctx.request_repaint();
        self.difference_dirty = true;
    }

    fn select_preset(&mut self, ctx: &egui::Context, preset: GalleryPreset) {
        if self.preset == preset {
            return;
        }
        self.preset = preset;
        if preset.is_renderer() && !self.fixture_id.is_renderer() {
            self.fixture_id = FixtureId::StaticMesh;
            self.rebuild_fixture(ctx);
        }
        self.difference_dirty = true;
    }

    fn select_fixture(&mut self, ctx: &egui::Context, fixture: FixtureId) {
        if self.fixture_id == fixture {
            return;
        }
        self.fixture_id = fixture;
        self.rebuild_fixture(ctx);
    }

    fn select_comparison(&mut self, comparison: ComparisonMode) {
        if self.comparison == comparison {
            return;
        }
        self.comparison = comparison;
        self.difference_dirty = true;
    }

    fn handle_shortcuts(&mut self, ctx: &egui::Context) {
        let (function_key, shift, previous_fixture, next_fixture) = ctx.input(|input| {
            let function_key = [egui::Key::F1, egui::Key::F2, egui::Key::F3, egui::Key::F4]
                .into_iter()
                .position(|key| input.key_pressed(key));
            (
                function_key,
                input.modifiers.shift,
                input.key_pressed(egui::Key::PageUp),
                input.key_pressed(egui::Key::PageDown),
            )
        });
        if let Some(index) = function_key {
            if shift {
                self.select_comparison(ComparisonMode::ALL[index]);
            } else {
                self.select_preset(ctx, GalleryPreset::ALL[index]);
            }
        }
        if previous_fixture || next_fixture {
            let current = FixtureId::ALL
                .iter()
                .position(|fixture| *fixture == self.fixture_id)
                .unwrap_or_default();
            let next = if previous_fixture {
                current.checked_sub(1).unwrap_or(FixtureId::ALL.len() - 1)
            } else {
                (current + 1) % FixtureId::ALL.len()
            };
            self.select_fixture(ctx, FixtureId::ALL[next]);
        }
    }

    fn reference(&self) -> Option<&ReferenceImage> {
        self.references
            .iter()
            .find_map(|(preset, image)| (*preset == self.preset).then_some(image))
    }

    fn handle_screenshot_events(&mut self, ctx: &egui::Context) {
        let screenshots = ctx.input(|input| {
            input
                .raw
                .events
                .iter()
                .filter_map(|event| match event {
                    egui::Event::Screenshot { image, .. } => Some(image.clone()),
                    _ => None,
                })
                .collect::<Vec<_>>()
        });
        let Some(request) = self.capture.take() else {
            return;
        };
        let Some(screenshot) = screenshots.last() else {
            self.capture = Some(request);
            return;
        };
        let (difference, stats) = difference_image(
            screenshot,
            request.live_rect,
            request.reference_rect,
            request.pixels_per_point,
            request.preset,
        );
        if let Some(texture) = &mut self.difference_texture {
            texture.set(difference, egui::TextureOptions::NEAREST);
        } else {
            self.difference_texture = Some(ctx.load_texture(
                "blacksite-ui-gallery-difference",
                difference,
                egui::TextureOptions::NEAREST,
            ));
        }
        self.difference_stats = Some(stats);
        self.difference_dirty = false;
    }

    fn toolbar(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) {
        ui.horizontal_wrapped(|ui| {
            ui.strong("Blacksite UI Gallery");
            ui.separator();
            egui::ComboBox::from_id_salt("gallery_preset")
                .selected_text(self.preset.label())
                .show_ui(ui, |ui| {
                    for preset in GalleryPreset::ALL {
                        let selected = self.preset == preset;
                        if ui.selectable_label(selected, preset.label()).clicked() {
                            self.select_preset(ctx, preset);
                        }
                    }
                });
            egui::ComboBox::from_id_salt("gallery_fixture")
                .selected_text(self.fixture_id.label())
                .show_ui(ui, |ui| {
                    for fixture in FixtureId::ALL {
                        let selected = self.fixture_id == fixture;
                        if ui.selectable_label(selected, fixture.label()).clicked() {
                            self.select_fixture(ctx, fixture);
                        }
                    }
                });
            for mode in ComparisonMode::ALL {
                let selected = self.comparison == mode;
                if ui.selectable_label(selected, mode.label()).clicked() {
                    self.select_comparison(mode);
                }
            }
            if self.comparison == ComparisonMode::Overlay {
                ui.add(
                    egui::Slider::new(&mut self.overlay_opacity, 0.0..=1.0)
                        .text("Reference opacity")
                        .show_value(false),
                );
            }
            if self.comparison == ComparisonMode::Difference
                && ui.button("Refresh difference").clicked()
            {
                self.difference_dirty = true;
            }
        });
        ui.horizontal_wrapped(|ui| {
            ui.small(format!(
                "Canvas: {} px · reference: {}",
                self.preset.width() as u32,
                self.preset.file_name()
            ));
            ui.separator();
            ui.small("Theme and fixture JSON reload without rebuilding");
            ui.separator();
            ui.small("F1–F4 presets · Shift+F1–F4 modes · PgUp/PgDn fixtures");
            if let Some(stats) = self.difference_stats {
                ui.separator();
                ui.small(format!(
                    "Δ mean {:.2} · max {} · {:.1}% pixels > 8",
                    stats.mean_channel_delta, stats.max_channel_delta, stats.changed_percent
                ));
            }
            if let Some(error) = self
                .documents
                .error
                .as_deref()
                .or(self.reference_error.as_deref())
            {
                ui.colored_label(editor_ui::theme::ERROR, error);
            }
        });
    }

    fn render_comparison(&mut self, ui: &mut egui::Ui) {
        let preset = self.preset;
        match self.comparison {
            ComparisonMode::Live => {
                self.last_live_rect = Some(self.render_live_canvas(ui));
            }
            ComparisonMode::SideBySide => {
                ui.horizontal_top(|ui| {
                    ui.vertical(|ui| {
                        ui.strong("Penpot reference");
                        if let Some(reference) = self.reference() {
                            reference.paint(ui, preset, 1.0);
                        }
                    });
                    ui.add_space(16.0);
                    ui.vertical(|ui| {
                        ui.strong("Production component");
                        self.last_live_rect = Some(self.render_live_canvas(ui));
                    });
                });
            }
            ComparisonMode::Overlay => {
                self.last_live_rect = Some(self.render_live_canvas(ui));
                if let (Some(rect), Some(reference)) = (self.last_live_rect, self.reference()) {
                    let mut overlay_ui = ui.new_child(
                        egui::UiBuilder::new()
                            .max_rect(rect)
                            .layout(egui::Layout::top_down(egui::Align::Min)),
                    );
                    reference.paint(&mut overlay_ui, preset, self.overlay_opacity);
                }
            }
            ComparisonMode::Difference => {
                ui.horizontal_top(|ui| {
                    self.last_live_rect = Some(self.render_live_canvas(ui));
                    ui.add_space(16.0);
                    if self.difference_dirty || self.capture.is_some() {
                        self.last_reference_rect = self
                            .reference()
                            .map(|reference| reference.paint(ui, preset, 1.0).rect);
                    } else if let Some(texture) = &self.difference_texture {
                        self.last_reference_rect = None;
                        ui.image((
                            texture.id(),
                            egui::vec2(preset.width(), preset.target_height()),
                        ));
                    } else {
                        ui.allocate_ui(egui::vec2(preset.width(), preset.target_height()), |ui| {
                            ui.centered_and_justified(|ui| {
                                ui.label("Difference capture pending…");
                            });
                        });
                    }
                });
            }
        }
    }

    fn render_live_canvas(&mut self, ui: &mut egui::Ui) -> egui::Rect {
        let size = egui::vec2(self.preset.width(), self.preset.target_height());
        let (rect, _) = ui.allocate_exact_size(size, egui::Sense::hover());
        let palette = editor_ui::design_system::palette(ui);
        let (canvas_fill, canvas_radius) = if self.preset.is_renderer() {
            (palette.recessed, 4.0)
        } else {
            (palette.panel, 7.0)
        };
        ui.painter().rect(
            rect,
            canvas_radius,
            canvas_fill,
            egui::Stroke::new(1.0_f32, palette.border_strong),
            egui::StrokeKind::Inside,
        );
        // Penpot material boards are complete Inspector canvases, while renderer boards include a
        // authored top inset before the component card. The preset owns both cases.
        let content_rect = self.preset.content_rect(rect);
        let mut child = ui.new_child(
            egui::UiBuilder::new()
                .max_rect(content_rect)
                .layout(egui::Layout::top_down(egui::Align::Min)),
        );
        child.set_clip_rect(rect.intersect(ui.clip_rect()));
        let previous_inputs = self.fixture.inputs.clone();
        let previous_renderer_inputs = self
            .fixture
            .renderer_slots
            .iter()
            .map(|fixture| fixture.inputs.clone())
            .collect::<Vec<_>>();
        if self.preset.is_renderer() {
            self.render_renderer_fixture(&mut child);
        } else {
            self.render_material_fixture(&mut child);
        }
        if previous_inputs != self.fixture.inputs
            || previous_renderer_inputs
                != self
                    .fixture
                    .renderer_slots
                    .iter()
                    .map(|fixture| fixture.inputs.clone())
                    .collect::<Vec<_>>()
        {
            self.difference_dirty = true;
        }
        rect
    }

    fn render_renderer_fixture(&mut self, ui: &mut egui::Ui) {
        let title = if self.fixture_id == FixtureId::SkinnedMesh {
            "Skinned Mesh Renderer"
        } else {
            "Static Mesh Renderer"
        };
        let component = ComponentCardViewModel {
            options: ComponentCardOptions {
                type_name: "gallery::Renderer",
                title,
                icon: icons::CUBE,
                active_toggle: true,
                removable: true,
                reorderable: true,
                resettable: true,
                copyable: true,
                summary: "3 renderers · 5 material slots",
                body_margin: 0,
            },
            active: true,
            collapsed: false,
            pasteable: false,
            can_move_up: false,
            can_move_down: true,
        };
        editor_ui::inspector::component_card(ui, &component, |ui| {
            let _ = renderer_array_header(ui, self.renderer_open.len());
            ui.add_space(26.0);
            for renderer_index in 0..self.renderer_open.len() {
                let renderer_label = match renderer_index {
                    0 => "metal_office_desk / Primitive 0",
                    1 => "metal_office_desk_drawer_06 / Primitive 0",
                    _ => "metal_office_desk_tray_01 / Primitive 0",
                };
                renderer_panel(ui, |ui| {
                    renderer_panel_header(
                        ui,
                        RendererPanelHeaderViewModel {
                            index: renderer_index,
                            label: renderer_label,
                            path: "draw:scene0:node2:mesh2:primitive0",
                            slot_count: if renderer_index == 0 { 3 } else { 1 },
                            texture_id: None,
                        },
                        &mut self.renderer_open[renderer_index],
                        |ui| {
                            editor_ui::design_system::controls::icon_button(
                                ui,
                                icons::DOTS_THREE_VERTICAL,
                                "Renderer actions",
                                true,
                            );
                            editor_ui::design_system::controls::icon_button(
                                ui,
                                icons::X,
                                "Remove renderer",
                                true,
                            );
                            editor_ui::design_system::controls::icon_button(
                                ui,
                                icons::CROSSHAIR,
                                "Locate mesh",
                                true,
                            );
                            editor_ui::design_system::controls::icon_button(
                                ui,
                                icons::FOLDER_OPEN,
                                "Browse mesh",
                                true,
                            );
                        },
                    );
                    if !self.renderer_open[renderer_index] {
                        return;
                    }
                    renderer_properties_fixture(ui, renderer_label);
                    let slot_count = if renderer_index == 0 { 3 } else { 1 };
                    renderer_material_slots_header(ui, slot_count);
                    if renderer_index == 0 {
                        for slot_index in 0..self.fixture.renderer_slots.len() {
                            renderer_material_slot_row(ui, slot_index, |ui| {
                                self.render_renderer_material_slot(ui, slot_index);
                            });
                        }
                    }
                });
            }
        });
    }

    fn render_material_fixture(&mut self, ui: &mut egui::Ui) {
        let section = MaterialsSectionViewModel {
            slot_ids: vec![self.fixture.slot.slot_id.clone()],
        };
        materials_section(ui, &section, |ui| {
            let slot = &mut self.fixture.slot;
            let render_state = &mut self.fixture.render_state;
            let inputs = &mut self.fixture.inputs;
            let textures = &self.fixture.textures;
            let response = material_slot_panel(
                ui,
                ("gallery-slot", &slot.slot_id),
                slot,
                |ui| {
                    material_surface_options_ui(ui, render_state, Some(inputs));
                    ui.add_space(4.0);
                    material_input_schema_editor(
                        ui,
                        &standard_lit_input_schema(),
                        inputs,
                        textures,
                        None,
                    );
                },
                |ui| {
                    ui.label(format!("Stable slot: {}", slot.slot_id));
                    ui.label("Gallery fixture — no project state is loaded");
                },
            );
            for action in response.actions {
                apply_slot_action(slot, action.action);
            }
        });
    }

    fn render_renderer_material_slot(&mut self, ui: &mut egui::Ui, slot_index: usize) {
        let preset_label = self.preset.label();
        let fixture = &mut self.fixture.renderer_slots[slot_index];
        let slot_id = fixture.slot.slot_id.clone();
        fixture.slot.expanded_header_when_parameters_closed = true;
        fixture.slot.parameters_initially_open =
            slot_index == 0 && self.preset == GalleryPreset::Renderer760;
        let textures = &self.fixture.textures;
        ui.push_id(
            (
                "gallery-renderer-slot-scope",
                preset_label,
                slot_id.as_str(),
            ),
            |ui| {
                let response = material_slot_panel(
                    ui,
                    "material-panel",
                    &fixture.slot,
                    |ui| {
                        material_surface_options_ui(
                            ui,
                            &mut fixture.render_state,
                            Some(&mut fixture.inputs),
                        );
                        ui.add_space(4.0);
                        material_input_schema_editor(
                            ui,
                            &standard_lit_input_schema(),
                            &mut fixture.inputs,
                            textures,
                            None,
                        );
                    },
                    |ui| {
                        ui.label(format!("Stable slot: {}", fixture.slot.slot_id));
                        ui.label("Gallery renderer fixture — no project state is loaded");
                    },
                );
                for action in response.actions {
                    apply_slot_action(&mut fixture.slot, action.action);
                }
            },
        );
    }
}

impl eframe::App for UiGalleryApp {
    fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        self.handle_screenshot_events(ctx);
        self.handle_shortcuts(ctx);
        let now = ctx.input(|input| input.time);
        if self.documents.poll(now) {
            if let Ok(palette) = self.documents.theme_value.palette() {
                editor_ui::theme::apply_editor_theme_with_palette(ctx, palette);
            }
            self.rebuild_fixture(ctx);
        }
        ctx.request_repaint_after(std::time::Duration::from_millis(250));
    }

    fn ui(&mut self, root: &mut egui::Ui, _frame: &mut eframe::Frame) {
        let ctx = root.ctx().clone();
        egui::CentralPanel::default().show_inside(root, |ui| {
            self.toolbar(&ctx, ui);
            ui.separator();
            egui::ScrollArea::both()
                .id_salt("gallery_canvas_scroll")
                .auto_shrink([false, false])
                .show(ui, |ui| self.render_comparison(ui));
            if self.comparison == ComparisonMode::Difference
                && self.difference_dirty
                && self.capture.is_none()
            {
                if let (Some(live_rect), Some(reference_rect)) =
                    (self.last_live_rect, self.last_reference_rect)
                {
                    self.capture = Some(CaptureRequest {
                        preset: self.preset,
                        live_rect,
                        reference_rect,
                        pixels_per_point: ctx.pixels_per_point(),
                    });
                    ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot(egui::UserData::new(
                        "blacksite-ui-gallery-difference",
                    )));
                }
            }
        });
    }
}

fn renderer_properties_fixture(ui: &mut egui::Ui, name: &str) {
    let palette = editor_ui::design_system::palette(ui);
    let mut name_buffer = name.to_owned();
    egui::Frame::new()
        .inner_margin(egui::Margin::symmetric(12, 10))
        .show(ui, |ui| {
            renderer_properties_header(ui);
            ui.add_space(2.0);
            property_fixture_row(ui, "Name", |ui| {
                ui.add_sized(
                    [ui.available_width().max(1.0), 24.0],
                    egui::TextEdit::singleline(&mut name_buffer),
                );
            });
            ui.add_space(4.0);
            property_fixture_row(ui, "Mesh", |ui| {
                let field = ui.available_rect_before_wrap();
                let field =
                    egui::Rect::from_min_size(field.min, egui::vec2(field.width().max(1.0), 38.0));
                ui.allocate_rect(field, egui::Sense::hover());
                ui.painter().rect(
                    field,
                    4.0,
                    palette.control,
                    egui::Stroke::new(1.0_f32, palette.border),
                    egui::StrokeKind::Inside,
                );
                ui.painter().text(
                    field.left_center() + egui::vec2(10.0, -7.0),
                    egui::Align2::LEFT_CENTER,
                    name,
                    editor_ui::design_system::typography::TypeRole::Body.font(),
                    palette.text_primary,
                );
                ui.painter().text(
                    field.left_center() + egui::vec2(10.0, 8.0),
                    egui::Align2::LEFT_CENTER,
                    "assets/Furniture/Office/metal_office_desk.mesh",
                    editor_ui::design_system::typography::TypeRole::Small.font(),
                    palette.text_muted,
                );
            });
            ui.add_space(4.0);
            property_fixture_row(ui, "Flags", |ui| {
                let mut visible = true;
                let mut cast_shadows = true;
                let mut receive_shadows = true;
                ui.horizontal_wrapped(|ui| {
                    ui.checkbox(&mut visible, "Visible");
                    ui.checkbox(&mut cast_shadows, "Cast shadows");
                    ui.checkbox(&mut receive_shadows, "Receive shadows");
                });
            });
            ui.add_space(4.0);
        });
}

fn property_fixture_row(ui: &mut egui::Ui, label: &str, add_value: impl FnOnce(&mut egui::Ui)) {
    ui.horizontal_top(|ui| {
        ui.allocate_ui_with_layout(
            egui::vec2(72.0, 28.0),
            egui::Layout::left_to_right(egui::Align::Center),
            |ui| {
                ui.label(label);
            },
        );
        ui.vertical(add_value);
    });
}

fn apply_slot_action(
    slot: &mut editor_ui::materials::MaterialSlotPanelViewModel,
    action: MaterialSlotAction,
) {
    match action {
        MaterialSlotAction::Assign { reference, .. } => {
            slot.label = reference.label.clone();
            slot.path = reference.source_path.clone();
            slot.assigned = Some(reference);
            slot.health = MaterialHealth::Dirty;
        }
        MaterialSlotAction::Clear => {
            slot.assigned = None;
            slot.health = MaterialHealth::Dirty;
        }
        MaterialSlotAction::SetShader(kind) => {
            slot.shader_kind = Some(kind);
            slot.shader = match kind {
                MaterialShaderKind::StandardLit => "Standard Lit",
                MaterialShaderKind::Unlit => "Unlit",
                MaterialShaderKind::Custom => "Custom Surface",
            }
            .to_owned();
            slot.health = MaterialHealth::Dirty;
        }
        MaterialSlotAction::BrowseLibrary
        | MaterialSlotAction::Locate
        | MaterialSlotAction::ExtractEditable
        | MaterialSlotAction::CreateInstance => {}
    }
}

fn make_fixture_textures(ctx: &egui::Context) -> Vec<egui::TextureHandle> {
    let mut handles = vec![make_material_thumbnail(ctx)];
    handles.extend([
        checker_thumbnail(ctx, "gallery-base-color", [126, 100, 74], [87, 69, 51]),
        checker_thumbnail(ctx, "gallery-orm", [198, 238, 18], [103, 164, 8]),
        checker_thumbnail(ctx, "gallery-normal", [116, 92, 232], [73, 145, 242]),
    ]);
    handles
}

fn fixture_thumbnail_ids(
    handles: &[egui::TextureHandle],
) -> (
    MaterialThumbnailPresentation,
    [MaterialThumbnailPresentation; 3],
) {
    (
        MaterialThumbnailPresentation::Ready(handles[0].id()),
        [
            MaterialThumbnailPresentation::Ready(handles[1].id()),
            MaterialThumbnailPresentation::Ready(handles[2].id()),
            MaterialThumbnailPresentation::Ready(handles[3].id()),
        ],
    )
}

fn make_material_thumbnail(ctx: &egui::Context) -> egui::TextureHandle {
    let size = 64;
    let mut image = egui::ColorImage::new([size, size], vec![egui::Color32::BLACK; size * size]);
    for y in 0..size {
        for x in 0..size {
            image[(x, y)] = if (x / 16 + y / 16) % 2 == 0 {
                egui::Color32::from_rgb(32, 37, 43)
            } else {
                egui::Color32::from_rgb(43, 50, 58)
            };
        }
    }
    paint_fixture_ellipse(&mut image, [37.0, 42.0], [19.0, 19.0], [17, 22, 27], 0.8);
    paint_fixture_ellipse(&mut image, [34.0, 38.0], [20.0, 20.0], [109, 123, 138], 1.0);
    paint_fixture_ellipse(&mut image, [27.0, 28.5], [6.0, 4.5], [199, 211, 221], 0.85);
    paint_fixture_ellipse(&mut image, [38.0, 42.0], [9.0, 9.0], [63, 74, 86], 1.0);
    ctx.load_texture(
        "gallery-material-sphere",
        image,
        egui::TextureOptions::NEAREST,
    )
}

fn paint_fixture_ellipse(
    image: &mut egui::ColorImage,
    center: [f32; 2],
    radius: [f32; 2],
    color: [u8; 3],
    opacity: f32,
) {
    for y in 0..image.size[1] {
        for x in 0..image.size[0] {
            let dx = (x as f32 + 0.5 - center[0]) / radius[0];
            let dy = (y as f32 + 0.5 - center[1]) / radius[1];
            let distance = (dx * dx + dy * dy).sqrt();
            let coverage = ((1.0 - distance) * radius[0].min(radius[1])).clamp(0.0, 1.0);
            let alpha = coverage * opacity;
            if alpha <= 0.0 {
                continue;
            }
            let previous = image[(x, y)];
            let blend = |source: u8, destination: u8| {
                (source as f32 * alpha + destination as f32 * (1.0 - alpha)).round() as u8
            };
            image[(x, y)] = egui::Color32::from_rgb(
                blend(color[0], previous.r()),
                blend(color[1], previous.g()),
                blend(color[2], previous.b()),
            );
        }
    }
}

fn checker_thumbnail(
    ctx: &egui::Context,
    name: &str,
    first: [u8; 3],
    second: [u8; 3],
) -> egui::TextureHandle {
    let size = 18;
    let mut image = egui::ColorImage::new([size, size], vec![egui::Color32::BLACK; size * size]);
    for y in 0..size {
        for x in 0..size {
            let color = if (x / 3 + y / 3) % 2 == 0 {
                first
            } else {
                second
            };
            image[(x, y)] = egui::Color32::from_rgb(color[0], color[1], color[2]);
        }
    }
    ctx.load_texture(name, image, egui::TextureOptions::NEAREST)
}
