// Monologe Trees Editor
//
// Editor for creating Monologue Trees levels
//
// BUGS:
//
// TODO:
// * edit textbox with actions
// * (brutal) export level
// * (hard) import level
// * (hard) Harden Active Camera
// * (medium) Pre-compute animation target entities
// * make min/max/close buttons into actions not selects
// * (???) Better handle hide/close monologue

use bevy::{
    asset::{Asset, AssetLoader, Assets, ChangeWatcher, LoadContext, LoadedAsset},
    audio::PlaybackMode,
    gltf::Gltf,
    prelude::*,
    utils::{BoxedFuture, Duration},
};
use monologue_trees::{debug::*, ui};

const WELCOME_MESSAGES: &'static [&'static str] = &[
    "Welcome to the Monologue Trees editor!",
    "Import assets by dragging and dropping files or folders into the editor!",
    concat!(
        "Supported file types (for now):\n",
        "* 3D: .gltf, .glb\n",
        "* Audio: .ogg\n",
        "* Font: .ttf, .otf\n",
        "* Monologues: .monologue.txt",
    ),
];

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins
                .set(WindowPlugin {
                    primary_window: Some(Window {
                        title: "Monologue Trees Editor".into(),
                        resolution: (640., 480.).into(),
                        ..default()
                    }),
                    ..default()
                })
                .set(AssetPlugin {
                    watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(200)),
                    ..default()
                }),
            DebugInfoPlugin,
            ui::GameUiPlugin {
                enable_alerts: true,
            },
        ))
        .register_type::<LevelRoot>()
        .init_resource::<AssetRegistry>()
        .init_resource::<FontInfo>()
        .add_asset::<Monologue>()
        .init_asset_loader::<MonologueLoader>()
        .add_systems(Startup, (initialize_ui, init_texts_ui, welcome_message))
        .add_systems(
            Update,
            (
                init_animations_ui,
                remove_animations_ui,
                add_animations_ui,
                play_all_animations,
                play_animation,
            ),
        )
        .add_systems(
            Update,
            (remove_scenes_ui, add_scenes_ui, control_active_scenes),
        )
        .add_systems(Update, (cameras_ui, manage_active_camera, fallback_camera))
        .add_systems(Update, (audio_ui, play_audio, pause_audio))
        .add_systems(
            Update,
            (
                gltf_ui,
                texts_ui,
                control_active_gltf,
                show_preview_text,
                sync_monologue_font,
            ),
        )
        .add_systems(Update, (fonts_ui, set_active_font))
        .add_systems(Startup, reload_assets)
        .add_systems(
            Update,
            (
                reload_assets.run_if(ui::activated::<ReloadAssets>),
                clear_assets.run_if(ui::activated::<ClearAssets>),
            ),
        )
        .add_systems(
            Update,
            (
                point_light_force_shadows,
                spot_light_force_shadows,
                directional_light_force_shadows,
            ),
        )
        .add_systems(Update, clear_level)
        .add_systems(
            Update,
            (
                level_ui,
                load_level,
                export_level.run_if(ui::activated::<ExportLevel>),
                rehydrate_level,
            ),
        )
        .run();
}

#[derive(Resource, Default)]
pub struct AssetRegistry(Vec<HandleUntyped>);

#[derive(Debug, Component)]
pub struct TabRoot;

#[derive(Debug, Component, Reflect, Default)]
#[reflect(Component)]
pub struct LevelRoot;

#[derive(Debug, Component)]
pub struct EditorCamera;

