use std::{
    collections::HashMap,
    sync::mpsc::{self},
};

use cgmath::{EuclideanSpace, Point2, Vector2};
use rand::prelude::*;
use winit::event_loop::EventLoopProxy;

use crate::util::{UserEvent, p2};

trait UiClosure: Fn(&mut egui::Ui, &mut bool) -> bool + Send {}
impl<T: Fn(&mut egui::Ui, &mut bool) -> bool + Send> UiClosure for T {}

struct Dialog {
    name: &'static str,
    closure: Box<dyn UiClosure>,
}

pub struct DialogManager {
    dialogs: HashMap<u64, Dialog>,
    receiver: mpsc::Receiver<Dialog>,
    proxy: DialogProxy,
}

impl DialogManager {
    pub fn new(proxy: EventLoopProxy<UserEvent>) -> Self {
        let dialogs = HashMap::new();
        let (sender, receiver) = mpsc::channel();

        Self {
            dialogs,
            receiver,
            proxy: DialogProxy { sender, proxy },
        }
    }

    pub fn get_proxy(&self) -> DialogProxy {
        self.proxy.clone()
    }

    pub fn update(&mut self, ctx: &egui::Context, size: Vector2<f32>, enter: &mut bool) {
        let mut rng = rand::rng();
        while let Ok(dialog) = self.receiver.try_recv() {
            self.dialogs.insert(rng.random(), dialog);
        }

        for key in self.dialogs.keys().copied().collect::<Vec<_>>() {
            let dialog = &self.dialogs[&key];
            let mut open = true;
            let mut done = false;
            egui::Window::new(dialog.name)
                .id(egui::Id::new(key))
                .collapsible(false)
                .resizable(false)
                .pivot(egui::Align2::CENTER_CENTER)
                .default_pos(p2(Point2::from_vec(size / 2.0)))
                .open(&mut open)
                .show(ctx, |ui| done = (dialog.closure)(ui, enter));
            if !open || done {
                self.dialogs.remove(&key);
            }
        }
    }
}

#[derive(Clone)]
pub struct DialogProxy {
    sender: mpsc::Sender<Dialog>,
    proxy: EventLoopProxy<UserEvent>,
}

impl DialogProxy {
    pub fn spawn_dialog<T, F>(&self, name: &'static str, closure: F) -> DialogHandle<T>
    where
        F: Fn(&mut egui::Ui, &mut bool) -> Option<T> + Send + 'static,
        T: 'static + Send,
    {
        let (tx, rx) = mpsc::channel();
        let outer = move |ui: &mut egui::Ui, enter: &mut bool| {
            let value = (closure)(ui, enter);
            match value {
                Some(value) => {
                    let _ = tx.send(value);
                    true
                }
                None => false,
            }
        };

        let dialog = Dialog {
            name,
            closure: Box::new(outer),
        };

        let _ = self.sender.send(dialog);
        let _ = self.proxy.send_event(UserEvent::Wake);
        DialogHandle(rx)
    }
}

pub struct DialogHandle<T>(mpsc::Receiver<T>);

impl<T> DialogHandle<T> {
    pub fn wait(self) -> Option<T> {
        self.0.recv().ok()
    }
}
