use std::marker::PhantomData;

use nightshade::prelude::*;

use super::config::MosaicConfig;
use super::widget::{Pane, Widget, WidgetContext};

pub struct TileBehavior<W: Widget<C, M>, C = (), M = ()> {
    pub(crate) viewport_rects: std::collections::HashMap<egui_tiles::TileId, egui::Rect>,
    pub(crate) viewport_textures: Vec<egui::TextureId>,
    pub(crate) selected_viewport_tile: Option<egui_tiles::TileId>,
    pub(crate) pending_add_widget: Option<(egui_tiles::TileId, usize)>,
    pub(crate) layout_modified: bool,
    pub(crate) config: MosaicConfig,
    pub(crate) pending_removals: Vec<(egui_tiles::TileId, W)>,
    pub(crate) is_active_window: bool,
    pub(crate) cached_cameras: Vec<Entity>,
    _phantom: PhantomData<(C, M)>,
}

impl<W: Widget<C, M>, C, M> Default for TileBehavior<W, C, M> {
    fn default() -> Self {
        Self {
            viewport_rects: std::collections::HashMap::new(),
            viewport_textures: Vec::new(),
            selected_viewport_tile: None,
            pending_add_widget: None,
            layout_modified: false,
            config: MosaicConfig::default(),
            pending_removals: Vec::new(),
            is_active_window: true,
            cached_cameras: Vec::new(),
            _phantom: PhantomData,
        }
    }
}

pub(crate) struct TileBehaviorWithWorld<'a, W: Widget<C, M>, C = (), M = ()> {
    pub(crate) behavior: &'a mut TileBehavior<W, C, M>,
    pub(crate) world: &'a mut World,
    pub(crate) app: &'a mut C,
    pub(crate) messages: &'a mut Vec<M>,
}

impl<W: Widget<C, M>, C, M> egui_tiles::Behavior<Pane<W>> for TileBehaviorWithWorld<'_, W, C, M> {
    fn tab_title_for_pane(&mut self, pane: &Pane<W>) -> egui::WidgetText {
        pane.widget.title().into()
    }

    fn pane_ui(
        &mut self,
        ui: &mut egui::Ui,
        tile_id: egui_tiles::TileId,
        pane: &mut Pane<W>,
    ) -> egui_tiles::UiResponse {
        let rect = ui.available_rect_before_wrap();
        self.behavior.viewport_rects.insert(tile_id, rect);

        let mut context = WidgetContext {
            world: &mut *self.world,
            selected_viewport_tile: &mut self.behavior.selected_viewport_tile,
            viewport_textures: &self.behavior.viewport_textures,
            current_tile_id: tile_id,
            app: &mut *self.app,
            cached_cameras: &self.behavior.cached_cameras,
            messages: &mut *self.messages,
        };

        pane.widget.ui(ui, &mut context);

        egui_tiles::UiResponse::None
    }

    fn simplification_options(&self) -> egui_tiles::SimplificationOptions {
        self.behavior.config.simplification_options
    }

    fn tab_bar_height(&self, _style: &egui::Style) -> f32 {
        self.behavior.config.tab_bar_height
    }

    fn close_button_outer_size(&self) -> f32 {
        self.behavior.config.close_button_size
    }

    fn gap_width(&self, _style: &egui::Style) -> f32 {
        self.behavior.config.gap_width
    }

    fn min_size(&self) -> f32 {
        self.behavior.config.min_size
    }

    fn is_tab_closable(
        &self,
        tiles: &egui_tiles::Tiles<Pane<W>>,
        tile_id: egui_tiles::TileId,
    ) -> bool {
        if !self.behavior.config.all_closable {
            return false;
        }
        if let Some(egui_tiles::Tile::Pane(pane)) = tiles.get(tile_id) {
            pane.widget.closable()
        } else {
            true
        }
    }

    fn on_tab_close(
        &mut self,
        tiles: &mut egui_tiles::Tiles<Pane<W>>,
        tile_id: egui_tiles::TileId,
    ) -> bool {
        if let Some(egui_tiles::Tile::Pane(pane)) = tiles.get(tile_id) {
            self.behavior
                .pending_removals
                .push((tile_id, pane.widget.clone()));
        }
        self.behavior.layout_modified = true;
        true
    }

    fn on_edit(&mut self, edit_action: egui_tiles::EditAction) {
        match edit_action {
            egui_tiles::EditAction::TileDropped | egui_tiles::EditAction::TileResized => {
                self.behavior.layout_modified = true;
            }
            _ => {}
        }
    }

    fn top_bar_right_ui(
        &mut self,
        _tiles: &egui_tiles::Tiles<Pane<W>>,
        ui: &mut egui::Ui,
        tile_id: egui_tiles::TileId,
        _tabs: &egui_tiles::Tabs,
        _scroll_offset: &mut f32,
    ) {
        if !self.behavior.config.show_add_button {
            return;
        }

        render_add_widget_popup::<W, C, M>(ui, tile_id, self.behavior);
    }
}