fn initialize_ui(mut commands: Commands) {
    // Empty entity for populating the level being edited
    commands.spawn((SpatialBundle { ..default() }, LevelRoot::default()));

    commands.spawn((
        Camera2dBundle { ..default() },
        UiCameraConfig { show_ui: true },
        Name::new("Editor Camera"),
        EditorCamera,
        ui::Active,
    ));

    let base_style = Style {
        border: UiRect::all(Val::Px(1.0)),
        padding: UiRect::all(Val::Px(1.0)),
        overflow: Overflow::clip(),
        flex_direction: FlexDirection::Column,
        ..default()
    };

    let simple_button = ButtonBundle {
        style: Style {
            ..base_style.clone()
        },
        background_color: Color::WHITE.into(),
        border_color: Color::BLACK.into(),
        ..default()
    };

    commands
        .spawn(NodeBundle {
            style: Style {
                top: Val::Px(0.0),
                left: Val::Px(0.0),
                position_type: PositionType::Absolute,
                border: UiRect::all(Val::Px(1.0)),
                margin: UiRect::all(Val::Px(1.0)),
                padding: UiRect::all(Val::Px(1.0)),
                flex_direction: FlexDirection::Column,
                overflow: Overflow::clip(),
                ..default()
            },
            background_color: Color::WHITE.into(),
            border_color: Color::BLACK.into(),
            ..default()
        })
        .with_children(|parent| {
            let container = parent
                .spawn((NodeBundle {
                    style: Style {
                        border: UiRect::all(Val::Px(1.0)),
                        margin: UiRect::all(Val::Px(1.0)),
                        padding: UiRect::all(Val::Px(1.0)),
                        flex_direction: FlexDirection::Row,
                        overflow: Overflow::clip(),
                        ..default()
                    },
                    background_color: Color::WHITE.into(),
                    border_color: Color::BLACK.into(),
                    ..default()
                },))
                .with_children(|parent| {
                    // HACK: This is super janky but I think we need it like this for UI layout rules
                    let mut content_containers: Vec<(String, Entity)> = Vec::new();

                    // Containers with asset content
                    parent
                        .spawn((
                            NodeBundle {
                                style: Style {
                                    border: UiRect::all(Val::Px(1.0)),
                                    margin: UiRect::all(Val::Px(1.0)),
                                    padding: UiRect::all(Val::Px(1.0)),
                                    flex_direction: FlexDirection::Column,
                                    overflow: Overflow::clip(),
                                    justify_content: JustifyContent::FlexStart,
                                    ..default()
                                },
                                background_color: Color::WHITE.into(),
                                border_color: Color::BLACK.into(),
                                ..default()
                            },
                            ui::Sorting(2),
                        ))
                        .with_children(|parent| {
                            content_containers.push(spawn_tab_container::<FontWidget>(
                                "Font",
                                parent,
                                ui::Select::Single,
                            ));
                            content_containers.push(spawn_tab_container::<MonologueWidget>(
                                "Monologue",
                                parent,
                                ui::Select::Single,
                            ));
                            content_containers.push(spawn_tab_container::<AudioWidget>(
                                "Audio",
                                parent,
                                ui::Select::Multi,
                            ));
                            content_containers.push(spawn_tab_container::<LevelWidget>(
                                "Level",
                                parent,
                                ui::Select::Single,
                            ));
                            content_containers.push(spawn_tab_container::<GltfWidget>(
                                "Gltf",
                                parent,
                                ui::Select::Single,
                            ));
                            content_containers.push(spawn_tab_container::<SceneWidget>(
                                "Scene",
                                parent,
                                ui::Select::Single,
                            ));
                            content_containers.push(spawn_tab_container::<CameraWidget>(
                                "Camera",
                                parent,
                                ui::Select::Single,
                            ));
                            content_containers.push(spawn_tab_container::<AnimationWidget>(
                                "Animation",
                                parent,
                                ui::Select::Multi,
                            ));
                        });

                    // Container for tabs that open/close containers
                    parent
                        .spawn((
                            NodeBundle {
                                style: Style {
                                    border: UiRect::all(Val::Px(1.0)),
                                    margin: UiRect::all(Val::Px(1.0)),
                                    padding: UiRect::all(Val::Px(1.0)),
                                    flex_direction: FlexDirection::Column,
                                    overflow: Overflow::clip(),
                                    ..default()
                                },
                                background_color: Color::WHITE.into(),
                                border_color: Color::BLACK.into(),
                                ..default()
                            },
                            ui::Sorting(1),
                            ui::Select::Single,
                        ))
                        .with_children(|parent| {
                            content_containers.iter().enumerate().for_each(
                                |(i, (name, target))| {
                                    parent.spawn((
                                        simple_button.clone(),
                                        ui::Title {
                                            text: name.clone(),
                                            ..default()
                                        },
                                        ui::Collapse { target: *target },
                                        ui::Sorting(i as u8),
                                    ));
                                },
                            );
                        });
                })
                .id();
            parent.spawn((
                ui::TitleBarBase::new(Color::WHITE).bundle(),
                ui::Title {
                    text: "Assets".into(),
                    ..default()
                },
                ui::Minimize { target: container },
                ui::Sorting(0),
            ));
        });
    commands
        .spawn(NodeBundle {
            style: Style {
                bottom: Val::Px(0.0),
                left: Val::Px(0.0),
                position_type: PositionType::Absolute,
                border: UiRect::all(Val::Px(1.0)),
                margin: UiRect::all(Val::Px(1.0)),
                padding: UiRect::all(Val::Px(1.0)),
                flex_direction: FlexDirection::Column,
                overflow: Overflow::clip(),
                ..default()
            },
            background_color: Color::WHITE.into(),
            border_color: Color::BLACK.into(),
            ..default()
        })
        .with_children(|parent| {
            let container = parent
                .spawn((
                    NodeBundle {
                        style: Style {
                            border: UiRect::all(Val::Px(1.0)),
                            margin: UiRect::all(Val::Px(1.0)),
                            padding: UiRect::all(Val::Px(1.0)),
                            flex_direction: FlexDirection::Column,
                            overflow: Overflow::clip(),
                            ..default()
                        },
                        background_color: Color::WHITE.into(),
                        border_color: Color::BLACK.into(),
                        ..default()
                    },
                    ui::Sorting(99),
                    ui::Select::Action,
                ))
                .with_children(|parent| {
                    parent.spawn((
                        simple_button.clone(),
                        ReloadAssets,
                        ui::Sorting(1),
                        ui::Title {
                            text: "Reload Assets".into(),
                            ..default()
                        },
                    ));
                    parent.spawn((
                        simple_button.clone(),
                        ClearAssets,
                        ui::Sorting(2),
                        ui::Title {
                            text: "Clear Assets".into(),
                            ..default()
                        },
                    ));
                    parent.spawn((
                        simple_button.clone(),
                        ExportLevel,
                        ui::Sorting(3),
                        ui::Title {
                            text: "Export Level".into(),
                            ..default()
                        },
                    ));
                    parent.spawn((
                        simple_button.clone(),
                        ClearLevel,
                        ui::Sorting(3),
                        ui::Title {
                            text: "Reset Level".into(),
                            ..default()
                        },
                    ));
                })
                .id();
            parent.spawn((
                ui::TitleBarBase::new(Color::WHITE).bundle(),
                ui::Title {
                    text: "Actions".into(),
                    ..default()
                },
                ui::Minimize { target: container },
                ui::Sorting(0),
            ));
        });
}

fn welcome_message(mut writer: EventWriter<ui::Alert>) {
    WELCOME_MESSAGES
        .iter()
        .for_each(|&msg| writer.send(ui::Alert::Info(msg.into())))
}

