use crate::map::sources::{ActiveLayer, LayerId, LayerSource};
use crate::map::tiles::{LayerManager, SourceId};
use crate::ui::shell::bars::pane_header;
use crate::ui::tokens::DESIGN_TOKENS;
use crate::ui::UiExt;
use eframe::emath::Align;
use egui::epaint::Marginf;
use egui::frame::Prepared;
use egui::{
    vec2, Button, Color32, CornerRadius, CursorIcon, FontFamily, Frame, Id, InnerResponse, Label,
    Layout, Margin, Order, Rect, Response, RichText, ScrollArea, Sense, Separator, Theme, Ui,
    UiBuilder, Visuals,
};
use std::collections::HashMap;
use std::sync::Arc;
use tracing::debug;

fn outer_rect(f: &Prepared) -> Rect {
    let content_rect = f.content_ui.min_rect();

    content_rect + f.frame.inner_margin + Marginf::from(f.frame.stroke.width) + f.frame.outer_margin
}

pub fn left_bar(
    ctx: &egui::Context,
    add_open: &mut bool,
    layer_manager: &mut LayerManager,
    selected_layer: &mut Option<LayerId>,
) {
    let mut frame = Frame::side_top_panel(&ctx.style());
    frame.inner_margin.top = 4;
    frame.inner_margin.left = 0;
    frame.inner_margin.right = 0;

    let mut needs_any_update = false;

    egui::SidePanel::left("left_panel")
        .frame(frame)
        .show(ctx, |ui| {
            pane_header(
                ui,
                "Layers",
                Some(egui_phosphor::regular::STACK),
                false,
                |ui| {
                    ui.style_mut().visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT;
                    if ui
                        .button(RichText::new(egui_phosphor::regular::PLUS).size(12.0))
                        .clicked()
                    {
                        *add_open = true;
                    }
                },
            );

            let lm_registry = &layer_manager.registered_sources;
            let layers_to_draw = layer_manager.active_layers.len();

            let mut layers_to_remove = vec![];

            for i in 0..layer_manager.active_layers.len() {
                let (retain, update_layers) = draw_layer(
                    ui,
                    i,
                    lm_registry,
                    &mut layer_manager.active_layers,
                    selected_layer,
                );

                if !retain {
                    layers_to_remove.push(layer_manager.active_layers[i].layer_id);
                }
                needs_any_update = needs_any_update || update_layers;
            }

            for layer_to_remove in &layers_to_remove {
                if *selected_layer == Some(*layer_to_remove) {
                    *selected_layer = None;
                }
            }

            layer_manager
                .active_layers
                .retain(|layer| !layers_to_remove.contains(&layer.layer_id));
        });

    if *add_open {
        let mut frame = Frame::popup(&ctx.style()).corner_radius(CornerRadius::ZERO);
        frame.inner_margin.left = 0;
        frame.inner_margin.right = 0;
        egui::Modal::new(Id::new("add_source_modal"))
            .frame(frame)
            .show(ctx, |ui| {
                pane_header(
                    ui,
                    "Add layer",
                    Some(egui_phosphor::regular::STACK_PLUS),
                    false,
                    |ui| {
                        ui.style_mut().visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT;
                        if ui
                            .button(RichText::new(egui_phosphor::regular::X).size(12.0))
                            .clicked()
                        {
                            *add_open = false;
                        }
                    },
                );

                for (id, registered_layer) in &layer_manager.registered_sources {
                    // ghost button

                    let mut frame = Frame::new();

                    frame.inner_margin.top = 4;
                    frame.inner_margin.left = 8;
                    frame.inner_margin.right = 8;
                    frame.inner_margin.bottom = 4;

                    let mut frame = frame.begin(ui);

                    {
                        frame.content_ui.horizontal(|ui| {
                            let mut frame = Frame::new().fill(
                                DESIGN_TOKENS
                                    .get_color(
                                        "colors.base.4",
                                        if ui.style().visuals.dark_mode {
                                            Theme::Dark
                                        } else {
                                            Theme::Light
                                        },
                                    )
                                    .unwrap(),
                            );

                            frame.inner_margin.top = 4;
                            frame.inner_margin.left = 8;
                            frame.inner_margin.right = 8;
                            frame.inner_margin.bottom = 4;

                            frame.corner_radius = CornerRadius::from(4);

                            frame.show(ui, |ui| {
                                ui.label(
                                    RichText::new(registered_layer.type_hint.icon()).size(24.0),
                                );
                            });

                            ui.vertical(|ui| {
                                ui.label(&registered_layer.display_name);
                                ui.horizontal(|ui| {
                                    let type_hint = registered_layer.type_hint.name();
                                    let icon = registered_layer.type_hint.icon();

                                    let type_lbl = format!("{icon} {type_hint}");
                                    ui.allocate_ui_with_layout(
                                        vec2(90.0, 20.0),
                                        Layout::left_to_right(Align::Center),
                                        |ui| {
                                            ui.label(RichText::new(type_lbl).weak());
                                            ui.add_space(ui.available_width());
                                        },
                                    );

                                    ui.allocate_ui_with_layout(
                                        vec2(120.0, 20.0),
                                        Layout::left_to_right(Align::Center),
                                        |ui| {
                                            ui.label(
                                                RichText::new(format!(
                                                    "{} {}",
                                                    egui_phosphor::regular::NAVIGATION_ARROW,
                                                    &registered_layer.location
                                                ))
                                                .weak(),
                                            );
                                            ui.add_space(ui.available_width());
                                        },
                                    );

                                    ui.allocate_ui_with_layout(
                                        vec2(100.0, 20.0),
                                        Layout::left_to_right(Align::Center),
                                        |ui| {
                                            ui.label(
                                                RichText::new(format!(
                                                    "{} {}",
                                                    egui_phosphor::regular::IDENTIFICATION_BADGE,
                                                    &registered_layer.source
                                                ))
                                                .weak(),
                                            );
                                            ui.add_space(ui.available_width());
                                        },
                                    );
                                });
                            });

                            ui.add_space(ui.available_width());
                        });
                    }
                    let response = ui.allocate_rect(outer_rect(&frame), Sense::click_and_drag());
                    if response.hovered() {
                        frame.frame.fill = DESIGN_TOKENS
                            .get_color(
                                "colors.base.7",
                                if ui.style().visuals.dark_mode {
                                    Theme::Dark
                                } else {
                                    Theme::Light
                                },
                            )
                            .unwrap();
                        ui.ctx()
                            .output_mut(|u| u.cursor_icon = CursorIcon::PointingHand);
                    }
                    if response.clicked() {
                        layer_manager.active_layers.push(ActiveLayer {
                            source_id: *id,
                            layer_id: LayerId::new(),
                            visible: true,
                        });
                        debug!("{:?}", layer_manager);
                        *add_open = false;
                    }
                    frame.paint(ui);
                }
            });
    }
}

