use nightshade::ecs::generational_registry::registry_entry_by_name_mut;
use nightshade::ecs::material::resources::material_registry_insert;
use nightshade::prelude::serde::{Deserialize, Serialize};
use nightshade::prelude::strum::{Display, EnumIter};
use nightshade::prelude::*;

#[derive(
    Debug, Clone, Copy, PartialEq, Eq, Hash, Default, EnumIter, Display, Serialize, Deserialize,
)]
#[serde(crate = "nightshade::prelude::serde")]
#[strum(crate = "nightshade::prelude::strum")]
pub enum WidgetKind {
    #[default]
    Camera,
    Materials,
    Web,
}

impl WidgetKind {
    pub fn title(&self) -> String {
        self.to_string()
    }
}

pub struct WidgetContext<'a> {
    pub world: *mut World,
    pub selected_viewport_tile: &'a mut Option<nightshade::prelude::egui_tiles::TileId>,
    pub viewport_textures: &'a Vec<egui::TextureId>,
    pub current_tile_id: nightshade::prelude::egui_tiles::TileId,
    #[cfg(not(target_arch = "wasm32"))]
    pub web_widget_rects: &'a mut Vec<(String, String, egui::Rect)>,
}

pub trait Widget {
    fn title(&self) -> String;
    fn ui(&mut self, ui: &mut egui::Ui, context: &mut WidgetContext);
}

#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(crate = "nightshade::prelude::serde")]
pub struct CameraWidget {
    pub camera_index: usize,
}

impl Widget for CameraWidget {
    fn title(&self) -> String {
        format!("Camera {}", self.camera_index + 1)
    }

    fn ui(&mut self, ui: &mut egui::Ui, context: &mut WidgetContext) {
        let world = context.world;
        let rect = ui.available_rect_before_wrap();
        let tile_id = context.current_tile_id;

        let mut cameras: Vec<Entity> = (unsafe { &*world }).query_entities(CAMERA).collect();
        cameras.sort_by_key(|entity| entity.id);
        let camera_entity = cameras.get(self.camera_index).copied();

        let texture_index = camera_entity.and_then(|cam| {
            (unsafe { &*world })
                .resources
                .user_interface
                .required_cameras
                .iter()
                .position(|&entity| entity == cam)
        });

        let clicked = if let Some(index) = texture_index
            && let Some(texture_id) = context.viewport_textures.get(index)
        {
            let image = egui::Image::new(egui::load::SizedTexture::new(
                *texture_id,
                egui::vec2(rect.width(), rect.height()),
            ))
            .fit_to_exact_size(egui::vec2(rect.width(), rect.height()))
            .sense(egui::Sense::click());

            let response = ui.put(rect, image);
            response.clicked()
        } else {
            let response = ui.allocate_rect(rect, egui::Sense::click());
            response.clicked()
        };

        let pixels_per_point = ui.ctx().pixels_per_point();

        if context.selected_viewport_tile.is_none() {
            *context.selected_viewport_tile = Some(tile_id);
            if let Some(cam) = camera_entity {
                (unsafe { &mut *world }).resources.active_camera = Some(cam);
            }
        }

        if clicked {
            *context.selected_viewport_tile = Some(tile_id);
            if let Some(cam) = camera_entity {
                (unsafe { &mut *world }).resources.active_camera = Some(cam);
            }
        }

        let is_selected = *context.selected_viewport_tile == Some(tile_id);
        if is_selected {
            (unsafe { &mut *world })
                .resources
                .window
                .active_viewport_rect = Some(nightshade::prelude::ViewportRect {
                x: rect.min.x * pixels_per_point,
                y: rect.min.y * pixels_per_point,
                width: rect.width() * pixels_per_point,
                height: rect.height() * pixels_per_point,
            });
            if let Some(cam) = camera_entity {
                (unsafe { &mut *world }).resources.active_camera = Some(cam);
            }

            ui.painter().rect_stroke(
                rect,
                egui::CornerRadius::ZERO,
                egui::Stroke::new(3.0, egui::Color32::from_rgb(255, 165, 0)),
                egui::StrokeKind::Inside,
            );
        }
    }
}