fn spawn_tab_container<T: Default + Component>(
    title: &'static str,
    parent: &mut ChildBuilder,
    select: ui::Select,
) -> (String, Entity) {
    (
        title.into(),
        // Content node
        parent
            .spawn((
                NodeBundle {
                    style: Style {
                        display: Display::None,
                        border: UiRect::all(Val::Px(1.0)),
                        margin: UiRect::all(Val::Px(1.0)),
                        padding: UiRect::all(Val::Px(1.0)),
                        flex_direction: FlexDirection::Column,
                        ..default()
                    },
                    background_color: Color::WHITE.into(),
                    border_color: Color::BLACK.into(),
                    ..default()
                },
                T::default(),
                ui::Scroll,
                Interaction::default(),
                select,
            ))
            .id(),
    )
}

use audio::*;
mod audio {

    use super::*;

    #[derive(Debug, Component, Default)]
    pub struct AudioWidget;

    pub fn audio_ui(
        mut events: EventReader<AssetEvent<AudioSource>>,
        mut commands: Commands,
        widget: Query<Entity, With<AudioWidget>>,
        current: Query<(Entity, &ui::TargetAsset<AudioSource>)>,
        server: Res<AssetServer>,
    ) {
        events.iter().for_each(|event| match event {
            AssetEvent::Created { handle } => {
                info!("Asset created! {:?}", event);
                let id = create_asset_button(
                    &widget,
                    &mut commands,
                    ui::TargetAsset {
                        handle: handle.clone(),
                    },
                    get_asset_name(&server, handle.clone()),
                    None,
                );
                commands.entity(id).insert(AudioSourceBundle {
                    source: handle.clone(),
                    settings: PlaybackSettings {
                        mode: PlaybackMode::Loop,
                        paused: true,
                        ..default()
                    },
                });
            }
            AssetEvent::Removed { handle } => {
                info!("Asset removed! {:?}", event);
                destroy_asset_button(
                    &current,
                    &mut commands,
                    &ui::TargetAsset {
                        handle: handle.clone(),
                    },
                );
            }
            AssetEvent::Modified { handle } => {
                info!("Asset modified! {:?}", event);
                destroy_asset_button(
                    &current,
                    &mut commands,
                    &ui::TargetAsset {
                        handle: handle.clone(),
                    },
                );
                let id = create_asset_button(
                    &widget,
                    &mut commands,
                    ui::TargetAsset {
                        handle: handle.clone(),
                    },
                    get_asset_name(&server, handle.clone()),
                    None,
                );
                commands.entity(id).insert(AudioSourceBundle {
                    source: handle.clone(),
                    settings: PlaybackSettings {
                        mode: PlaybackMode::Loop,
                        paused: true,
                        ..default()
                    },
                });
            }
        });
    }

    pub fn play_audio(events: Query<&AudioSink, (With<Button>, Added<ui::Active>)>) {
        events.iter().for_each(|sink| {
            sink.play();
        });
    }

    pub fn pause_audio(mut events: RemovedComponents<ui::Active>, sinks: Query<&AudioSink>) {
        events
            .iter()
            .filter_map(|entity| sinks.get(entity).ok())
            .for_each(|sink| sink.stop());
    }
}

use assets::*;
mod assets {
    use super::*;

    #[derive(Debug, Component)]
    pub struct ReloadAssets;

    pub fn reload_assets(
        server: Res<AssetServer>,
        mut registry: ResMut<AssetRegistry>,
        mut writer: EventWriter<ui::Alert>,
    ) {
        match server.load_folder(".") {
            Ok(handles) => registry.0 = handles,
            Err(e) => writer.send(ui::Alert::Warn(format!(
                "Could not find `assets` folder!\n{:?}",
                e
            ))),
        }
    }

    pub fn get_asset_name<T: Asset>(server: &AssetServer, handle: Handle<T>) -> String {
        if let Some(asset_path) = server.get_handle_path(handle.clone()) {
            if let Some(stem) = asset_path.path().file_stem() {
                if let Some(val) = stem.to_str() {
                    String::from(val)
                } else {
                    String::from("???")
                }
            } else {
                String::from("???")
            }
        } else {
            String::from("???")
        }
    }

    pub fn create_asset_button<A: Asset, C: Component>(
        root: &Query<Entity, With<C>>,
        commands: &mut Commands,
        target: ui::TargetAsset<A>,
        name: String,
        font: Option<Handle<Font>>,
    ) -> Entity {
        commands
            .spawn((
                target,
                ButtonBundle {
                    style: Style {
                        border: UiRect::all(Val::Px(1.0)),
                        margin: UiRect::all(Val::Px(1.0)),
                        padding: UiRect::all(Val::Px(1.0)),
                        ..default()
                    },
                    border_color: Color::BLACK.into(),
                    ..default()
                },
                ui::Title {
                    text: name,
                    font: font.clone(),
                },
            ))
            .set_parent(root.single())
            .id()
    }

    pub fn create_entity_button<C: Component>(
        root: &Query<Entity, With<C>>,
        commands: &mut Commands,
        target: ui::TargetEntity,
        name: String,
    ) -> Entity {
        commands
            .spawn((
                target,
                ButtonBundle {
                    style: Style {
                        border: UiRect::all(Val::Px(1.0)),
                        margin: UiRect::all(Val::Px(1.0)),
                        padding: UiRect::all(Val::Px(1.0)),
                        ..default()
                    },
                    border_color: Color::BLACK.into(),
                    ..default()
                },
                ui::Title {
                    text: name,
                    ..default()
                },
            ))
            .set_parent(root.single())
            .id()
    }