fn draw_layer(
    ui: &mut Ui,
    you: usize,
    registry: &HashMap<SourceId, LayerSource>,
    active_layers: &mut Vec<ActiveLayer>,
    selected_layer: &mut Option<LayerId>,
) -> (bool, bool) {
    let mut retain = true;
    let mut needs_layers_update = false;
    let layer = active_layers[you];

    let source = registry.get(&layer.source_id).unwrap();

    let r = ui.scope_builder(
        UiBuilder::new()
            .id_salt(layer.layer_id)
            .sense(Sense::click_and_drag()),
        |ui| {
            let title = source.short_name.as_str();
            let icon = source.type_hint.icon();

            let mut any_children_hovered = false;

            let mut frame = Frame::new();
            //frame.fill = ui.style().visuals.hyperlink_color;
            frame.inner_margin.left = 8;
            frame.inner_margin.right = 8;
            frame.inner_margin.top = 2;
            frame.inner_margin.bottom = 2;

            let mut frame = frame.begin(ui);
            {
                frame.content_ui.horizontal(|ui| {
                    ui.add(
                        Label::new(
                            RichText::new(egui_phosphor::regular::DOTS_SIX_VERTICAL)
                                .size(12.0)
                                .family(FontFamily::Name("icon-only".into()))
                                .weak(),
                        )
                        .sense(Sense::empty())
                        .selectable(false),
                    );

                    ui.add_space(8.0);

                    let mut name_label = RichText::new(format!("{icon} {title}"));
                    if !layer.visible {
                        name_label = name_label.weak();
                    }

                    ui.add(
                        Label::new(name_label)
                            .sense(Sense::empty())
                            .selectable(false),
                    );

                    ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
                        ui.style_mut().visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT;

                        let mut visibility_label = RichText::new(if layer.visible {
                            egui_phosphor::regular::EYE
                        } else {
                            egui_phosphor::regular::EYE_SLASH
                        })
                        .size(12.0)
                        .family(FontFamily::Name("icon-only".into()));

                        if !layer.visible {
                            visibility_label = visibility_label.weak();
                        }

                        let visibility_btn = ui.button(visibility_label);

                        any_children_hovered = any_children_hovered || visibility_btn.hovered();
                        if visibility_btn.clicked() {
                            active_layers[you].visible = !active_layers[you].visible;
                        }

                        let mut delete_label = RichText::new(egui_phosphor::regular::TRASH)
                            .size(12.0)
                            .family(FontFamily::Name("icon-only".into()));

                        if !layer.visible {
                            delete_label = delete_label.weak();
                        }

                        let delete_button = ui.button(delete_label);
                        any_children_hovered = any_children_hovered || delete_button.hovered();

                        if delete_button.clicked() {
                            retain = false;
                            needs_layers_update = true;
                        }
                    });
                });
            }
            frame.allocate_space(ui);

            let response = ui.response();

            if response.hovered() && !any_children_hovered {
                frame.frame.fill = ui.visuals().widgets.active.weak_bg_fill;
                ui.ctx().output_mut(|u| u.cursor_icon = CursorIcon::Grab);
            }

            if response.clicked() {
                *selected_layer = Some(layer.layer_id);
            }

            if let Some(sel_layer) = selected_layer {
                if *sel_layer == layer.layer_id {
                    if !layer.visible {
                        frame.frame.fill = ui.visuals().widgets.inactive.bg_fill;
                    } else {
                        frame.frame.fill = ui.visuals().widgets.hovered.bg_fill;
                    }
                }
            }

            frame.paint(ui);
        },
    );

    let response = r.response;

    let is_layer_being_dragged = egui::DragAndDrop::has_payload_of_type::<LayerId>(ui.ctx());
    if is_layer_being_dragged {
        let layer_being_dragged: Arc<LayerId> = egui::DragAndDrop::payload(ui.ctx()).unwrap();
        if *layer_being_dragged != layer.layer_id && response.contains_pointer() {
            let rect = response.rect;
            if let Some(pointer) = ui.input(|i| i.pointer.interact_pos()) {
                let stroke = ui.style().visuals.widgets.active.fg_stroke;

                let above = pointer.y < rect.center().y;
                if above {
                    // above us
                    ui.painter().hline(rect.x_range(), rect.top(), stroke);
                } else {
                    // below us
                    ui.painter().hline(rect.x_range(), rect.bottom(), stroke);
                }

                if let Some(payload) = response.dnd_release_payload() {
                    // released this frame
                    let layer_id: LayerId = *payload;
                    // remove it from the array

                    let pos = active_layers
                        .iter()
                        .position(|u| u.layer_id == layer_id)
                        .unwrap();
                    let layer = active_layers.remove(pos);

                    // add it back at the appropriate index
                    let mut add_idx = you;
                    if !above {
                        add_idx += 1;
                    }
                    if add_idx > active_layers.len() {
                        active_layers.push(layer);
                    } else {
                        active_layers.insert(add_idx, layer);
                    }

                    needs_layers_update = true;
                }
            }
        }
    }

    if response.dragged() {
        egui::DragAndDrop::set_payload(ui.ctx(), layer.layer_id);
        *selected_layer = Some(layer.layer_id);
        let layer_id = egui::LayerId::new(Order::Tooltip, layer.layer_id.into());
        ui.ctx().style_mut(|style| {
            style.visuals.window_fill = DESIGN_TOKENS
                .get_color(
                    "colors.accent.3",
                    if ui.style().visuals.dark_mode {
                        Theme::Dark
                    } else {
                        Theme::Light
                    },
                )
                .unwrap();
            style.visuals.widgets.noninteractive.fg_stroke.color = DESIGN_TOKENS
                .get_color(
                    "colors.accent.12",
                    if ui.style().visuals.dark_mode {
                        Theme::Dark
                    } else {
                        Theme::Light
                    },
                )
                .unwrap();
        });
        egui::show_tooltip(ui.ctx(), layer_id, egui::Id::new("drag_detail"), |ui| {
            ui.horizontal(|ui| {
                ui.label(RichText::new(source.type_hint.icon()));
                ui.label(RichText::new(&source.short_name));
            })
        });
        // reset style
        DESIGN_TOKENS.set_color(ui.ctx());
    }

    if response.drag_started() {}

    // handle potential droppage
    let Some(dragged_layer_id) =
        egui::DragAndDrop::payload(ui.ctx()).map(|payload: Arc<LayerId>| (*payload))
    else {
        return (retain, needs_layers_update); // nothing is being dragged
    };
    ui.ctx().set_cursor_icon(CursorIcon::Grabbing);

    (retain, needs_layers_update)
}