#[derive(Clone, Serialize, Deserialize)]
#[serde(crate = "nightshade::prelude::serde")]
pub struct MaterialsWidget {
    pub selected_material: Option<String>,
    #[serde(skip)]
    pub new_material_name: String,
    #[serde(default = "default_true")]
    pub materials_list_expanded: bool,
}

fn default_true() -> bool {
    true
}

impl Default for MaterialsWidget {
    fn default() -> Self {
        Self {
            selected_material: None,
            new_material_name: String::new(),
            materials_list_expanded: true,
        }
    }
}

impl Widget for MaterialsWidget {
    fn title(&self) -> String {
        "Materials".to_string()
    }

    fn ui(&mut self, ui: &mut egui::Ui, context: &mut WidgetContext) {
        let world = unsafe { &mut *context.world };
        let tile_id = context.current_tile_id;

        let available_rect = ui.available_rect_before_wrap();
        ui.painter()
            .rect_filled(available_rect, 0.0, ui.style().visuals.panel_fill);

        egui::ScrollArea::vertical()
            .id_salt(("materials_widget_scroll", tile_id))
            .auto_shrink([false, false])
            .show(ui, |ui| {
                ui.set_min_width(available_rect.width());
                ui.add_space(4.0);
                ui.horizontal(|ui| {
                    ui.label("New material:");
                    ui.text_edit_singleline(&mut self.new_material_name);
                    if ui.button("Create").clicked()
                        && !self.new_material_name.is_empty()
                        && !world
                            .resources
                            .material_registry
                            .registry
                            .name_to_index
                            .contains_key(&self.new_material_name)
                    {
                        material_registry_insert(
                            &mut world.resources.material_registry,
                            self.new_material_name.clone(),
                            Material::default(),
                        );
                        self.selected_material = Some(self.new_material_name.clone());
                        self.new_material_name.clear();
                    }
                });

                ui.separator();

                let material_names: Vec<String> = world
                    .resources
                    .material_registry
                    .registry
                    .index_to_name
                    .iter()
                    .filter_map(|opt| opt.clone())
                    .collect();

                let header_response =
                    ui.collapsing(format!("Materials ({})", material_names.len()), |ui| {
                        egui::ScrollArea::vertical()
                            .id_salt(tile_id)
                            .max_height(200.0)
                            .show(ui, |ui| {
                                for name in &material_names {
                                    let ref_count = world
                                        .resources
                                        .material_registry
                                        .registry
                                        .name_to_index
                                        .get(name)
                                        .map(|&index| {
                                            world
                                                .resources
                                                .material_registry
                                                .registry
                                                .reference_counts
                                                [index as usize]
                                        })
                                        .unwrap_or(0);
                                    let is_selected = self.selected_material.as_ref() == Some(name);

                                    ui.horizontal(|ui| {
                                        let label = format!("{} ({})", name, ref_count);
                                        if ui.selectable_label(is_selected, &label).clicked() {
                                            self.selected_material = Some(name.clone());
                                        }

                                        if ref_count == 0
                                            && ui
                                                .small_button("🗑")
                                                .on_hover_text("Delete")
                                                .clicked()
                                        {
                                            if let Some((index, generation)) = world
                                                .resources
                                                .material_registry
                                                .registry
                                                .lookup_index(name)
                                            {
                                                world
                                                    .resources
                                                    .material_registry
                                                    .registry
                                                    .remove(index, generation);
                                            }
                                            if self.selected_material.as_ref() == Some(name) {
                                                self.selected_material = None;
                                            }
                                        }
                                    });
                                }
                            });
                    });

                self.materials_list_expanded = header_response.fully_open();

                ui.separator();

                if let Some(material_name) = &self.selected_material.clone() {
                    if let Some(material) = registry_entry_by_name_mut(
                        &mut world.resources.material_registry.registry,
                        material_name,
                    ) {
                        ui.collapsing(format!("Edit: {}", material_name), |ui| {
                            ui.horizontal(|ui| {
                                ui.label("Base Color:");
                                ui.color_edit_button_rgba_unmultiplied(&mut material.base_color);
                            });

                            ui.horizontal(|ui| {
                                ui.label("R:");
                                ui.add(
                                    egui::Slider::new(&mut material.base_color[0], 0.0..=1.0)
                                        .fixed_decimals(2),
                                );
                            });

                            ui.horizontal(|ui| {
                                ui.label("G:");
                                ui.add(
                                    egui::Slider::new(&mut material.base_color[1], 0.0..=1.0)
                                        .fixed_decimals(2),
                                );
                            });

                            ui.horizontal(|ui| {
                                ui.label("B:");
                                ui.add(
                                    egui::Slider::new(&mut material.base_color[2], 0.0..=1.0)
                                        .fixed_decimals(2),
                                );
                            });

                            ui.horizontal(|ui| {
                                ui.label("A:");
                                ui.add(
                                    egui::Slider::new(&mut material.base_color[3], 0.0..=1.0)
                                        .fixed_decimals(2),
                                );
                            });

                            ui.separator();

                            ui.horizontal(|ui| {
                                ui.label("Alpha Mode:");
                                egui::ComboBox::from_id_salt(("alpha_mode", tile_id))
                                    .selected_text(match material.alpha_mode {
                                        AlphaMode::Opaque => "Opaque",
                                        AlphaMode::Mask => "Mask",
                                        AlphaMode::Blend => "Blend",
                                    })
                                    .show_ui(ui, |ui| {
                                        ui.selectable_value(
                                            &mut material.alpha_mode,
                                            AlphaMode::Opaque,
                                            "Opaque",
                                        );
                                        ui.selectable_value(
                                            &mut material.alpha_mode,
                                            AlphaMode::Mask,
                                            "Mask",
                                        );
                                        ui.selectable_value(
                                            &mut material.alpha_mode,
                                            AlphaMode::Blend,
                                            "Blend",
                                        );
                                    });
                            });

                            if material.alpha_mode == AlphaMode::Mask {
                                ui.horizontal(|ui| {
                                    ui.label("Alpha Cutoff:");
                                    ui.add(
                                        egui::Slider::new(&mut material.alpha_cutoff, 0.0..=1.0)
                                            .fixed_decimals(2),
                                    );
                                });
                            }

                            ui.separator();

                            ui.horizontal(|ui| {
                                ui.label("Roughness:");
                                ui.add(
                                    egui::Slider::new(&mut material.roughness, 0.0..=1.0)
                                        .fixed_decimals(2),
                                );
                            });

                            ui.horizontal(|ui| {
                                ui.label("Metallic:");
                                ui.add(
                                    egui::Slider::new(&mut material.metallic, 0.0..=1.0)
                                        .fixed_decimals(2),
                                );
                            });

                            ui.horizontal(|ui| {
                                ui.label("Unlit:");
                                ui.checkbox(&mut material.unlit, "");
                            });

                            ui.separator();

                            ui.label("Emissive Factor:");

                            ui.horizontal(|ui| {
                                ui.label("R:");
                                ui.add(
                                    egui::Slider::new(&mut material.emissive_factor[0], 0.0..=1.0)
                                        .fixed_decimals(2),
                                );
                            });

                            ui.horizontal(|ui| {
                                ui.label("G:");
                                ui.add(
                                    egui::Slider::new(&mut material.emissive_factor[1], 0.0..=1.0)
                                        .fixed_decimals(2),
                                );
                            });

                            ui.horizontal(|ui| {
                                ui.label("B:");
                                ui.add(
                                    egui::Slider::new(&mut material.emissive_factor[2], 0.0..=1.0)
                                        .fixed_decimals(2),
                                );
                            });

                            ui.separator();

                            ui.label("Textures:");

                            let mut base_texture_str =
                                material.base_texture.clone().unwrap_or_default();
                            ui.horizontal(|ui| {
                                ui.label("Base Texture:");
                                if ui.text_edit_singleline(&mut base_texture_str).changed() {
                                    material.base_texture = if base_texture_str.is_empty() {
                                        None
                                    } else {
                                        Some(base_texture_str)
                                    };
                                }
                            });

                            let mut emissive_texture_str =
                                material.emissive_texture.clone().unwrap_or_default();
                            ui.horizontal(|ui| {
                                ui.label("Emissive Texture:");
                                if ui.text_edit_singleline(&mut emissive_texture_str).changed() {
                                    material.emissive_texture = if emissive_texture_str.is_empty() {
                                        None
                                    } else {
                                        Some(emissive_texture_str)
                                    };
                                }
                            });
                        });
                    } else {
                        self.selected_material = None;
                    }
                }
            });
    }
}