    pub fn destroy_asset_button<A: Asset>(
        current: &Query<(Entity, &ui::TargetAsset<A>)>,
        commands: &mut Commands,
        target: &ui::TargetAsset<A>,
    ) {
        if let Some(entity) = current.iter().find_map(|(entity, this)| {
            if this.handle == target.handle {
                Some(entity)
            } else {
                None
            }
        }) {
            commands.entity(entity).despawn_recursive();
        }
    }

    pub fn destroy_entity_button(
        current: &Query<(Entity, &ui::TargetEntity)>,
        commands: &mut Commands,
        target: &ui::TargetEntity,
    ) {
        if let Some(entity) = current.iter().find_map(|(entity, this)| {
            if this.entity == target.entity {
                Some(entity)
            } else {
                None
            }
        }) {
            commands.entity(entity).despawn_recursive();
        }
    }
}

use gltf::*;
mod gltf {
    use super::*;

    #[derive(Debug, Component, Default)]
    pub struct GltfWidget;

    pub fn gltf_ui(
        mut events: EventReader<AssetEvent<Gltf>>,
        mut commands: Commands,
        widget: Query<Entity, With<GltfWidget>>,
        current: Query<(Entity, &ui::TargetAsset<Gltf>)>,
        server: Res<AssetServer>,
    ) {
        events.iter().for_each(|event| match event {
            AssetEvent::Created { handle } => {
                info!("Asset created! {:?}", event);
                create_asset_button(
                    &widget,
                    &mut commands,
                    ui::TargetAsset {
                        handle: handle.clone(),
                    },
                    get_asset_name(&server, handle.clone()),
                    None,
                );
            }
            AssetEvent::Removed { handle } => {
                info!("Asset removed! {:?}", event);
                destroy_asset_button(
                    &current,
                    &mut commands,
                    &ui::TargetAsset {
                        handle: handle.clone(),
                    },
                );
            }
            AssetEvent::Modified { handle } => {
                info!("Asset modified! {:?}", event);
                destroy_asset_button(
                    &current,
                    &mut commands,
                    &ui::TargetAsset {
                        handle: handle.clone(),
                    },
                );
                create_asset_button(
                    &widget,
                    &mut commands,
                    ui::TargetAsset {
                        handle: handle.clone(),
                    },
                    get_asset_name(&server, handle.clone()),
                    None,
                );
            }
        });
    }

    pub fn control_active_gltf(
        events: Query<Entity, (With<ui::TargetAsset<Gltf>>, Added<ui::Active>)>,
        root: Query<Entity, With<LevelRoot>>,
        mut commands: Commands,
    ) {
        events.iter().for_each(|_| {
            commands.entity(root.single()).despawn_descendants();
        });
    }
}

use scenes::*;
mod scenes {
    use super::*;

    #[derive(Debug, Component, Default)]
    pub struct SceneWidget;

    pub fn add_scenes_ui(
        gltf_selected: Query<&ui::TargetAsset<Gltf>, Added<ui::Active>>,
        mut commands: Commands,
        gltfs: Res<Assets<Gltf>>,
        widget: Query<Entity, With<SceneWidget>>,
    ) {
        gltf_selected.iter().for_each(|ui::TargetAsset { handle }| {
            if let Some(gltf) = gltfs.get(&handle.clone()) {
                gltf.named_scenes.iter().for_each(|(name, handle)| {
                    create_asset_button(
                        &widget,
                        &mut commands,
                        ui::TargetAsset {
                            handle: handle.clone(),
                        },
                        name.clone(),
                        None,
                    );
                })
            }
        });
    }

    pub fn remove_scenes_ui(
        mut gltf_unselected: RemovedComponents<ui::Active>,
        target_assets: Query<&ui::TargetAsset<Gltf>>,
        current: Query<(Entity, &ui::TargetAsset<Scene>)>,
        gltfs: Res<Assets<Gltf>>,
        mut commands: Commands,
    ) {
        gltf_unselected
            .iter()
            .filter_map(|entity| target_assets.get(entity).ok())
            .filter_map(|ui::TargetAsset { handle }| gltfs.get(handle))
            .for_each(|gltf| {
                gltf.scenes.iter().for_each(|handle| {
                    destroy_asset_button(
                        &current,
                        &mut commands,
                        &ui::TargetAsset {
                            handle: handle.clone(),
                        },
                    );
                });
            });
    }

    pub fn control_active_scenes(
        added: Query<Entity, (With<Button>, Added<ui::Active>)>,
        mut removed: RemovedComponents<ui::Active>,
        scene_refs: Query<&ui::TargetAsset<Scene>>,
        scenes: Query<(Entity, &Handle<Scene>)>,
        level_root: Query<Entity, With<LevelRoot>>,
        mut commands: Commands,
    ) {
        // A scene button was marked inactive
        removed.iter().for_each(|entity| {
            // Get the handle associated with that button
            if let Ok(ui::TargetAsset { handle }) = scene_refs.get(entity) {
                if let Some(entity) = scenes.iter().find_map(|(entity, this_handle)| {
                    if this_handle == handle {
                        Some(entity)
                    } else {
                        None
                    }
                }) {
                    commands.entity(entity).despawn_recursive();
                }
            }
        });
        added.iter().for_each(|entity| {
            if let Ok(ui::TargetAsset { handle }) = scene_refs.get(entity) {
                info!("Spawning Scene {:?}", handle);
                commands
                    .entity(level_root.single())
                    .with_children(|parent| {
                        parent.spawn(SceneBundle {
                            scene: handle.clone(),
                            ..default()
                        });
                    });
            }
        });
    }
}

