//! Audio source/listener authoring cards and transient audition controls.

use bevy::prelude::*;
use bevy_egui::egui;
use egui_phosphor_icons::icons;
use settings::{AUDIO_GAIN_DB_MAX, AUDIO_GAIN_DB_MIN};
use shared::{
    AudioListenerDesc, AudioRolloff, AudioSourceDesc, COMPONENT_AUDIO_LISTENER_DESC,
    COMPONENT_AUDIO_SOURCE_DESC,
};

use crate::assets::{audio_clip_ref_for_asset, EditorAsset, EditorAssetKind, EditorAssets};
use crate::history::{set_audio_listener_with_history, set_audio_source_with_history};
use crate::play::audio_preview::{audition_audio_source, stop_audio_preview, AudioPreviewState};
use crate::ui::inspector::{
    apply_component_card_response, component_card, component_card_context, property_row,
    ComponentCardOptions,
};
use crate::ui::theme::{SELECTION, TEXT_DIM};

pub fn audio_source_inspector_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) {
    let Some(mut source) = world.get::<AudioSourceDesc>(entity).cloned() else {
        return;
    };
    let original = source.clone();
    let audio_assets: Vec<EditorAsset> = world
        .resource::<EditorAssets>()
        .assets
        .iter()
        .filter(|asset| matches!(asset.kind, EditorAssetKind::AudioClip))
        .cloned()
        .collect();
    let dragged_audio = world
        .resource::<EditorAssets>()
        .dragging_selection()
        .and_then(|selection| {
            world
                .resource::<EditorAssets>()
                .asset_for_selection(selection)
        })
        .filter(|asset| matches!(asset.kind, EditorAssetKind::AudioClip))
        .cloned();
    let buses = world
        .resource::<settings::ProjectSettings>()
        .audio
        .buses
        .clone();
    let previewing = world.resource::<AudioPreviewState>().source == Some(entity);
    let diagnostics = world
        .resource::<game::audio::AudioRuntimeDiagnostics>()
        .clone();
    let mut pending_clip: Option<EditorAsset> = None;
    let mut clear_clip = false;
    let mut audition = false;
    let mut stop = false;

    let card = component_card_context(
        world,
        entity,
        ComponentCardOptions::removable(
            COMPONENT_AUDIO_SOURCE_DESC,
            "Audio Source",
            icons::SPEAKER_HIGH,
        ),
    );
    let card_response = component_card(ui, &card, |ui| {
        let clip_response = property_row(ui, "Clip", |ui| {
            ui.horizontal_wrapped(|ui| {
                let selected_text = source
                    .clip
                    .as_ref()
                    .map(|clip| clip.label.as_str())
                    .filter(|label| !label.trim().is_empty())
                    .unwrap_or("None");
                egui::ComboBox::from_id_salt(("audio_clip", entity))
                    .selected_text(selected_text)
                    .width(150.0)
                    .show_ui(ui, |ui| {
                        for asset in &audio_assets {
                            let selected = source.clip.as_ref().is_some_and(|clip| {
                                clip.source_path.as_deref() == asset.path.as_deref()
                            });
                            if ui.selectable_label(selected, &asset.label).clicked() {
                                pending_clip = Some(asset.clone());
                            }
                        }
                    });
                if ui
                    .add_enabled(source.clip.is_some(), egui::Button::new(icons::X.as_str()))
                    .on_hover_text("Clear clip")
                    .clicked()
                {
                    clear_clip = true;
                }
            })
            .response
        });
        if dragged_audio.is_some() && clip_response.hovered() {
            ui.painter().rect_stroke(
                clip_response.rect.expand(3.0),
                3.0,
                egui::Stroke::new(1.5, SELECTION),
                egui::StrokeKind::Outside,
            );
            if ui.input(|input| input.pointer.any_released()) {
                pending_clip = dragged_audio.clone();
            }
        }

        property_row(ui, "Playback", |ui| {
            ui.horizontal_wrapped(|ui| {
                if ui
                    .add_enabled(
                        source.clip.is_some() && !previewing,
                        egui::Button::new(format!("{} Audition", icons::PLAY.as_str())),
                    )
                    .clicked()
                {
                    audition = true;
                }
                if ui
                    .add_enabled(
                        previewing,
                        egui::Button::new(format!("{} Stop", icons::STOP.as_str())),
                    )
                    .clicked()
                {
                    stop = true;
                }
            });
        });
        property_row(ui, "Gain", |ui| {
            ui.add(
                egui::Slider::new(&mut source.gain_db, AUDIO_GAIN_DB_MIN..=AUDIO_GAIN_DB_MAX)
                    .suffix(" dB"),
            );
        });
        property_row(ui, "Pitch", |ui| {
            ui.add(
                egui::DragValue::new(&mut source.pitch)
                    .range(0.25..=4.0)
                    .speed(0.01),
            );
        });
        property_row(ui, "Mode", |ui| {
            ui.horizontal_wrapped(|ui| {
                ui.checkbox(&mut source.autoplay, "Autoplay");
                ui.checkbox(&mut source.looping, "Loop");
            });
        });
        property_row(ui, "Spatial blend", |ui| {
            ui.add(egui::Slider::new(&mut source.spatial_blend, 0.0..=1.0));
        });
        property_row(ui, "Min distance", |ui| {
            ui.add(
                egui::DragValue::new(&mut source.attenuation.min_distance)
                    .range(0.01..=10_000.0)
                    .speed(0.1)
                    .suffix(" m"),
            );
        });
        property_row(ui, "Max distance", |ui| {
            ui.add(
                egui::DragValue::new(&mut source.attenuation.max_distance)
                    .range(0.02..=100_000.0)
                    .speed(0.25)
                    .suffix(" m"),
            );
        });
        property_row(ui, "Rolloff", |ui| {
            ui.horizontal_wrapped(|ui| {
                egui::ComboBox::from_id_salt(("audio_rolloff", entity))
                    .selected_text(format!("{:?}", source.attenuation.rolloff))
                    .show_ui(ui, |ui| {
                        for (rolloff, label) in [
                            (AudioRolloff::Linear, "Linear"),
                            (AudioRolloff::Inverse, "Inverse"),
                            (AudioRolloff::Exponential, "Exponential"),
                        ] {
                            ui.selectable_value(&mut source.attenuation.rolloff, rolloff, label);
                        }
                    });
                ui.add(
                    egui::DragValue::new(&mut source.attenuation.rolloff_factor)
                        .range(0.01..=8.0)
                        .speed(0.05),
                );
            });
        });
        property_row(ui, "Bus", |ui| {
            egui::ComboBox::from_id_salt(("audio_bus", entity))
                .selected_text(
                    buses
                        .iter()
                        .find(|bus| bus.id == source.bus)
                        .map(|bus| bus.label.as_str())
                        .unwrap_or(source.bus.as_str()),
                )
                .show_ui(ui, |ui| {
                    for bus in &buses {
                        ui.selectable_value(&mut source.bus, bus.id.clone(), &bus.label);
                    }
                });
        });
        ui.small(
            egui::RichText::new(&diagnostics.message)
                .color(TEXT_DIM)
                .italics(),
        );
    });

    if clear_clip {
        source.clip = None;
    }
    if let Some(asset) = pending_clip {
        match audio_clip_ref_for_asset(world, &asset) {
            Ok(clip) => {
                source.clip = Some(clip);
                world.resource_mut::<EditorAssets>().clear_drag();
            }
            Err(error) => {
                world.resource_mut::<crate::scene_io::SceneIo>().status =
                    format!("Audio clip assignment failed: {error}");
            }
        }
    }
    source.attenuation.max_distance = source
        .attenuation
        .max_distance
        .max(source.attenuation.min_distance + 0.01);
    if source != original {
        set_audio_source_with_history(world, entity, source);
    }
    if stop {
        stop_audio_preview(world);
    } else if audition {
        if let Err(error) = audition_audio_source(world, entity) {
            world.resource_mut::<crate::scene_io::SceneIo>().status =
                format!("Audio audition failed: {error}");
        }
    }
    apply_component_card_response(world, entity, card_response);
}

pub fn audio_listener_inspector_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) {
    let Some(mut listener) = world.get::<AudioListenerDesc>(entity).copied() else {
        return;
    };
    let original = listener;
    let card = component_card_context(
        world,
        entity,
        ComponentCardOptions::removable(
            COMPONENT_AUDIO_LISTENER_DESC,
            "Audio Listener",
            icons::HEADPHONES,
        ),
    );
    let card_response = component_card(ui, &card, |ui| {
        property_row(ui, "Enabled", |ui| {
            ui.checkbox(&mut listener.enabled, "Receive spatial audio");
        });
        property_row(ui, "Priority", |ui| {
            ui.add(egui::DragValue::new(&mut listener.priority).speed(1));
        });
        property_row(ui, "Ear gap", |ui| {
            ui.add(
                egui::DragValue::new(&mut listener.ear_gap)
                    .range(0.01..=2.0)
                    .speed(0.01)
                    .suffix(" m"),
            );
        });
        ui.small(
            egui::RichText::new("Highest priority enabled listener wins; actor ID breaks ties.")
                .color(TEXT_DIM),
        );
    });
    if listener != original {
        set_audio_listener_with_history(world, entity, listener);
    }
    apply_component_card_response(world, entity, card_response);
}
