use glam::Vec3;

use crate::components::camera::CameraComponent;
use crate::components::intent::CameraTransitionIntent;
use crate::entity::EntityHandle;
use crate::utility::transform::Transform;
use crate::world::Storage;

pub fn dialog_camera_transition_system(
    bubble_tags: &Storage<()>,
    camera_transition_intents: &mut Storage<CameraTransitionIntent>,
    was_dialog_active: &mut bool,
    camera_entity: EntityHandle,
)
{
    let dialog_active = !bubble_tags.all().is_empty();
    if dialog_active != *was_dialog_active
    {
        camera_transition_intents.insert(camera_entity, CameraTransitionIntent { duration: 0.8 });
        *was_dialog_active = dialog_active;
    }
}

const CAMERA_LAG: f32 = 4.0;
const VERTICAL_BIAS: f32 = 0.4;
const MIN_DISTANCE: f32 = 8.0;
const MAX_DISTANCE: f32 = 24.0;

pub fn dialog_camera_system(
    cameras: &mut Storage<CameraComponent>,
    transforms: &mut Storage<Transform>,
    bubble_tags: &Storage<()>,
    player_pos: Vec3,
    delta: f32,
)
{
    let camera_entity = cameras
        .components
        .iter()
        .find(|(_, cam)| cam.is_active)
        .map(|(e, _)| *e);

    let Some(camera_entity) = camera_entity
    else
    {
        return;
    };

    let bubble_positions: Vec<Vec3> = bubble_tags
        .all()
        .iter()
        .filter_map(|&bubble| transforms.with(bubble, |t| t.position))
        .collect();

    if bubble_positions.is_empty()
    {
        return;
    }

    let all_positions: Vec<Vec3> = std::iter::once(player_pos)
        .chain(bubble_positions.iter().copied())
        .collect();

    let centroid =
        all_positions.iter().copied().fold(Vec3::ZERO, |a, b| a + b) / all_positions.len() as f32;

    let max_spread = all_positions
        .iter()
        .map(|p| (*p - centroid).length())
        .fold(0.0f32, f32::max);

    let camera_distance = (max_spread * 1.8 + MIN_DISTANCE).min(MAX_DISTANCE);

    let to_player = (player_pos - centroid).normalize_or(Vec3::Z);
    let camera_back_dir = Vec3::new(to_player.x, 0.0, to_player.z).normalize_or(Vec3::Z);

    let target_camera_pos =
        centroid + camera_back_dir * camera_distance + Vec3::Y * camera_distance * VERTICAL_BIAS;

    let current_camera_pos = transforms
        .with(camera_entity, |t| t.position)
        .unwrap_or(target_camera_pos);

    let smoothed = current_camera_pos.lerp(target_camera_pos, (CAMERA_LAG * delta).min(1.0));

    transforms.with_mut(camera_entity, |t| {
        t.position = smoothed;
    });

    let look_target = centroid;

    if let Some(camera) = cameras.get_mut(camera_entity)
    {
        let look_dir = (look_target - smoothed).normalize_or(-Vec3::Z);
        camera.yaw = look_dir.z.atan2(look_dir.x);
        camera.pitch = look_dir.y.asin();
    }
}