use animations::*;
mod animations {
    use super::*;

    #[derive(Debug, Component, Default)]
    pub struct AnimationWidget;

    #[derive(Debug, Component)]
    pub struct AnimationPlayAll;

    pub fn init_animations_ui(
        events: Query<Entity, Added<AnimationWidget>>,
        mut commands: Commands,
    ) {
        events.iter().for_each(|entity| {
            commands.entity(entity).with_children(|parent| {
                parent.spawn((
                    AnimationPlayAll,
                    ButtonBundle {
                        style: Style {
                            border: UiRect::all(Val::Px(1.0)),
                            margin: UiRect::all(Val::Px(1.0)),
                            padding: UiRect::all(Val::Px(1.0)),
                            ..default()
                        },
                        border_color: Color::BLACK.into(),
                        ..default()
                    },
                    ui::Title {
                        text: "Play All".into(),
                        ..default()
                    },
                ));
            });
        })
    }

    /// When a new scene is loaded, add any newly compatible animations
    /// TODO: Add target entity(s) too
    pub fn add_animations_ui(
        player_spawned: Query<&Name, Added<AnimationPlayer>>,
        widget: Query<Entity, With<AnimationWidget>>,
        mut commands: Commands,
        gltfs: Res<Assets<Gltf>>,
        clips: Res<Assets<AnimationClip>>,
    ) {
        player_spawned.iter().for_each(|player_name| {
            gltfs
                .iter()
                .flat_map(|(_, gltf)| gltf.named_animations.iter())
                .filter_map(|(clip_name, handle)| {
                    if let Some(clip) = clips.get(&handle) {
                        Some((clip_name, handle, clip))
                    } else {
                        None
                    }
                })
                .filter(|(_, _, clip)| clip.compatible_with(player_name))
                .for_each(|(clip_name, handle, _)| {
                    create_asset_button(
                        &widget,
                        &mut commands,
                        ui::TargetAsset {
                            handle: handle.clone(),
                        },
                        clip_name.clone(),
                        None,
                    );
                });
        });
    }

    // When a scene is de-selected, remove any outdated animation options
    pub fn remove_animations_ui(
        mut removed_players: RemovedComponents<Handle<Scene>>,
        current: Query<(Entity, &ui::TargetAsset<AnimationClip>)>,
        clips: Res<Assets<AnimationClip>>,
        targets: Query<(&AnimationPlayer, &Name)>,
        mut commands: Commands,
    ) {
        // For each removed scene
        removed_players.iter().for_each(|_| {
            // Iterate over the current animation buttons
            current
                .iter()
                .filter(|(_, ui::TargetAsset { handle })| {
                    // Check if this clip is compatible with any remaining entities
                    // NOTE: We are checking this is *not* compatible with any entities
                    clips
                        .get(handle)
                        .map(|clip| !(targets.iter().any(|(_, name)| clip.compatible_with(name))))
                        .unwrap_or(true)
                })
                .for_each(|(_, ui::TargetAsset { handle })| {
                    // Destroy the buton if it is so
                    destroy_asset_button(
                        &current,
                        &mut commands,
                        &ui::TargetAsset {
                            handle: handle.clone(),
                        },
                    );
                });
        });
    }

    pub fn play_all_animations(
        start: Query<Entity, (With<Button>, Added<ui::Active>)>,
        mut stop: RemovedComponents<ui::Active>,
        play_all_btn: Query<Entity, With<AnimationPlayAll>>,
        clip_btns: Query<Entity, With<ui::TargetAsset<AnimationClip>>>,
        mut commands: Commands,
    ) {
        stop.iter()
            .filter(|&entity| play_all_btn.contains(entity))
            .for_each(|_| {
                clip_btns.iter().for_each(|entity| {
                    commands.entity(entity).remove::<ui::Active>();
                })
            });
        start
            .iter()
            .filter(|&entity| play_all_btn.contains(entity))
            .for_each(|_| {
                clip_btns.iter().for_each(|entity| {
                    commands.entity(entity).insert(ui::Active);
                })
            });
    }

    pub fn play_animation(
        start: Query<Entity, (With<Button>, Added<ui::Active>)>,
        mut stop: RemovedComponents<ui::Active>,
        clip_refs: Query<&ui::TargetAsset<AnimationClip>>,
        mut targets: Query<(&mut AnimationPlayer, &Name), With<Transform>>,
        clips: Res<Assets<AnimationClip>>,
    ) {
        stop.iter().for_each(|entity| {
            if let Ok(ui::TargetAsset { handle }) = clip_refs.get(entity) {
                let clip = clips.get(&handle).expect("Load animation clip");
                targets
                    .iter_mut()
                    .filter(|(_, name)| clip.compatible_with(name))
                    .for_each(|(mut player, _)| {
                        player.pause();
                    })
            }
        });
        start.iter().for_each(|entity| {
            if let Ok(ui::TargetAsset { handle }) = clip_refs.get(entity) {
                let clip = clips.get(&handle).expect("Load animation clip");
                targets
                    .iter_mut()
                    .filter(|(_, name)| clip.compatible_with(name))
                    .for_each(|(mut player, _)| {
                        if player.is_paused() {
                            player.resume();
                        } else {
                            player.play(handle.clone()).repeat();
                        }
                    })
            }
        });
    }
}

use fonts::*;
mod fonts {
    use super::*;

