mod bundles;
mod components;
mod debug;
mod editor;
mod entity;
mod loaders;
mod paths;
mod physics;
mod picking;
mod postprocess;
mod render;
mod snow;
mod snow_light;
mod state;
mod systems;
mod texture;
mod utility;
mod world;

use crate::debug::{collider_debug, DebugMode};
use crate::editor::{editor_loop, EditorState, FrameStats};

use std::time::{Duration, Instant};

use glam::Vec3;
use render::Renderer;
use sdl3::event::Event;
use sdl3::keyboard::Keycode;
use sdl3::mouse::MouseButton;
use utility::input::InputState;
use world::World;

use crate::bundles::camera::CameraBundle;
use crate::bundles::player::PlayerBundle;
use crate::bundles::spotlight::spawn_spotlights;
use crate::bundles::terrain::{TerrainBundle, TerrainConfig};
use crate::bundles::test_char::TestCharBundle;
use crate::bundles::Bundle;
use crate::loaders::scene::Space;
use crate::physics::PhysicsManager;
use crate::snow::{SnowConfig, SnowLayer};

use crate::systems::camera::stop_camera_following;
use crate::systems::{
    camera_follow_system, camera_input_system, camera_view_matrix, dialog_bubble_render_system,
    dialog_camera_system, dialog_projectile_system, dialog_system, physics_sync_system,
    player_input_system, render_system, rotate_system, snow_system, spotlight_sync_system,
    start_camera_following, state_machine_physics_system, state_machine_system,
    tree_dissolve_update_system, tree_instance_buffer_update_system, tree_occlusion_system,
    trigger_system,
};
use crate::utility::time::Time;