fn render_add_widget_popup<W: Widget<C, M>, C, M>(
    ui: &mut egui::Ui,
    tile_id: egui_tiles::TileId,
    behavior: &mut TileBehavior<W, C, M>,
) {
    let button_response = ui.button("+").on_hover_text("Add widget");
    let popup_id = egui::Id::new("mosaic_add_widget_popup").with(tile_id);

    if button_response.clicked() {
        egui::Popup::open_id(ui.ctx(), popup_id);
        ui.memory_mut(|memory| {
            memory
                .data
                .get_temp_mut_or_insert_with::<String>(popup_id, String::new);
            memory
                .data
                .get_temp_mut_or_insert_with::<usize>(popup_id.with("selected_index"), || 0);
        });
    }

    egui::Popup::from_response(&button_response)
        .layout(egui::Layout::top_down_justified(egui::Align::LEFT))
        .open_memory(None::<egui::SetOpenCommand>)
        .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
        .id(popup_id)
        .align(egui::RectAlign::BOTTOM_START)
        .width(button_response.rect.width())
        .show(|ui| {
            render_add_widget_popup_content::<W, C, M>(ui, tile_id, popup_id, behavior);
        });
}

fn render_add_widget_popup_content<W: Widget<C, M>, C, M>(
    ui: &mut egui::Ui,
    tile_id: egui_tiles::TileId,
    popup_id: egui::Id,
    behavior: &mut TileBehavior<W, C, M>,
) {
    ui.set_min_width(200.0);

    let search = render_widget_search_input(ui, popup_id);

    ui.add_space(4.0);
    ui.separator();

    render_widget_catalog_list::<W, C, M>(ui, popup_id, tile_id, behavior, &search);

    ui.separator();
    ui.horizontal(|ui| {
        ui.spacing_mut().item_spacing.x = 4.0;
        ui.visuals_mut().widgets.noninteractive.fg_stroke.color = ui.visuals().weak_text_color();
        ui.label("Up/Down to navigate");
        ui.add_space(8.0);
        ui.label("Enter to select");
    });
}

fn render_widget_search_input(ui: &mut egui::Ui, popup_id: egui::Id) -> String {
    let mut search = ui.memory_mut(|memory| {
        memory
            .data
            .get_temp_mut_or_default::<String>(popup_id)
            .clone()
    });

    let is_popup_open = egui::Popup::is_id_open(ui.ctx(), popup_id);
    let popup_just_opened = ui.memory_mut(|memory| {
        let popup_open_key = popup_id.with("was_just_opened");
        if is_popup_open {
            let was_open_before = memory
                .data
                .get_temp::<bool>(popup_open_key)
                .unwrap_or(false);
            if !was_open_before {
                memory.data.insert_temp(popup_open_key, true);
                true
            } else {
                false
            }
        } else {
            memory.data.insert_temp(popup_open_key, false);
            false
        }
    });

    ui.spacing_mut().item_spacing.y = 6.0;

    let search_frame = egui::Frame::NONE
        .fill(ui.visuals().extreme_bg_color)
        .inner_margin(egui::Margin::same(8))
        .stroke(ui.visuals().widgets.noninteractive.bg_stroke);

    search_frame.show(ui, |ui| {
        ui.horizontal(|ui| {
            let search_response = ui
                .text_edit_singleline(&mut search)
                .on_hover_text("Type to filter widgets");

            let should_focus = popup_just_opened
                || (egui::Popup::is_id_open(ui.ctx(), popup_id)
                    && !ui.memory(|memory| memory.has_focus(search_response.id)));

            if should_focus {
                ui.memory_mut(|memory| memory.request_focus(search_response.id));
                ui.ctx().request_repaint();
            }

            if search_response.changed() {
                ui.memory_mut(|memory| {
                    *memory.data.get_temp_mut_or_default::<String>(popup_id) = search.clone();
                    *memory
                        .data
                        .get_temp_mut_or_default::<usize>(popup_id.with("selected_index")) = 0;
                });
            }

            if !search.is_empty() && ui.button("x").clicked() {
                search.clear();
                ui.memory_mut(|memory| {
                    *memory.data.get_temp_mut_or_default::<String>(popup_id) = String::new();
                });
            }
        });
    });

    search
}