    #[derive(Debug, Component, Default)]
    pub struct FontWidget;

    #[derive(Debug, Resource, Default)]
    pub struct FontInfo {
        pub default: Option<Handle<Font>>,
    }

    pub fn fonts_ui(
        mut events: EventReader<AssetEvent<Font>>,
        mut commands: Commands,
        widget: Query<Entity, With<FontWidget>>,
        current: Query<(Entity, &ui::TargetAsset<Font>)>,
        server: Res<AssetServer>,
    ) {
        events.iter().for_each(|event| match event {
            AssetEvent::Created { handle } => {
                info!("Asset created! {:?}", event);
                create_asset_button(
                    &widget,
                    &mut commands,
                    ui::TargetAsset {
                        handle: handle.clone(),
                    },
                    get_asset_name(&server, handle.clone()),
                    Some(handle.clone()),
                );
            }
            AssetEvent::Removed { handle } => {
                info!("Asset removed! {:?}", event);
                destroy_asset_button(
                    &current,
                    &mut commands,
                    &ui::TargetAsset {
                        handle: handle.clone(),
                    },
                );
            }
            AssetEvent::Modified { handle } => {
                info!("Asset modified! {:?}", event);
                destroy_asset_button(
                    &current,
                    &mut commands,
                    &ui::TargetAsset {
                        handle: handle.clone(),
                    },
                );
                create_asset_button(
                    &widget,
                    &mut commands,
                    ui::TargetAsset {
                        handle: handle.clone(),
                    },
                    get_asset_name(&server, handle.clone()),
                    Some(handle.clone()),
                );
            }
        });
    }

    pub fn set_active_font(
        events: Query<&ui::TargetAsset<Font>, Added<ui::Active>>,
        mut font: ResMut<FontInfo>,
    ) {
        events
            .iter()
            .for_each(|ui::TargetAsset { handle }| font.default = Some(handle.clone()));
    }
}

use monologues::*;
mod monologues {
    use super::*;
    use bevy::{
        reflect::{TypePath, TypeUuid},
        ui::FocusPolicy,
    };
    use serde::Deserialize;

    #[derive(Debug, Component, Default)]
    pub struct MonologueWidget;

    #[derive(Debug, Deserialize, TypeUuid, TypePath, PartialEq)]
    #[uuid = "216a570b-d142-4026-baed-d7feb0250458"]
    pub struct Monologue {
        text: String,
    }

    #[derive(Debug, Component)]
    pub struct MonologueModal;

    #[derive(Debug, Component)]
    pub struct MonologueContainer;

    #[derive(Default)]
    pub struct MonologueLoader;

    impl AssetLoader for MonologueLoader {
        fn load<'a>(
            &'a self,
            bytes: &'a [u8],
            load_context: &'a mut LoadContext,
        ) -> BoxedFuture<'a, Result<(), bevy::asset::Error>> {
            Box::pin(async move {
                let asset = Monologue {
                    text: String::from_utf8(bytes.to_vec())?,
                };
                load_context.set_default_asset(LoadedAsset::new(asset));
                Ok(())
            })
        }

        fn extensions(&self) -> &[&str] {
            &["monologue.txt"]
        }
    }

    pub fn init_texts_ui(mut commands: Commands) {
        commands.spawn((
            NodeBundle {
                style: Style {
                    width: Val::Percent(100.0),
                    align_items: AlignItems::Center,
                    justify_content: JustifyContent::Center,
                    ..default()
                },
                focus_policy: FocusPolicy::Pass,
                ..default()
            },
            MonologueContainer,
        ));
    }

    pub fn texts_ui(
        mut events: EventReader<AssetEvent<Monologue>>,
        mut commands: Commands,
        widget: Query<Entity, With<MonologueWidget>>,
        current: Query<(Entity, &ui::TargetAsset<Monologue>)>,
        server: Res<AssetServer>,
    ) {
        events.iter().for_each(|event| match event {
            AssetEvent::Created { handle } => {
                info!("Monologue created! {:?}", event);
                create_asset_button(
                    &widget,
                    &mut commands,
                    ui::TargetAsset {
                        handle: handle.clone(),
                    },
                    get_asset_name(&server, handle.clone()),
                    None,
                );
            }
            AssetEvent::Removed { handle } => {
                info!("Monologue removed! {:?}", event);
                destroy_asset_button(
                    &current,
                    &mut commands,
                    &ui::TargetAsset {
                        handle: handle.clone(),
                    },
                );
            }
            AssetEvent::Modified { handle } => {
                info!("Monologue modified! {:?}", event);
                destroy_asset_button(
                    &current,
                    &mut commands,
                    &ui::TargetAsset {
                        handle: handle.clone(),
                    },
                );
                create_asset_button(
                    &widget,
                    &mut commands,
                    ui::TargetAsset {
                        handle: handle.clone(),
                    },
                    get_asset_name(&server, handle.clone()),
                    None,
                );
            }
        });
    }

    pub fn show_preview_text(
        added: Query<Entity, (With<Button>, Added<ui::Active>)>,
        monologue_handles: Query<&ui::TargetAsset<Monologue>>,
        monologues: Res<Assets<Monologue>>,
        container: Query<Entity, With<MonologueContainer>>,
        mut commands: Commands,
        font: Res<FontInfo>,
    ) {
        added
            .iter()
            .filter_map(|entity| monologue_handles.get(entity).ok())
            .for_each(|ui::TargetAsset { handle }| {
                let monologue = monologues.get(handle).expect("Preview loaded monologue");
                commands
                    .entity(container.single())
                    .despawn_descendants()
                    .with_children(|parent| {
                        parent
                            .spawn(NodeBundle {
                                style: Style {
                                    max_width: Val::Percent(50.0),
                                    padding: UiRect::all(Val::Px(1.0)),
                                    margin: UiRect::all(Val::Px(1.0)),
                                    border: UiRect::all(Val::Px(1.0)),
                                    flex_direction: FlexDirection::Column,
                                    ..default()
                                },
                                background_color: Color::WHITE.into(),
                                border_color: Color::BLACK.into(),
                                ..default()
                            })
                            .with_children(|parent| {
                                parent.spawn((
                                    ui::TitleBarBase::new(Color::VIOLET).bundle(),
                                    ui::Title {
                                        text: "Monologue".into(),
                                        ..default()
                                    },
                                    ui::Close {
                                        target: parent.parent_entity(),
                                    },
                                    ui::Sorting(0),
                                ));
                                let style = match &font.default {
                                    Some(handle) => TextStyle {
                                        color: Color::BLACK.into(),
                                        font_size: 16.0,
                                        font: handle.clone(),
                                        ..default()
                                    },
                                    None => TextStyle {
                                        color: Color::BLACK.into(),
                                        font_size: 16.0,
                                        ..default()
                                    },
                                };
                                parent.spawn((
                                    TextBundle::from_section(monologue.text.clone(), style),
                                    handle.clone(),
                                ));
                            });
                    });
            });
    }

    // TODO: Sync Handle<Monologue> and TextStyle components to automagically generate and sync text
    pub fn sync_monologue_font(
        mut texts: Query<&mut Text, With<Handle<Monologue>>>,
        font: Res<FontInfo>,
    ) {
        if font.is_changed() || font.is_added() {
            texts.iter_mut().for_each(|mut text| {
                text.sections
                    .iter_mut()
                    .for_each(|section| match &font.default {
                        Some(handle) => section.style.font = handle.clone(),
                        None => section.style.font = Handle::default(),
                    });
            });
        }
    }
}