fn main() -> Result<(), Box<dyn std::error::Error>>
{
    let sdl_context = sdl3::init()?;
    let video_subsystem = sdl_context.video()?;

    let window = video_subsystem
        .window("snow_trail", 1200, 900)
        .position_centered()
        .resizable()
        .vulkan()
        .build()?;
    let renderer = pollster::block_on(Renderer::new(&window, 2))?;
    render::init(renderer);

    let mut editor = render::with_device(|device| {
        render::with_queue(|queue| {
            render::with_surface_format(|format| EditorState::new(device, queue, format))
        })
    });
    editor.init_platform(&window);

    let space = Space::load_space(&crate::paths::meshes::terrain())?;
    let terrain_config = TerrainConfig::default();

    let player_spawn = space.player_spawn;
    let camera_spawn = space.camera_spawn_position();

    let tree_positions: Vec<Vec3> = space
        .mesh_data
        .iter()
        .flat_map(|(_, instances)| instances.iter().map(|inst| inst.position))
        .collect();

    let mut world = World::new();

    let _player_entity = PlayerBundle {
        position: player_spawn,
    }
    .spawn(&mut world)
    .unwrap();

    let _test_char_entity = TestCharBundle {
        position: space.test_char_spawn,
    }
    .spawn(&mut world)
    .unwrap();

    let _terrain_entity = TerrainBundle::spawn(&mut world, space.mesh_data, &terrain_config)?;
    spawn_spotlights(&mut world, space.spotlights);

    render::set_terrain_data();

    let terrain_half_size = terrain_config.size / 2.0;
    render::init_snow_light_accumulation(
        glam::Vec2::new(-terrain_half_size.x, -terrain_half_size.y),
        glam::Vec2::new(terrain_half_size.x, terrain_half_size.y),
    );

    let snow_config = SnowConfig::default();
    let mut snow_layer = SnowLayer::load(&snow_config)?;
    for pos in &tree_positions
    {
        snow_layer.deform_at_position(*pos, 5.0, 50.0);
    }
    println!("Snow layer loaded successfully");

    render::set_snow_depth(&snow_layer.depth_texture_view);

    let mut debug_mode = DebugMode::default();

    let camera_entity = CameraBundle {
        position: camera_spawn,
    }
    .spawn(&mut world)
    .unwrap();
    start_camera_following(&mut world, camera_entity);

    let _event_pump = sdl_context.event_pump()?;
    let mut input_state = InputState::new();

    sdl_context.mouse().set_relative_mouse_mode(&window, true);

    Time::init();
    let mut last_frame = Instant::now();
    let target_fps = 60;
    let frame_duration = Duration::from_millis(1000 / target_fps);

    const FIXED_TIMESTEP: f32 = 1.0 / 60.0;
    let mut physics_accumulator = 0.0;

    let mut stats = FrameStats {
        fps: 0.0,
        frame_ms: 0.0,
        physics_budget_ms: 0.0,
        draw_call_count: 0,
    };

    'running: loop
    {
        let frame_start = Instant::now();
        let time = Time::get_time_elapsed();
        let delta = (frame_start - last_frame).as_secs_f32();
        last_frame = frame_start;

        editor.begin_frame();

        while let Some(raw_event) = dear_imgui_sdl3::sdl3_poll_event_ll()
        {
            editor.process_event(&raw_event);
            let event = Event::from_ll(raw_event);

            match &event
            {
                Event::Quit { .. } =>
                {
                    input_state.quit_requested = true;
                    continue;
                }

                Event::KeyDown {
                    keycode: Some(Keycode::Tab),
                    repeat: false,
                    ..
                } =>
                {
                    editor.active = !editor.active;
                    if editor.active
                    {
                        stop_camera_following(&mut world, camera_entity);
                        sdl_context.mouse().set_relative_mouse_mode(&window, false);
                        editor.right_mouse_held = false;
                        input_state.mouse_captured = false;
                    }
                    else
                    {
                        start_camera_following(&mut world, camera_entity);
                        input_state.mouse_captured = true;
                        sdl_context.mouse().set_relative_mouse_mode(&window, true);
                    }
                    continue;
                }

                Event::MouseButtonDown {
                    mouse_btn: MouseButton::Right,
                    ..
                } if editor.active =>
                {
                    editor.right_mouse_held = true;
                    input_state.mouse_captured = true;
                    stop_camera_following(&mut world, camera_entity);
                    sdl_context.mouse().set_relative_mouse_mode(&window, true);
                    continue;
                }

                Event::MouseButtonUp {
                    mouse_btn: MouseButton::Right,
                    ..
                } if editor.active =>
                {
                    editor.right_mouse_held = false;
                    input_state.mouse_captured = false;
                    sdl_context.mouse().set_relative_mouse_mode(&window, false);
                    continue;
                }

                Event::MouseButtonDown {
                    mouse_btn: MouseButton::Left,
                    x,
                    y,
                    ..
                } if editor.active && !editor.wants_mouse() =>
                {
                    if let Some(view) = crate::systems::camera_view_matrix(&world)
                    {
                        if let Some((_, cam)) = world.active_camera()
                        {
                            let projection = cam.projection_matrix();
                            let (win_w, win_h) = window.size();
                            let ray = crate::picking::Ray::from_screen_position(
                                *x,
                                *y,
                                win_w,
                                win_h,
                                &view,
                                &projection,
                            );
                            editor.selected_entity = crate::picking::pick_entity(&ray, &world);
                            render::set_selected_entity(editor.selected_entity);
                        }
                    }
                    continue;
                }

                _ =>
                {}
            }

            if editor.active && (editor.wants_keyboard() || editor.wants_mouse())
            {
                continue;
            }

            let capture_changed = input_state.handle_event(&event);
            if capture_changed && !editor.active
            {
                sdl_context
                    .mouse()
                    .set_relative_mouse_mode(&window, input_state.mouse_captured);
            }
        }

        if input_state.quit_requested
        {
            break 'running;
        }

        if input_state.debug_cycle_just_pressed
        {
            debug_mode = debug_mode.cycle();
            println!("Debug mode: {:?}", debug_mode);
        }

        if input_state.f2_just_pressed
        {
            editor.show_player_state = !editor.show_player_state;
        }

        camera_input_system(&mut world, &input_state);

        if editor.active
        {
            editor_loop(&mut editor, &mut world, &input_state, &stats, delta);
        }
        else
        {
            let dialog_active = !world.bubble_tags.all().is_empty();
            if dialog_active
            {
                dialog_camera_system(&mut world, delta);
            }
            else
            {
                camera_follow_system(&mut world);
            }
            player_input_system(&mut world, &input_state);
            if editor.show_player_state
            {
                editor.build_hud(&world);
            }
        }

        let physics_start = Instant::now();

        physics_accumulator += delta;

        while physics_accumulator >= FIXED_TIMESTEP
        {
            state_machine_physics_system(&mut world, FIXED_TIMESTEP);

            PhysicsManager::physics_step();

            physics_sync_system(&mut world);
            trigger_system(&mut world);
            dialog_system(&mut world, FIXED_TIMESTEP);
            dialog_projectile_system(&mut world, &input_state);

            physics_accumulator -= FIXED_TIMESTEP;
        }

        stats.physics_budget_ms = physics_start.elapsed().as_secs_f32() * 1000.0;

        state_machine_system(&mut world, delta);

        rotate_system(&mut world, delta);

        tree_occlusion_system(&mut world);
        tree_dissolve_update_system(&mut world, delta);
        tree_instance_buffer_update_system(&mut world);

        let spotlights = spotlight_sync_system(&world);
        render::update_spotlights(spotlights);

        snow_system(&world, &mut snow_layer, editor.active);

        let mut draw_calls = render_system(&world);
        draw_calls.extend(snow_layer.get_draw_calls());

        if debug_mode == DebugMode::Colliders
        {
            draw_calls.extend(collider_debug::render_collider_debug());
        }

        if let Some((camera_entity, camera_component)) = world.active_camera()
        {
            if let Some(camera_transform) = world.transforms.get(camera_entity)
            {
                let player_pos = world.player_position();

                if let Some(view) = camera_view_matrix(&world)
                {
                    let projection = camera_component.projection_matrix();
                    let view_proj = projection * view;

                    let billboard_calls =
                        dialog_bubble_render_system(&world, camera_transform.position, view_proj);

                    stats.draw_call_count = draw_calls.len();
                    stats.fps = 1.0 / delta;
                    stats.frame_ms = delta * 1000.0;

                    let frame = render::render(
                        &view,
                        &projection,
                        camera_transform.position,
                        player_pos,
                        &draw_calls,
                        &billboard_calls,
                        time,
                        delta,
                        debug_mode,
                    );

                    if editor.active || editor.show_player_state
                    {
                        let screen_view = frame
                            .texture
                            .create_view(&wgpu::TextureViewDescriptor::default());
                        let mut encoder = render::with_device(|d| {
                            d.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                                label: Some("ImGui Encoder"),
                            })
                        });
                        editor.render(&mut encoder, &screen_view);
                        render::with_queue(|q| q.submit(std::iter::once(encoder.finish())));
                    }

                    frame.present();
                }
            }
        }

        input_state.clear_just_pressed();

        let frame_time = frame_start.elapsed();
        if frame_time < frame_duration
        {
            std::thread::sleep(frame_duration - frame_time);
        }
    }

    Ok(())
}