#[derive(Clone, Serialize, Deserialize)]
#[serde(crate = "nightshade::prelude::serde")]
#[cfg_attr(target_arch = "wasm32", derive(Default))]
pub struct WebWidget {
    #[cfg(not(target_arch = "wasm32"))]
    pub url: String,
    #[cfg(not(target_arch = "wasm32"))]
    pub id: String,
}

#[cfg(not(target_arch = "wasm32"))]
impl Default for WebWidget {
    fn default() -> Self {
        Self {
            url: "https://matthewberger.dev/nightshade".to_string(),
            id: nightshade::prelude::uuid::Uuid::new_v4().to_string(),
        }
    }
}

impl Widget for WebWidget {
    fn title(&self) -> String {
        "Web".to_string()
    }

    fn ui(&mut self, ui: &mut egui::Ui, _context: &mut WidgetContext) {
        let rect = ui.available_rect_before_wrap();

        ui.painter()
            .rect_filled(rect, 0.0, ui.style().visuals.panel_fill);

        #[cfg(target_arch = "wasm32")]
        {
            ui.centered_and_justified(|ui| {
                ui.label("Web widget is unavailable on wasm");
            });
        }

        #[cfg(not(target_arch = "wasm32"))]
        {
            _context
                .web_widget_rects
                .push((self.id.clone(), self.url.clone(), rect));
        }
    }
}