use cameras::*;
mod cameras {
    use super::*;

    #[derive(Debug, Component, Default)]
    pub struct CameraWidget;

    pub fn cameras_ui(
        mut added: Query<(Entity, &mut Camera, &Name), Added<Camera>>,
        mut removed: RemovedComponents<Camera>,
        editor_camera: Query<Entity, With<EditorCamera>>,
        widget: Query<Entity, With<CameraWidget>>,
        current: Query<(Entity, &ui::TargetEntity)>,
        mut commands: Commands,
    ) {
        removed.iter().for_each(|entity| {
            info!("Destroy button for {:?}", entity);
            destroy_entity_button(&current, &mut commands, &ui::TargetEntity { entity });
        });
        added.iter_mut().for_each(|(entity, mut camera, name)| {
            info!("Camera added {:?} {:?}", entity, name);
            create_entity_button(
                &widget,
                &mut commands,
                ui::TargetEntity { entity },
                name.as_str().into(),
            );
            camera.is_active = entity == editor_camera.single();
        });
    }

    /// Set the camera active component based on button clicks
    pub fn manage_active_camera(
        events: Query<&ui::TargetEntity, Added<ui::Active>>,
        mut cameras: Query<(Entity, &mut Camera)>,
    ) {
        events.iter().for_each(|ui::TargetEntity { entity }| {
            cameras.iter_mut().for_each(|(this_entity, mut camera)| {
                if this_entity == *entity {
                    info!("Marking {:?} as active camera", entity);
                    camera.is_active = true;
                } else {
                    info!("Marking {:?} as inactive camera", entity);
                    camera.is_active = false;
                }
            });
        });
    }

    // In the event that an active camera is despawned, fall back to the editor camera
    pub fn fallback_camera(
        modified: Query<Entity, (Changed<Camera>, Without<EditorCamera>)>,
        mut removed: RemovedComponents<Camera>,
        other_cameras: Query<&Camera, Without<EditorCamera>>,
        mut editor_camera: Query<&mut Camera, With<EditorCamera>>,
    ) {
        // Any time a camera is modified
        modified.iter().chain(removed.iter()).for_each(|_| {
            // If no other cameras are active
            if !other_cameras.iter().any(|camera| camera.is_active) {
                // Make the editor camera active
                editor_camera.single_mut().is_active = true;
            }
        })
    }
}

use lighting::*;
mod lighting {
    use super::*;

    pub fn spot_light_force_shadows(mut spot_lights: Query<&mut SpotLight, Added<SpotLight>>) {
        spot_lights.iter_mut().for_each(|mut light| {
            light.shadows_enabled = true;
        })
    }

    pub fn directional_light_force_shadows(
        mut directional_lights: Query<&mut DirectionalLight, Added<DirectionalLight>>,
    ) {
        directional_lights.iter_mut().for_each(|mut light| {
            light.shadows_enabled = true;
        })
    }

    pub fn point_light_force_shadows(mut point_lights: Query<&mut PointLight, Added<PointLight>>) {
        point_lights.iter_mut().for_each(|mut light| {
            light.shadows_enabled = true;
        })
    }
}

use reset::*;
mod reset {
    use super::*;

    #[derive(Debug, Component)]
    pub struct ClearLevel;

