use crate::{HashMap, Ui, UiRegion, Vec2, WidgetId, util::Id};

pub trait SenseCtx: 'static {
    fn cursor_state(&self) -> CursorState;
    fn window_size(&self) -> Vec2;
    fn active(&mut self, trigger: &SenseTrigger) -> bool;
}

#[derive(Clone, Copy, PartialEq)]
pub enum Sense {
    PressStart,
    Pressing,
    PressEnd,
    HoverStart,
    Hovering,
    HoverEnd,
    NotHovering,
}

pub struct CursorState {
    pos: Vec2,
    exists: bool,
    primary: ActivationState,
}

#[derive(Debug, Clone, Copy, Default)]
pub enum ActivationState {
    Start,
    On,
    End,
    #[default]
    Off,
}

pub struct Sensor<Ctx> {
    pub sense: Sense,
    pub f: Box<dyn SenseFn<Ctx>>,
}

impl<Ctx: 'static> Clone for SensorGroup<Ctx> {
    fn clone(&self) -> Self {
        Self {
            shape: self.shape.clone(),
            hover: self.hover.clone(),
            f: self.f.box_clone(),
        }
    }
}
pub type SensorMap<Ctx> = HashMap<Id, Vec<Sensor<Ctx>>>;
pub type ActiveSensors<Ctx> = Vec<SensorGroup<Ctx>>;
pub trait SenseFn_<Ctx> = FnMut(&mut Ui<Ctx>, &mut Ctx) + 'static;
pub type SenseShape = UiRegion;
#[derive(Clone)]
pub struct SenseTrigger {
    pub shape: SenseShape,
    pub sense: Sense,
}
pub struct SensorGroup<Ctx> {
    pub shape: SenseShape,
    pub hover: ActivationState,
    pub sensors: Vec<Sensor<Ctx>>,
}
pub trait SenseFn<Ctx>: SenseFn_<Ctx> {
    fn box_clone(&self) -> Box<dyn SenseFn<Ctx>>;
}
impl<F: SenseFn_<Ctx> + Clone, Ctx> SenseFn<Ctx> for F {
    fn box_clone(&self) -> Box<dyn SenseFn<Ctx>> {
        Box::new(self.clone())
    }
}

impl<Ctx> Ui<Ctx> {
    pub fn add_sensor<W>(&mut self, id: &WidgetId<W>, f: Sensor<Ctx>) {
        self.sensor_map
            .entry(id.id.duplicate())
            .or_default()
            .push(f);
    }

    pub fn run_sensors(&mut self, ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2)
    where
        Ctx: SenseCtx + 'static,
    {
        let mut groups = std::mem::take(&mut self.active_sensors_swap);
        groups.clone_from(&self.active_sensors);
        for (hover, sensors) in groups.drain(..).rev() {
            for mut sensor in sensors {
                if should_run(sensor.trigger, cursor, hover, window_size) {
                    (sensor.f)(self, ctx);
                }
            }
        }
        self.active_sensors_swap = groups;
    }
}

pub fn should_run(
    trigger: SenseTrigger,
    cursor: &CursorState,
    hover: ActivationState,
    window_size: Vec2,
) -> bool {
    let region = trigger.shape.to_screen(window_size);
    if !cursor.exists || !region.contains(cursor.pos) {
        return trigger.sense == Sense::NotHovering;
    }
}
