#![allow(clippy::forget_non_drop)] use bevy::pbr::NotShadowReceiver;
use bevy::scene::SceneInstance;
use bevy::{pbr::NotShadowCaster, prelude::*};
use de_core::{
    gamestate::GameState,
    objects::{MovableSolid, ObjectTypeComponent, StaticSolid},
    state::AppState,
};
use de_index::{ColliderWithCache, PreciseIndexSet, QueryCollider, SpatialQuery};
use de_map::size::MapBounds;
use de_objects::{AssetCollection, SceneType, Scenes, SolidObjects, EXCLUSION_OFFSET};
use de_types::{
    objects::{ActiveObjectType, BuildingType, ObjectType},
    projection::ToFlat,
};
use parry2d::{
    bounding_volume::{Aabb, BoundingVolume},
    math::Vector,
};
use parry3d::math::Isometry;
const MAP_PADDING: f32 = 2. * EXCLUSION_OFFSET + 0.1;
const MAP_OFFSET: Vector<f32> = Vector::new(MAP_PADDING, MAP_PADDING);
const VALID_PLACEMENT: Color = Color::rgba(0.2, 0.8, 0.2, 0.7);
const INVALID_PLACEMENT: Color = Color::rgba(0.86, 0.08, 0.24, 0.7);
pub(crate) struct DraftPlugin;
impl Plugin for DraftPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(OnEnter(AppState::InGame), insert_materials)
            .add_systems(OnExit(AppState::InGame), cleanup)
            .add_systems(Update, new_draft.run_if(in_state(GameState::Playing)))
            .add_systems(
                PostUpdate,
                (update_draft, check_draft_loaded, update_draft_colour)
                    .run_if(in_state(GameState::Playing))
                    .after(PreciseIndexSet::Index),
            );
    }
}
#[derive(Bundle)]
pub struct DraftBundle {
    object_type: ObjectTypeComponent,
    transform: Transform,
    global_transform: GlobalTransform,
    visibility: VisibilityBundle,
    draft: DraftAllowed,
    ready: DraftReady,
}
impl DraftBundle {
    pub fn new(building_type: BuildingType, transform: Transform) -> Self {
        Self {
            object_type: ObjectType::Active(ActiveObjectType::Building(building_type)).into(),
            transform,
            global_transform: transform.into(),
            visibility: VisibilityBundle::default(),
            draft: DraftAllowed::default(),
            ready: DraftReady::default(),
        }
    }
}
#[derive(Component, Default)]
pub struct DraftAllowed(bool);
impl DraftAllowed {
    pub fn allowed(&self) -> bool {
        self.0
    }
}
#[derive(Component, Default)]
struct DraftReady(bool);
type Solids<'w, 's> = SpatialQuery<'w, 's, Entity, Or<(With<StaticSolid>, With<MovableSolid>)>>;
fn new_draft(
    mut commands: Commands,
    drafts: Query<(Entity, &ObjectTypeComponent), Added<DraftAllowed>>,
    scenes: Res<Scenes>,
) {
    for (entity, object_type) in drafts.iter() {
        commands.entity(entity).with_children(|parent| {
            parent.spawn(SceneBundle {
                scene: scenes.get(SceneType::Solid(**object_type)).clone(),
                ..Default::default()
            });
        });
    }
}
fn update_draft(
    mut drafts: Query<(&Transform, &ObjectTypeComponent, &mut DraftAllowed)>,
    solids: Solids,
    solid_objects: SolidObjects,
    bounds: Res<MapBounds>,
) {
    for (transform, &object_type, mut draft) in drafts.iter_mut() {
        let collider = QueryCollider::new(
            solid_objects.get(*object_type).collider(),
            Isometry::new(
                transform.translation.into(),
                transform.rotation.to_scaled_axis().into(),
            ),
        );
        let flat_aabb = collider.world_aabb().to_flat();
        let shrinked_map = {
            let aabb = bounds.aabb();
            Aabb::new(aabb.mins + MAP_OFFSET, aabb.maxs - MAP_OFFSET)
        };
        let allowed = shrinked_map.contains(&flat_aabb) && !solids.collides(&collider);
        if allowed != draft.0 {
            draft.0 = allowed
        }
    }
}
#[derive(Clone, Resource)]
struct DraftMaterials {
    valid_placement: Handle<StandardMaterial>,
    invalid_placement: Handle<StandardMaterial>,
}
fn cleanup(mut commands: Commands) {
    commands.remove_resource::<DraftMaterials>();
}
fn insert_materials(mut commands: Commands, mut materials: ResMut<Assets<StandardMaterial>>) {
    commands.insert_resource(DraftMaterials {
        valid_placement: materials.add(VALID_PLACEMENT),
        invalid_placement: materials.add(INVALID_PLACEMENT),
    });
}
fn update_object_material(
    entity: Entity,
    allowed: bool,
    standard_materials: &mut Query<&mut Handle<StandardMaterial>>,
    draft_materials: &DraftMaterials,
) {
    let Ok(mut material_handle) = standard_materials.get_mut(entity) else {
        return;
    };
    if allowed {
        *material_handle = draft_materials.valid_placement.clone();
    } else {
        *material_handle = draft_materials.invalid_placement.clone();
    }
}
fn check_draft_loaded(
    scene_spawner: Res<SceneSpawner>,
    instances: Query<(&Parent, &SceneInstance)>,
    mut drafts: Query<&mut DraftReady>,
) {
    for (parent, instance) in instances.iter() {
        if let Ok(mut draft) = drafts.get_mut(parent.get()) {
            let ready = scene_spawner.instance_is_ready(**instance);
            if draft.0 != ready {
                draft.0 = ready;
            }
        }
    }
}
type ChangedDraftQuery<'w, 's> = Query<
    'w,
    's,
    (
        &'static DraftAllowed,
        Ref<'static, DraftReady>,
        &'static Children,
    ),
    Or<(Changed<DraftAllowed>, Changed<DraftReady>)>,
>;
fn update_draft_colour(
    mut commands: Commands,
    draft_query: ChangedDraftQuery,
    scene_instances_query: Query<&SceneInstance>,
    mut standard_materials: Query<&mut Handle<StandardMaterial>>,
    scene_spawner: Res<SceneSpawner>,
    draft_materials: Res<DraftMaterials>,
) {
    for (draft, ready, children) in draft_query.iter() {
        if !ready.0 {
            continue;
        }
        let allowed = draft.allowed();
        for &child in children.into_iter() {
            let Ok(scene_instance) = scene_instances_query.get(child) else {
                continue;
            };
            let entities = scene_spawner.iter_instance_entities(**scene_instance);
            for entity in entities {
                if ready.is_changed() {
                    commands
                        .entity(entity)
                        .insert((NotShadowCaster, NotShadowReceiver));
                }
                update_object_material(entity, allowed, &mut standard_materials, &draft_materials);
            }
        }
    }
}