use glam::Vec3;

use crate::components::camera::{CameraComponent, CameraTransition};
use crate::components::intent::{CameraTransitionIntent, FollowPlayerIntent, StopFollowingIntent};
use crate::components::FollowComponent;
use crate::entity::EntityHandle;
use crate::physics::PhysicsManager;
use crate::utility::input::InputState;
use crate::utility::transform::Transform;
use crate::world::Storage;

const CAMERA_GROUND_OFFSET: f32 = 2.0;

pub fn camera_view_matrix(
    cameras: &Storage<CameraComponent>,
    transforms: &Storage<Transform>,
    follows: &Storage<FollowComponent>,
) -> Option<glam::Mat4>
{
    let (camera_entity, camera_component) = cameras
        .components
        .iter()
        .find(|(_, cam)| cam.is_active)
        .map(|(e, c)| (*e, c))?;
    let camera_transform = transforms.get(camera_entity)?;

    if let Some(follow) = follows.get(camera_entity)
    {
        if let Some(target_transform) = transforms.get(follow.target)
        {
            return Some(glam::Mat4::look_at_rh(
                camera_transform.position,
                target_transform.position,
                glam::Vec3::Y,
            ));
        }
    }

    let forward = camera_component.get_forward();
    let target = camera_transform.position + forward;
    Some(glam::Mat4::look_at_rh(
        camera_transform.position,
        target,
        glam::Vec3::Y,
    ))
}

pub fn camera_input_system(
    cameras: &mut Storage<CameraComponent>,
    follows: &Storage<FollowComponent>,
    input_state: &InputState,
)
{
    let camera_entities: Vec<_> = cameras.all();

    for camera_entity in camera_entities
    {
        if let Some(camera) = cameras.get_mut(camera_entity)
        {
            if !camera.is_active
            {
                continue;
            }

            if input_state.mouse_delta.0.abs() > 0.0 || input_state.mouse_delta.1.abs() > 0.0
            {
                let is_following = follows.get(camera_entity).is_some();

                camera.yaw += input_state.mouse_delta.0 * 0.0008;

                if is_following
                {
                    camera.pitch += input_state.mouse_delta.1 * 0.0008;
                }
                else
                {
                    camera.pitch -= input_state.mouse_delta.1 * 0.0008;
                }

                camera.pitch = camera
                    .pitch
                    .clamp(-89.0_f32.to_radians(), 89.0_f32.to_radians());
            }
        }
    }
}

pub fn camera_intent_system(
    follow_player_intents: &mut Storage<FollowPlayerIntent>,
    stop_following_intents: &mut Storage<StopFollowingIntent>,
    camera_transition_intents: &mut Storage<CameraTransitionIntent>,
    follows: &mut Storage<FollowComponent>,
    transforms: &mut Storage<Transform>,
    cameras: &mut Storage<CameraComponent>,
    player_tags: &Storage<()>,
    camera_transitions: &mut Storage<CameraTransition>,
)
{
    let follow_entities: Vec<EntityHandle> = follow_player_intents.all();
    for entity in follow_entities
    {
        start_camera_following(follows, transforms, cameras, player_tags, entity);
        follow_player_intents.remove(entity);
    }

    let stop_entities: Vec<EntityHandle> = stop_following_intents.all();
    for entity in stop_entities
    {
        stop_camera_following(follows, transforms, cameras, entity);
        stop_following_intents.remove(entity);
    }

    let transition_entities: Vec<EntityHandle> = camera_transition_intents.all();
    for entity in transition_entities
    {
        let duration = camera_transition_intents
            .get(entity)
            .map(|i| i.duration)
            .unwrap_or(0.5);
        start_camera_transition(camera_transitions, transforms, cameras, entity, duration);
        camera_transition_intents.remove(entity);
    }
}