    pub fn clear_level(
        events: Query<Entity, (With<ClearLevel>, Added<ui::Active>)>,
        actives: Query<
            Entity,
            (
                With<ui::Active>,
                Or<(
                    With<ui::TargetEntity>,
                    With<ui::TargetAsset<Gltf>>,
                    With<ui::TargetAsset<Scene>>,
                    With<ui::TargetAsset<AnimationClip>>,
                    With<ui::TargetAsset<AudioSource>>,
                    With<ui::TargetAsset<Monologue>>,
                    With<ui::TargetAsset<Font>>,
                )>,
            ),
        >,
        root: Query<Entity, With<LevelRoot>>,
        mut commands: Commands,
    ) {
        events.iter().for_each(|_| {
            actives.iter().for_each(|entity| {
                commands.entity(entity).remove::<ui::Active>();
            });
            commands.entity(root.single()).despawn_descendants();
        })
    }

    #[derive(Debug, Component)]
    pub struct ClearAssets;

    pub fn clear_assets(
        asset_holders: Query<
            Entity,
            Or<(
                With<ui::TargetAsset<Gltf>>,
                With<Handle<Gltf>>,
                With<ui::TargetAsset<Scene>>,
                With<Handle<Scene>>,
                With<ui::TargetAsset<AnimationClip>>,
                With<Handle<AnimationClip>>,
                With<ui::TargetAsset<AudioSource>>,
                With<Handle<AudioSource>>,
                With<ui::TargetAsset<Monologue>>,
                With<Handle<Monologue>>,
                With<ui::TargetAsset<Font>>,
                With<Handle<Font>>,
            )>,
        >,
        mut registry: ResMut<AssetRegistry>,
        mut commands: Commands,
    ) {
        info!("Clearing assets");

        // Clear buttons holding asset references
        asset_holders
            .iter()
            .for_each(|entity| commands.entity(entity).despawn_recursive());

        // Empty asset registry
        registry.0.clear();
    }
}

pub use level::*;
mod level {
    use std::fs::File;
    use std::io::Write;

    use bevy::tasks::IoTaskPool;

    use super::*;

    type Level = DynamicScene;

    #[derive(Debug, Component, Default)]
    pub struct LevelWidget;

    #[derive(Debug, Component)]
    pub struct ExportLevel;

    pub fn level_ui(
        mut events: EventReader<AssetEvent<Level>>,
        mut commands: Commands,
        widget: Query<Entity, With<LevelWidget>>,
        current: Query<(Entity, &ui::TargetAsset<Level>)>,
        server: Res<AssetServer>,
    ) {
        events.iter().for_each(|event| match event {
            AssetEvent::Created { handle } => {
                info!("Asset created! {:?}", event);
                create_asset_button(
                    &widget,
                    &mut commands,
                    ui::TargetAsset {
                        handle: handle.clone(),
                    },
                    get_asset_name(&server, handle.clone()),
                    None,
                );
            }
            AssetEvent::Removed { handle } => {
                info!("Asset removed! {:?}", event);
                destroy_asset_button(
                    &current,
                    &mut commands,
                    &ui::TargetAsset {
                        handle: handle.clone(),
                    },
                );
            }
            AssetEvent::Modified { handle } => {
                info!("Asset modified! {:?}", event);
                destroy_asset_button(
                    &current,
                    &mut commands,
                    &ui::TargetAsset {
                        handle: handle.clone(),
                    },
                );
                create_asset_button(
                    &widget,
                    &mut commands,
                    ui::TargetAsset {
                        handle: handle.clone(),
                    },
                    get_asset_name(&server, handle.clone()),
                    None,
                );
            }
        });
    }

    pub fn load_level(
        events: Query<
            &ui::TargetAsset<DynamicScene>,
            (Added<ui::Active>, With<ui::TargetAsset<DynamicScene>>),
        >,
        root: Query<Entity, With<LevelRoot>>,
        mut commands: Commands,
    ) {
        events.iter().for_each(|ui::TargetAsset { handle }| {
            commands.entity(root.single()).despawn_recursive();
            commands.spawn(DynamicSceneBundle {
                scene: handle.clone(),
                ..default()
            });
        });
    }

    pub fn export_level(
        root: Query<Entity, With<LevelRoot>>,
        children: Query<&Children>,
        world: &World,
    ) {
        let app_type_registry = world.resource::<AppTypeRegistry>().clone();
        let mut builder = DynamicSceneBuilder::from_world(world.clone());

        builder.deny_all_resources();

        // builder.allow_all();
        builder.deny::<ComputedVisibility>();

        // Level administrivia
        builder.allow::<LevelRoot>();

        // Scene components
        builder.allow::<Handle<Scene>>();
        builder.allow::<Visibility>();
        builder.allow::<Transform>();
        builder.allow::<GlobalTransform>();

        // Audio components
        builder.allow::<Handle<AudioSource>>();
        builder.allow::<PlaybackSettings>();

        root.iter().for_each(|r| {
            // Extract the level root
            builder.extract_entity(r);

            match children.get(r) {
                Ok(cs) => {
                    builder.extract_entities(cs.iter().map(|&entity| entity));
                }
                Err(e) => warn!("Empty level! {:?}", e),
            }
        });

        let scene = builder.build();

        let serialized = scene
            .serialize_ron(&app_type_registry)
            .expect("Serialize scene");

        IoTaskPool::get()
            .spawn(async move {
                // Write the scene RON data to file
                File::create(format!("assets/output.scn.ron"))
                    .and_then(|mut file| file.write(serialized.as_bytes()))
                    .expect("Error while writing scene to file");
            })
            .detach();
    }

    pub fn rehydrate_level(
        events: Query<Entity, (Added<Visibility>, Without<ComputedVisibility>)>,
        mut commands: Commands,
    ) {
        events.iter().for_each(|entity| {
            commands
                .entity(entity)
                .insert(ComputedVisibility::default());
        });
    }
}
