use bevy::prelude::*;
use de_core::{
    frustum,
    gamestate::GameState,
    objects::{ObjectTypeComponent, Playable},
    schedule::InputSchedule,
    screengeom::ScreenRect,
};
use de_objects::SolidObjects;
use de_types::objects::ObjectType;
use crate::{
    frustum::ScreenFrustum,
    selection::{SelectEvent, SelectionMode, SelectionSet},
};
pub(super) struct AreaPlugin;
impl Plugin for AreaPlugin {
    fn build(&self, app: &mut App) {
        app.add_event::<SelectInRectEvent>().add_systems(
            InputSchedule,
            select_in_area
                .run_if(in_state(GameState::Playing))
                .in_set(AreaSelectSet::SelectInArea)
                .before(SelectionSet::Update),
        );
    }
}
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, SystemSet)]
pub(crate) enum AreaSelectSet {
    SelectInArea,
}
#[derive(Event)]
pub(crate) struct SelectInRectEvent {
    rect: ScreenRect,
    mode: SelectionMode,
    filter_object_type: Option<ObjectType>,
}
impl SelectInRectEvent {
    pub(crate) fn new(
        rect: ScreenRect,
        mode: SelectionMode,
        filter_object_type: Option<ObjectType>,
    ) -> Self {
        Self {
            rect,
            mode,
            filter_object_type,
        }
    }
    fn rect(&self) -> ScreenRect {
        self.rect
    }
    fn mode(&self) -> SelectionMode {
        self.mode
    }
    fn filter_object_type(&self) -> Option<ObjectType> {
        self.filter_object_type
    }
}
fn select_in_area(
    screen_frustum: ScreenFrustum,
    solids: SolidObjects,
    candidates: Query<(Entity, &ObjectTypeComponent, &Transform), With<Playable>>,
    mut in_events: EventReader<SelectInRectEvent>,
    mut out_events: EventWriter<SelectEvent>,
) {
    for in_event in in_events.read() {
        let event_frustum = screen_frustum.rect(in_event.rect());
        let entities: Vec<Entity> = candidates
            .iter()
            .filter(|(_, &object_type, _)| {
                in_event
                    .filter_object_type()
                    .map_or(true, |filter| filter == *object_type)
            })
            .filter_map(|(entity, &object_type, &transform)| {
                let aabb = solids.get(*object_type).collider().aabb();
                if frustum::intersects_parry(&event_frustum, transform, &aabb) {
                    Some(entity)
                } else {
                    None
                }
            })
            .collect();
        out_events.send(SelectEvent::many(entities, in_event.mode()));
    }
}