pub fn camera_follow_system(
    follows: &mut Storage<FollowComponent>,
    cameras: &Storage<CameraComponent>,
    transforms: &mut Storage<Transform>,
)
{
    let camera_entities: Vec<_> = follows.all();

    for camera_entity in camera_entities
    {
        if let Some(camera) = cameras.get(camera_entity)
        {
            if let Some(follow) = follows.get(camera_entity)
            {
                let target_entity = follow.target;
                let offset = follow.offset.position;

                if let Some(target_transform) = transforms.get(target_entity)
                {
                    let target_position = target_transform.position;
                    let distance = offset.length();

                    let orbit_yaw = camera.yaw + std::f32::consts::PI;

                    let offset_x = distance * orbit_yaw.cos() * camera.pitch.cos();
                    let offset_y = distance * camera.pitch.sin();
                    let offset_z = distance * orbit_yaw.sin() * camera.pitch.cos();

                    let new_offset = Vec3::new(offset_x, offset_y, offset_z);

                    transforms.with_mut(camera_entity, |camera_transform| {
                        camera_transform.position = target_position + new_offset;
                    });

                    follows.components.get_mut(&camera_entity).map(|f| {
                        f.offset.position = new_offset;
                    });
                }
            }
        }
    }
}

pub fn camera_noclip_system(
    cameras: &Storage<CameraComponent>,
    follows: &Storage<FollowComponent>,
    transforms: &mut Storage<Transform>,
    input_state: &InputState,
    delta: f32,
)
{
    if !input_state.mouse_captured
    {
        return;
    }

    let camera_entities: Vec<_> = cameras.all();

    for camera_entity in camera_entities
    {
        if follows.get(camera_entity).is_some()
        {
            continue;
        }

        if let Some(camera) = cameras.get(camera_entity)
        {
            if !camera.is_active
            {
                continue;
            }

            let forward = camera.get_forward();
            let right = camera.get_right();

            let mut input_vec = Vec3::ZERO;

            if input_state.w
            {
                input_vec.z += 1.0;
            }
            if input_state.s
            {
                input_vec.z -= 1.0;
            }
            if input_state.d
            {
                input_vec.x += 1.0;
            }
            if input_state.a
            {
                input_vec.x -= 1.0;
            }
            if input_state.space
            {
                input_vec.y += 10.0;
            }

            if input_vec.length_squared() > 0.0
            {
                input_vec = input_vec.normalize();
            }

            let mut speed = 10.0 * delta;
            if input_state.shift
            {
                speed *= 10.0;
            }

            if let Some(camera_transform) = transforms.get_mut(camera_entity)
            {
                camera_transform.position += forward * input_vec.z * speed;
                camera_transform.position += right * input_vec.x * speed;
                camera_transform.position += Vec3::Y * input_vec.y * speed;
            }
        }
    }
}

fn start_camera_following(
    follows: &mut Storage<FollowComponent>,
    transforms: &mut Storage<Transform>,
    cameras: &mut Storage<CameraComponent>,
    player_tags: &Storage<()>,
    camera_entity: EntityHandle,
)
{
    if let Some(camera_transform) = transforms.get(camera_entity)
    {
        let player_entities = player_tags.all();
        if let Some(&player_entity) = player_entities.first()
        {
            if let Some(target_transform) = transforms.get(player_entity)
            {
                let offset = camera_transform.position - target_transform.position;
                let distance = offset.length();

                if distance > 0.0
                {
                    if let Some(camera) = cameras.get_mut(camera_entity)
                    {
                        camera.pitch = (offset.y / distance).asin();
                        camera.yaw = offset.z.atan2(offset.x) + std::f32::consts::PI;
                    }
                }

                follows.insert(
                    camera_entity,
                    FollowComponent {
                        target: player_entity,
                        offset: Transform::from_position(offset),
                        inherit_rotation: false,
                        inherit_scale: false,
                    },
                );
            }
        }
    }
}