#[derive(Clone, Serialize, Deserialize)]
#[serde(crate = "nightshade::prelude::serde")]
pub enum EditorWidget {
    Camera(CameraWidget),
    Materials(MaterialsWidget),
    Web(WebWidget),
}

impl EditorWidget {
    pub fn from_kind(kind: WidgetKind) -> Self {
        match kind {
            WidgetKind::Camera => EditorWidget::Camera(CameraWidget::default()),
            WidgetKind::Materials => EditorWidget::Materials(MaterialsWidget::default()),
            WidgetKind::Web => EditorWidget::Web(WebWidget::default()),
        }
    }

    pub fn required_camera(&self, world: &World) -> Option<Entity> {
        match self {
            EditorWidget::Camera(widget) => {
                let mut cameras: Vec<Entity> = world.query_entities(CAMERA).collect();
                cameras.sort_by_key(|entity| entity.id);
                cameras.get(widget.camera_index).copied()
            }
            EditorWidget::Materials(_) => None,
            EditorWidget::Web(_) => None,
        }
    }
}

impl Widget for EditorWidget {
    fn title(&self) -> String {
        match self {
            EditorWidget::Camera(widget) => widget.title(),
            EditorWidget::Materials(widget) => widget.title(),
            EditorWidget::Web(widget) => widget.title(),
        }
    }

    fn ui(&mut self, ui: &mut egui::Ui, context: &mut WidgetContext) {
        match self {
            EditorWidget::Camera(widget) => widget.ui(ui, context),
            EditorWidget::Materials(widget) => widget.ui(ui, context),
            EditorWidget::Web(widget) => widget.ui(ui, context),
        }
    }
}

#[derive(Clone, Serialize, Deserialize)]
#[serde(crate = "nightshade::prelude::serde")]
pub struct Pane {
    pub widget: EditorWidget,
}

impl Pane {
    pub fn new(kind: WidgetKind) -> Self {
        Self {
            widget: EditorWidget::from_kind(kind),
        }
    }

    pub fn camera(camera_index: usize) -> Self {
        Self {
            widget: EditorWidget::Camera(CameraWidget { camera_index }),
        }
    }
}