fn contains_case_insensitive_ascii(haystack: &str, needle: &str) -> bool {
    if needle.len() > haystack.len() {
        return false;
    }
    haystack
        .as_bytes()
        .windows(needle.len())
        .any(|window| window.eq_ignore_ascii_case(needle.as_bytes()))
}

fn handle_catalog_keyboard_navigation(
    ui: &mut egui::Ui,
    popup_id: egui::Id,
    item_count: usize,
) -> (usize, bool) {
    let mut selected_index = ui.memory_mut(|memory| {
        *memory
            .data
            .get_temp_mut_or_default::<usize>(popup_id.with("selected_index"))
    });

    if selected_index >= item_count {
        selected_index = item_count - 1;
        ui.memory_mut(|memory| {
            *memory
                .data
                .get_temp_mut_or_default::<usize>(popup_id.with("selected_index")) = selected_index;
        });
    }

    if ui.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::ArrowDown))
        && selected_index < item_count - 1
    {
        selected_index += 1;
        ui.memory_mut(|memory| {
            *memory
                .data
                .get_temp_mut_or_default::<usize>(popup_id.with("selected_index")) = selected_index;
        });
    }

    if ui.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::ArrowUp))
        && selected_index > 0
    {
        selected_index -= 1;
        ui.memory_mut(|memory| {
            *memory
                .data
                .get_temp_mut_or_default::<usize>(popup_id.with("selected_index")) = selected_index;
        });
    }

    let enter_pressed =
        ui.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::Enter));

    (selected_index, enter_pressed)
}

fn render_widget_catalog_list<W: Widget<C, M>, C, M>(
    ui: &mut egui::Ui,
    popup_id: egui::Id,
    tile_id: egui_tiles::TileId,
    behavior: &mut TileBehavior<W, C, M>,
    search: &str,
) {
    let catalog = W::catalog();
    let filtered: Vec<(usize, &super::widget::WidgetEntry<W>)> = catalog
        .iter()
        .enumerate()
        .filter(|(_, entry)| {
            search.is_empty() || contains_case_insensitive_ascii(&entry.name, search)
        })
        .collect();

    if filtered.is_empty() {
        ui.vertical_centered(|ui| {
            ui.add_space(10.0);
            ui.label("No matches found");
            ui.add_space(10.0);
        });
        return;
    }

    let (selected_index, enter_pressed) =
        handle_catalog_keyboard_navigation(ui, popup_id, filtered.len());

    let item_height = 28.0;
    let max_height = (filtered.len().min(8) as f32 * item_height) + 4.0;

    egui::ScrollArea::vertical()
        .id_salt(ui.next_auto_id())
        .max_height(max_height)
        .auto_shrink([false, false])
        .show_viewport(ui, |ui, _viewport| {
            ui.style_mut().visuals.button_frame = false;
            ui.style_mut().spacing.item_spacing.y = 1.0;

            if enter_pressed
                || ui.input(|input| {
                    input.key_pressed(egui::Key::ArrowUp) || input.key_pressed(egui::Key::ArrowDown)
                })
            {
                ui.scroll_to_rect(
                    egui::Rect::from_min_size(
                        egui::pos2(0.0, selected_index as f32 * item_height),
                        egui::vec2(1.0, item_height),
                    ),
                    None,
                );
            }

            for (display_index, (catalog_index, entry)) in filtered.iter().enumerate() {
                let is_selected = display_index == selected_index;

                let button = egui::Button::new(egui::RichText::new(&entry.name).size(14.0).color(
                    if is_selected {
                        ui.visuals().selection.stroke.color
                    } else {
                        ui.visuals().text_color()
                    },
                ));

                let button_response = ui.add_sized(
                    [ui.available_width(), item_height],
                    button.fill(if is_selected {
                        ui.visuals().selection.bg_fill
                    } else {
                        ui.visuals().extreme_bg_color
                    }),
                );

                if button_response.hovered() && !is_selected {
                    ui.memory_mut(|memory| {
                        *memory
                            .data
                            .get_temp_mut_or_default::<usize>(popup_id.with("selected_index")) =
                            display_index;
                    });
                }

                if button_response.clicked() || (enter_pressed && is_selected) {
                    behavior.pending_add_widget = Some((tile_id, *catalog_index));
                    behavior.layout_modified = true;
                    ui.memory_mut(|memory| {
                        *memory
                            .data
                            .get_temp_mut_or_default::<usize>(popup_id.with("selected_index")) = 0;
                    });
                    ui.ctx().request_repaint();
                }
            }
        });
}