fn stop_camera_following(
    follows: &mut Storage<FollowComponent>,
    transforms: &Storage<Transform>,
    cameras: &mut Storage<CameraComponent>,
    camera_entity: EntityHandle,
)
{
    if let Some(follow) = follows.get(camera_entity)
    {
        let target_entity = follow.target;

        if let Some(camera_transform) = transforms.get(camera_entity)
        {
            if let Some(target_transform) = transforms.get(target_entity)
            {
                let look_direction =
                    (target_transform.position - camera_transform.position).normalize();

                if let Some(camera) = cameras.get_mut(camera_entity)
                {
                    camera.yaw = look_direction.z.atan2(look_direction.x);
                    camera.pitch = look_direction.y.asin();
                }
            }
        }

        follows.remove(camera_entity);
    }
}

pub fn camera_ground_clamp_system(
    cameras: &Storage<CameraComponent>,
    follows: &Storage<FollowComponent>,
    transforms: &mut Storage<Transform>,
)
{
    let camera_entity = cameras
        .components
        .iter()
        .find(|(_, cam)| cam.is_active)
        .map(|(e, _)| *e);

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

    if follows.get(camera_entity).is_none()
    {
        return;
    }

    transforms.with_mut(camera_entity, |t| {
        let ground_y =
            PhysicsManager::get_terrain_height_at(t.position.x, t.position.z).unwrap_or(0.0);
        let min_y = ground_y + CAMERA_GROUND_OFFSET;
        if t.position.y < min_y
        {
            t.position.y = min_y;
        }
    });
}

fn start_camera_transition(
    camera_transitions: &mut Storage<CameraTransition>,
    transforms: &mut Storage<Transform>,
    cameras: &mut Storage<CameraComponent>,
    camera_entity: EntityHandle,
    duration: f32,
)
{
    let Some(camera) = cameras.get(camera_entity)
    else
    {
        return;
    };

    let source_yaw = camera.yaw;
    let source_pitch = camera.pitch;

    let source_position = transforms
        .with(camera_entity, |t| t.position)
        .unwrap_or(Vec3::ZERO);

    camera_transitions.insert(
        camera_entity,
        CameraTransition {
            source_position,
            source_yaw,
            source_pitch,
            elapsed: 0.0,
            duration,
        },
    );
}

pub fn camera_transition_system(
    camera_transitions: &mut Storage<CameraTransition>,
    transforms: &mut Storage<Transform>,
    cameras: &mut Storage<CameraComponent>,
    delta: f32,
)
{
    let entities: Vec<EntityHandle> = camera_transitions.all();

    for entity in entities
    {
        let finished = {
            let Some(transition) = camera_transitions.get_mut(entity)
            else
            {
                continue;
            };

            transition.elapsed += delta;
            let t = (transition.elapsed / transition.duration).min(1.0);
            let t = smoothstep(t);

            let source_position = transition.source_position;
            let source_yaw = transition.source_yaw;
            let source_pitch = transition.source_pitch;
            let finished = t >= 1.0;

            transforms.with_mut(entity, |transform| {
                transform.position = source_position.lerp(transform.position, t);
            });

            if let Some(camera) = cameras.get_mut(entity)
            {
                camera.yaw = lerp_angle(source_yaw, camera.yaw, t);
                camera.pitch = source_pitch + (camera.pitch - source_pitch) * t;
            }

            if !finished
            {
                if let Some(transition) = camera_transitions.get_mut(entity)
                {
                    let pos = transforms
                        .with(entity, |tr| tr.position)
                        .unwrap_or(Vec3::ZERO);
                    let cam = cameras.get(entity);
                    transition.source_position = pos;
                    if let Some(cam) = cam
                    {
                        transition.source_yaw = cam.yaw;
                        transition.source_pitch = cam.pitch;
                    }
                }
            }

            finished
        };

        if finished
        {
            camera_transitions.remove(entity);
        }
    }
}

fn smoothstep(t: f32) -> f32
{
    t * t * (3.0 - 2.0 * t)
}

fn lerp_angle(from: f32, to: f32, t: f32) -> f32
{
    let mut diff = to - from;
    while diff > std::f32::consts::PI
    {
        diff -= std::f32::consts::TAU;
    }
    while diff < -std::f32::consts::PI
    {
        diff += std::f32::consts::TAU;
    }
    from + diff * t
}
