use crate::ecs::camera::components::Projection;
use crate::ecs::graphics::resources::{Atmosphere, Fog, PbrDebugMode};
use crate::ecs::transform::commands::mark_local_transform_dirty;
use crate::ecs::world::World;
use crate::ecs::world::commands::{
    spawn_cone_at, spawn_cube_at, spawn_cylinder_at, spawn_plane_at, spawn_sphere_at,
    spawn_torus_at,
};
use nalgebra_glm::Vec3;

use super::{Command, ShellState};

pub fn builtin_commands<C>() -> [Command<C>; 36] {
    [
        Command {
            name: "spawn_cube",
            description: "Spawn a cube at the specified position",
            usage: "spawn_cube <x> <y> <z>",
            execute: spawn_cube,
        },
        Command {
            name: "spawn_sphere",
            description: "Spawn a sphere at the specified position",
            usage: "spawn_sphere <x> <y> <z>",
            execute: spawn_sphere,
        },
        Command {
            name: "spawn_cylinder",
            description: "Spawn a cylinder at the specified position",
            usage: "spawn_cylinder <x> <y> <z>",
            execute: spawn_cylinder,
        },
        Command {
            name: "spawn_cone",
            description: "Spawn a cone at the specified position",
            usage: "spawn_cone <x> <y> <z>",
            execute: spawn_cone,
        },
        Command {
            name: "spawn_torus",
            description: "Spawn a torus at the specified position",
            usage: "spawn_torus <x> <y> <z>",
            execute: spawn_torus,
        },
        Command {
            name: "spawn_plane",
            description: "Spawn a plane at the specified position",
            usage: "spawn_plane <x> <y> <z>",
            execute: spawn_plane,
        },
        Command {
            name: "grid",
            description: "Toggle grid visibility (on/off)",
            usage: "grid <on|off>",
            execute: grid,
        },
        Command {
            name: "atmosphere",
            description: "Set the atmosphere type",
            usage: "atmosphere <none|sky|cloudy|space|nebula|sunset>",
            execute: atmosphere,
        },
        Command {
            name: "fov",
            description: "Get or set camera field of view in degrees",
            usage: "fov [degrees]",
            execute: fov,
        },
        Command {
            name: "timescale",
            description: "Get or set time scale (1.0 = normal, 0.5 = slow-mo, 2.0 = fast)",
            usage: "timescale [value]",
            execute: timescale,
        },
        Command {
            name: "fps",
            description: "Show current frames per second",
            usage: "fps",
            execute: fps,
        },
        Command {
            name: "quit",
            description: "Exit the application",
            usage: "quit",
            execute: quit,
        },
        Command {
            name: "pos",
            description: "Show current camera position",
            usage: "pos",
            execute: pos,
        },
        Command {
            name: "tp",
            description: "Teleport camera to specified position",
            usage: "tp <x> <y> <z>",
            execute: teleport,
        },
        Command {
            name: "unlit",
            description: "Toggle unlit rendering mode",
            usage: "unlit [on|off]",
            execute: unlit,
        },
        Command {
            name: "bloom",
            description: "Toggle bloom effect, set intensity, or set threshold",
            usage: "bloom [on|off|<intensity>|threshold <value>]",
            execute: bloom,
        },
        Command {
            name: "rotate",
            description: "Rotate an entity by degrees on each axis",
            usage: "rotate <entity> <x> <y> <z>",
            execute: rotate,
        },
        Command {
            name: "scale",
            description: "Scale an entity uniformly or on each axis",
            usage: "scale <entity> <factor> OR scale <entity> <x> <y> <z>",
            execute: scale,
        },
        Command {
            name: "move",
            description: "Move an entity by offset or to absolute position",
            usage: "move <entity> <x> <y> <z>",
            execute: move_entity,
        },
        Command {
            name: "delete",
            description: "Delete an entity from the world",
            usage: "delete <entity>",
            execute: delete,
        },
        Command {
            name: "inspect",
            description: "Show information about an entity",
            usage: "inspect <entity>",
            execute: inspect,
        },
        Command {
            name: "cull",
            description: "Toggle GPU frustum and occlusion culling",
            usage: "cull [on|off|gpu on|off|occlusion on|off]",
            execute: cull,
        },
        Command {
            name: "bounds",
            description: "Toggle bounding volume debug overlay",
            usage: "bounds [on|off]",
            execute: bounds,
        },
        Command {
            name: "normals",
            description: "Toggle vertex normal debug lines",
            usage: "normals [on|off]",
            execute: normals,
        },
        Command {
            name: "pbr",
            description: "Set PBR debug visualization mode",
            usage: "pbr [none|basecolor|normal|metallic|roughness|occlusion|emissive|f|g|d|diffuse|specular|miprainbow]",
            execute: pbr,
        },
        Command {
            name: "ssao",
            description: "Toggle screen-space ambient occlusion",
            usage: "ssao [on|off]",
            execute: ssao,
        },
        Command {
            name: "ssr",
            description: "Toggle screen-space reflections",
            usage: "ssr [on|off]",
            execute: ssr,
        },
        Command {
            name: "ssgi",
            description: "Toggle screen-space global illumination",
            usage: "ssgi [on|off]",
            execute: ssgi,
        },
        Command {
            name: "fxaa",
            description: "Toggle FXAA antialiasing",
            usage: "fxaa [on|off]",
            execute: fxaa,
        },
        Command {
            name: "fog",
            description: "Toggle distance fog or set start/end distance",
            usage: "fog [on|off|<start> <end>]",
            execute: fog,
        },
        Command {
            name: "exposure",
            description: "Get or set exposure multiplier",
            usage: "exposure [value]",
            execute: exposure,
        },
        Command {
            name: "gamma",
            description: "Get or set gamma correction",
            usage: "gamma [value]",
            execute: gamma,
        },
        Command {
            name: "saturation",
            description: "Get or set color saturation",
            usage: "saturation [value]",
            execute: saturation,
        },
        Command {
            name: "contrast",
            description: "Get or set color contrast",
            usage: "contrast [value]",
            execute: contrast,
        },
        Command {
            name: "fullscreen",
            description: "Toggle fullscreen mode",
            usage: "fullscreen [on|off]",
            execute: fullscreen,
        },
        Command {
            name: "cursor",
            description: "Toggle mouse cursor visibility",
            usage: "cursor [on|off]",
            execute: cursor,
        },
    ]
}

impl<C> ShellState<C> {
    pub fn register_builtin_commands(&mut self) {
        for command in builtin_commands() {
            self.registry.register_builtin(command);
        }
    }
}

fn spawn_cube<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.len() != 3 {
        return "Usage: spawn_cube <x> <y> <z>".to_string();
    }
    let x: f32 = match args[0].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid x coordinate".to_string(),
    };
    let y: f32 = match args[1].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid y coordinate".to_string(),
    };
    let z: f32 = match args[2].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid z coordinate".to_string(),
    };
    spawn_cube_at(world, Vec3::new(x, y, z));
    format!("Spawned cube at ({}, {}, {})", x, y, z)
}

fn spawn_sphere<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.len() != 3 {
        return "Usage: spawn_sphere <x> <y> <z>".to_string();
    }
    let x: f32 = match args[0].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid x coordinate".to_string(),
    };
    let y: f32 = match args[1].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid y coordinate".to_string(),
    };
    let z: f32 = match args[2].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid z coordinate".to_string(),
    };
    spawn_sphere_at(world, Vec3::new(x, y, z));
    format!("Spawned sphere at ({}, {}, {})", x, y, z)
}

fn spawn_cylinder<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.len() != 3 {
        return "Usage: spawn_cylinder <x> <y> <z>".to_string();
    }
    let x: f32 = match args[0].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid x coordinate".to_string(),
    };
    let y: f32 = match args[1].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid y coordinate".to_string(),
    };
    let z: f32 = match args[2].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid z coordinate".to_string(),
    };
    spawn_cylinder_at(world, Vec3::new(x, y, z));
    format!("Spawned cylinder at ({}, {}, {})", x, y, z)
}

fn spawn_cone<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.len() != 3 {
        return "Usage: spawn_cone <x> <y> <z>".to_string();
    }
    let x: f32 = match args[0].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid x coordinate".to_string(),
    };
    let y: f32 = match args[1].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid y coordinate".to_string(),
    };
    let z: f32 = match args[2].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid z coordinate".to_string(),
    };
    spawn_cone_at(world, Vec3::new(x, y, z));
    format!("Spawned cone at ({}, {}, {})", x, y, z)
}

fn spawn_torus<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.len() != 3 {
        return "Usage: spawn_torus <x> <y> <z>".to_string();
    }
    let x: f32 = match args[0].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid x coordinate".to_string(),
    };
    let y: f32 = match args[1].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid y coordinate".to_string(),
    };
    let z: f32 = match args[2].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid z coordinate".to_string(),
    };
    spawn_torus_at(world, Vec3::new(x, y, z));
    format!("Spawned torus at ({}, {}, {})", x, y, z)
}

fn spawn_plane<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.len() != 3 {
        return "Usage: spawn_plane <x> <y> <z>".to_string();
    }
    let x: f32 = match args[0].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid x coordinate".to_string(),
    };
    let y: f32 = match args[1].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid y coordinate".to_string(),
    };
    let z: f32 = match args[2].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid z coordinate".to_string(),
    };
    spawn_plane_at(world, Vec3::new(x, y, z));
    format!("Spawned plane at ({}, {}, {})", x, y, z)
}

fn grid<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.is_empty() {
        let state = if world.resources.debug_draw.show_grid {
            "on"
        } else {
            "off"
        };
        return format!("Grid is currently {}", state);
    }

    match args[0].to_lowercase().as_str() {
        "on" | "true" | "1" => {
            world.resources.debug_draw.show_grid = true;
            "Grid enabled".to_string()
        }
        "off" | "false" | "0" => {
            world.resources.debug_draw.show_grid = false;
            "Grid disabled".to_string()
        }
        _ => "Usage: grid <on|off>".to_string(),
    }
}

fn atmosphere<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.is_empty() {
        let current = match world.resources.render_settings.atmosphere {
            Atmosphere::None => "none",
            Atmosphere::Sky => "sky",
            Atmosphere::CloudySky => "cloudy",
            Atmosphere::Space => "space",
            Atmosphere::Nebula => "nebula",
            Atmosphere::Sunset => "sunset",
            _ => "other",
        };
        return format!("Current atmosphere: {}", current);
    }

    let atmosphere = match args[0].to_lowercase().as_str() {
        "none" => Atmosphere::None,
        "sky" => Atmosphere::Sky,
        "cloudy" | "cloudysky" => Atmosphere::CloudySky,
        "space" => Atmosphere::Space,
        "nebula" => Atmosphere::Nebula,
        "sunset" => Atmosphere::Sunset,
        _ => {
            return "Unknown atmosphere type. Usage: atmosphere <none|sky|cloudy|space|nebula|sunset>".to_string();
        }
    };

    world.resources.render_settings.atmosphere = atmosphere;
    format!("Atmosphere set to {}", args[0].to_lowercase())
}

fn fov<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let Some(camera_entity) = world.resources.active_camera else {
        return "No active camera".to_string();
    };

    let Some(camera) = world.core.get_camera_mut(camera_entity) else {
        return "Camera component not found".to_string();
    };

    let Projection::Perspective(perspective) = &mut camera.projection else {
        return "Camera is not using perspective projection".to_string();
    };

    if args.is_empty() {
        let fov_degrees = perspective.y_fov_rad.to_degrees();
        return format!("Current FOV: {:.1} degrees", fov_degrees);
    }

    let value: f32 = match args[0].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid FOV value".to_string(),
    };

    if !(1.0..=179.0).contains(&value) {
        return "FOV must be between 1 and 179 degrees".to_string();
    }

    perspective.y_fov_rad = value.to_radians();
    format!("FOV set to {:.1} degrees", value)
}

fn timescale<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.is_empty() {
        return format!(
            "Current timescale: {:.2}",
            world.resources.window.timing.time_speed
        );
    }

    let scale: f32 = match args[0].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid timescale value".to_string(),
    };

    if !(0.0..=10.0).contains(&scale) {
        return "Timescale must be between 0.0 and 10.0".to_string();
    }

    world.resources.window.timing.time_speed = scale;
    format!("Timescale set to {:.2}", scale)
}

fn fps<C>(_args: &[&str], world: &mut World, _context: &mut C) -> String {
    let timing = &world.resources.window.timing;
    format!(
        "FPS: {:.1} | Frame time: {:.2}ms | Uptime: {:.1}s",
        timing.frames_per_second,
        timing.delta_time * 1000.0,
        timing.uptime_milliseconds as f32 / 1000.0
    )
}

fn quit<C>(_args: &[&str], world: &mut World, _context: &mut C) -> String {
    world.resources.window.should_exit = true;
    "Exiting...".to_string()
}

fn pos<C>(_args: &[&str], world: &mut World, _context: &mut C) -> String {
    let Some(camera_entity) = world.resources.active_camera else {
        return "No active camera".to_string();
    };

    let Some(transform) = world.core.get_local_transform(camera_entity) else {
        return "Camera transform not found".to_string();
    };

    format!(
        "Position: ({:.2}, {:.2}, {:.2})",
        transform.translation.x, transform.translation.y, transform.translation.z
    )
}

fn teleport<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.len() != 3 {
        return "Usage: tp <x> <y> <z>".to_string();
    }

    let Some(camera_entity) = world.resources.active_camera else {
        return "No active camera".to_string();
    };

    let x: f32 = match args[0].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid x coordinate".to_string(),
    };
    let y: f32 = match args[1].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid y coordinate".to_string(),
    };
    let z: f32 = match args[2].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid z coordinate".to_string(),
    };

    let Some(transform) = world.core.get_local_transform_mut(camera_entity) else {
        return "Camera transform not found".to_string();
    };

    transform.translation = Vec3::new(x, y, z);
    mark_local_transform_dirty(world, camera_entity);

    format!("Teleported to ({:.2}, {:.2}, {:.2})", x, y, z)
}

fn unlit<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.is_empty() {
        let state = if world.resources.render_settings.unlit_mode {
            "on"
        } else {
            "off"
        };
        return format!("Unlit mode is currently {}", state);
    }

    match args[0].to_lowercase().as_str() {
        "on" | "true" | "1" => {
            world.resources.render_settings.unlit_mode = true;
            "Unlit mode enabled".to_string()
        }
        "off" | "false" | "0" => {
            world.resources.render_settings.unlit_mode = false;
            "Unlit mode disabled".to_string()
        }
        _ => "Usage: unlit [on|off]".to_string(),
    }
}

fn bloom<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.is_empty() {
        let state = if world.resources.render_settings.bloom_enabled {
            format!(
                "on (intensity: {:.2}, threshold: {:.2})",
                world.resources.render_settings.bloom_intensity,
                world.resources.render_settings.bloom_threshold
            )
        } else {
            "off".to_string()
        };
        return format!("Bloom is currently {}", state);
    }

    match args[0].to_lowercase().as_str() {
        "on" | "true" | "1" => {
            world.resources.render_settings.bloom_enabled = true;
            "Bloom enabled".to_string()
        }
        "off" | "false" | "0" => {
            world.resources.render_settings.bloom_enabled = false;
            "Bloom disabled".to_string()
        }
        "threshold" => {
            if args.len() < 2 {
                return format!(
                    "Bloom threshold is {:.2}",
                    world.resources.render_settings.bloom_threshold
                );
            }
            if let Ok(threshold) = args[1].parse::<f32>() {
                if (0.0..=10.0).contains(&threshold) {
                    world.resources.render_settings.bloom_threshold = threshold;
                    return format!("Bloom threshold set to {:.2}", threshold);
                }
                return "Bloom threshold must be between 0.0 and 10.0".to_string();
            }
            "Usage: bloom threshold <value>".to_string()
        }
        _ => {
            if let Ok(intensity) = args[0].parse::<f32>() {
                if (0.0..=5.0).contains(&intensity) {
                    world.resources.render_settings.bloom_enabled = true;
                    world.resources.render_settings.bloom_intensity = intensity;
                    return format!("Bloom intensity set to {:.2}", intensity);
                }
                return "Bloom intensity must be between 0.0 and 5.0".to_string();
            }
            "Usage: bloom [on|off|<intensity>|threshold <value>]".to_string()
        }
    }
}

fn rotate<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.len() != 4 {
        return "Usage: rotate <entity> <x> <y> <z>".to_string();
    }

    let Some(entity) = super::parse_entity(args[0]) else {
        return format!("Invalid entity format: {}", args[0]);
    };

    let x: f32 = match args[1].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid x rotation".to_string(),
    };
    let y: f32 = match args[2].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid y rotation".to_string(),
    };
    let z: f32 = match args[3].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid z rotation".to_string(),
    };

    let Some(transform) = world.core.get_local_transform_mut(entity) else {
        return "Entity not found or has no transform".to_string();
    };

    let rotation_x = nalgebra_glm::quat_angle_axis(x.to_radians(), &Vec3::new(1.0, 0.0, 0.0));
    let rotation_y = nalgebra_glm::quat_angle_axis(y.to_radians(), &Vec3::new(0.0, 1.0, 0.0));
    let rotation_z = nalgebra_glm::quat_angle_axis(z.to_radians(), &Vec3::new(0.0, 0.0, 1.0));
    let rotation = rotation_z * rotation_y * rotation_x;
    transform.rotation = rotation * transform.rotation;
    mark_local_transform_dirty(world, entity);

    format!("Rotated {} by ({}, {}, {}) degrees", args[0], x, y, z)
}

fn scale<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.len() < 2 {
        return "Usage: scale <entity> <factor> OR scale <entity> <x> <y> <z>".to_string();
    }

    let Some(entity) = super::parse_entity(args[0]) else {
        return format!("Invalid entity format: {}", args[0]);
    };

    let factor = if args.len() == 2 {
        let value: f32 = match args[1].parse() {
            Ok(value) => value,
            Err(_) => return "Invalid scale factor".to_string(),
        };
        Vec3::new(value, value, value)
    } else if args.len() == 4 {
        let x: f32 = match args[1].parse() {
            Ok(value) => value,
            Err(_) => return "Invalid x scale".to_string(),
        };
        let y: f32 = match args[2].parse() {
            Ok(value) => value,
            Err(_) => return "Invalid y scale".to_string(),
        };
        let z: f32 = match args[3].parse() {
            Ok(value) => value,
            Err(_) => return "Invalid z scale".to_string(),
        };
        Vec3::new(x, y, z)
    } else {
        return "Usage: scale <entity> <factor> OR scale <entity> <x> <y> <z>".to_string();
    };

    let new_scale = {
        let Some(transform) = world.core.get_local_transform_mut(entity) else {
            return "Entity not found or has no transform".to_string();
        };
        transform.scale = transform.scale.component_mul(&factor);
        transform.scale
    };
    mark_local_transform_dirty(world, entity);

    format!(
        "Scaled {} to ({:.2}, {:.2}, {:.2})",
        args[0], new_scale.x, new_scale.y, new_scale.z
    )
}

fn move_entity<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.len() != 4 {
        return "Usage: move <entity> <x> <y> <z>".to_string();
    }

    let Some(entity) = super::parse_entity(args[0]) else {
        return format!("Invalid entity format: {}", args[0]);
    };

    let x: f32 = match args[1].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid x position".to_string(),
    };
    let y: f32 = match args[2].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid y position".to_string(),
    };
    let z: f32 = match args[3].parse() {
        Ok(value) => value,
        Err(_) => return "Invalid z position".to_string(),
    };

    let Some(transform) = world.core.get_local_transform_mut(entity) else {
        return "Entity not found or has no transform".to_string();
    };

    transform.translation = Vec3::new(x, y, z);
    mark_local_transform_dirty(world, entity);

    format!("Moved {} to ({:.2}, {:.2}, {:.2})", args[0], x, y, z)
}

fn delete<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.is_empty() {
        return "Usage: delete <entity>".to_string();
    }

    let Some(entity) = super::parse_entity(args[0]) else {
        return format!("Invalid entity format: {}", args[0]);
    };

    if world.core.get_local_transform(entity).is_none() {
        return "Entity not found".to_string();
    }

    world.despawn_entities(&[entity]);
    format!("Deleted {}", args[0])
}

fn inspect<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.is_empty() {
        return "Usage: inspect <entity>".to_string();
    }

    let Some(entity) = super::parse_entity(args[0]) else {
        return format!("Invalid entity format: {}", args[0]);
    };

    let mut info = format!("Entity: {}\n", args[0]);

    if let Some(name) = world.core.get_name(entity) {
        info.push_str(&format!("Name: {}\n", name.0));
    }

    if let Some(transform) = world.core.get_local_transform(entity) {
        info.push_str(&format!(
            "Position: ({:.2}, {:.2}, {:.2})\n",
            transform.translation.x, transform.translation.y, transform.translation.z
        ));
        info.push_str(&format!(
            "Scale: ({:.2}, {:.2}, {:.2})\n",
            transform.scale.x, transform.scale.y, transform.scale.z
        ));
    } else {
        return "Entity not found".to_string();
    }

    if let Some(visibility) = world.core.get_visibility(entity) {
        info.push_str(&format!("Visible: {}\n", visibility.visible));
    }

    info
}

fn parse_bool(value: &str) -> Option<bool> {
    match value.to_lowercase().as_str() {
        "on" | "true" | "1" => Some(true),
        "off" | "false" | "0" => Some(false),
        _ => None,
    }
}

fn cull<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let graphics = &mut world.resources.renderer_state;
    if args.is_empty() {
        return format!(
            "GPU culling: {}",
            if graphics.gpu_culling_enabled {
                "on"
            } else {
                "off"
            }
        );
    }
    match args[0].to_lowercase().as_str() {
        "gpu" => {
            let Some(value) = args.get(1).and_then(|raw| parse_bool(raw)) else {
                return "Usage: cull gpu <on|off>".to_string();
            };
            graphics.gpu_culling_enabled = value;
            format!("GPU culling {}", if value { "enabled" } else { "disabled" })
        }
        other => {
            let Some(value) = parse_bool(other) else {
                return "Usage: cull [on|off|gpu on|off]".to_string();
            };
            graphics.gpu_culling_enabled = value;
            format!("Culling {}", if value { "enabled" } else { "disabled" })
        }
    }
}

fn bounds<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let graphics = &mut world.resources.debug_draw;
    if args.is_empty() {
        return format!(
            "Bounding volumes: {}",
            if graphics.show_bounding_volumes {
                "on"
            } else {
                "off"
            }
        );
    }
    let Some(value) = parse_bool(args[0]) else {
        return "Usage: bounds [on|off]".to_string();
    };
    graphics.show_bounding_volumes = value;
    format!(
        "Bounding volumes {}",
        if value { "enabled" } else { "disabled" }
    )
}

fn normals<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let graphics = &mut world.resources.debug_draw;
    if args.is_empty() {
        return format!(
            "Normals: {}",
            if graphics.show_normals { "on" } else { "off" }
        );
    }
    let Some(value) = parse_bool(args[0]) else {
        return "Usage: normals [on|off]".to_string();
    };
    graphics.show_normals = value;
    format!(
        "Normal debug {}",
        if value { "enabled" } else { "disabled" }
    )
}

fn pbr<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.is_empty() {
        return format!(
            "PBR debug mode: {}",
            world.resources.debug_draw.pbr_debug_mode.name()
        );
    }
    let mode = match args[0].to_lowercase().as_str() {
        "none" | "off" => PbrDebugMode::None,
        "basecolor" | "base" | "color" => PbrDebugMode::BaseColor,
        "normal" | "normals" => PbrDebugMode::Normal,
        "metallic" | "metal" => PbrDebugMode::Metallic,
        "roughness" | "rough" => PbrDebugMode::Roughness,
        "occlusion" | "ao" => PbrDebugMode::Occlusion,
        "emissive" | "emit" => PbrDebugMode::Emissive,
        "f" | "fresnel" => PbrDebugMode::F,
        "g" | "geometry" => PbrDebugMode::G,
        "d" | "distribution" => PbrDebugMode::D,
        "diffuse" => PbrDebugMode::Diffuse,
        "specular" | "spec" => PbrDebugMode::Specular,
        "miprainbow" | "mip" | "rainbow" => PbrDebugMode::MipRainbow,
        _ => {
            return "Usage: pbr [none|basecolor|normal|metallic|roughness|occlusion|emissive|f|g|d|diffuse|specular|miprainbow]".to_string();
        }
    };
    world.resources.debug_draw.pbr_debug_mode = mode;
    format!("PBR debug mode: {}", mode.name())
}

fn ssao<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let graphics = &mut world.resources.render_settings;
    if args.is_empty() {
        return format!("SSAO: {}", if graphics.ssao_enabled { "on" } else { "off" });
    }
    let Some(value) = parse_bool(args[0]) else {
        return "Usage: ssao [on|off]".to_string();
    };
    graphics.ssao_enabled = value;
    format!("SSAO {}", if value { "enabled" } else { "disabled" })
}

fn ssr<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let graphics = &mut world.resources.render_settings;
    if args.is_empty() {
        return format!("SSR: {}", if graphics.ssr_enabled { "on" } else { "off" });
    }
    let Some(value) = parse_bool(args[0]) else {
        return "Usage: ssr [on|off]".to_string();
    };
    graphics.ssr_enabled = value;
    format!("SSR {}", if value { "enabled" } else { "disabled" })
}

fn ssgi<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let graphics = &mut world.resources.render_settings;
    if args.is_empty() {
        return format!("SSGI: {}", if graphics.ssgi_enabled { "on" } else { "off" });
    }
    let Some(value) = parse_bool(args[0]) else {
        return "Usage: ssgi [on|off]".to_string();
    };
    graphics.ssgi_enabled = value;
    format!("SSGI {}", if value { "enabled" } else { "disabled" })
}

fn fxaa<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let graphics = &mut world.resources.render_settings;
    if args.is_empty() {
        return format!("FXAA: {}", if graphics.fxaa_enabled { "on" } else { "off" });
    }
    let Some(value) = parse_bool(args[0]) else {
        return "Usage: fxaa [on|off]".to_string();
    };
    graphics.fxaa_enabled = value;
    format!("FXAA {}", if value { "enabled" } else { "disabled" })
}

fn fog<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    if args.is_empty() {
        return match world.resources.render_settings.fog {
            Some(fog) => format!("Fog: on (start {:.1}, end {:.1})", fog.start, fog.end),
            None => "Fog: off".to_string(),
        };
    }
    if args.len() >= 2
        && let (Ok(start), Ok(end)) = (args[0].parse::<f32>(), args[1].parse::<f32>())
    {
        let mut fog = world.resources.render_settings.fog.unwrap_or_default();
        fog.start = start;
        fog.end = end.max(start + 0.01);
        world.resources.render_settings.fog = Some(fog);
        return format!("Fog start {:.1}, end {:.1}", fog.start, fog.end);
    }
    let Some(value) = parse_bool(args[0]) else {
        return "Usage: fog [on|off|<start> <end>]".to_string();
    };
    if value {
        if world.resources.render_settings.fog.is_none() {
            world.resources.render_settings.fog = Some(Fog::default());
        }
        "Fog enabled".to_string()
    } else {
        world.resources.render_settings.fog = None;
        "Fog disabled".to_string()
    }
}

fn exposure<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let grading = &mut world.resources.render_settings.color_grading;
    if args.is_empty() {
        return format!("Exposure: {:.3}", grading.exposure);
    }
    let Ok(value) = args[0].parse::<f32>() else {
        return "Usage: exposure [value]".to_string();
    };
    grading.exposure = value.max(0.0);
    format!("Exposure set to {:.3}", grading.exposure)
}

fn gamma<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let grading = &mut world.resources.render_settings.color_grading;
    if args.is_empty() {
        return format!("Gamma: {:.2}", grading.gamma);
    }
    let Ok(value) = args[0].parse::<f32>() else {
        return "Usage: gamma [value]".to_string();
    };
    grading.gamma = value.clamp(0.1, 10.0);
    format!("Gamma set to {:.2}", grading.gamma)
}

fn saturation<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let grading = &mut world.resources.render_settings.color_grading;
    if args.is_empty() {
        return format!("Saturation: {:.2}", grading.saturation);
    }
    let Ok(value) = args[0].parse::<f32>() else {
        return "Usage: saturation [value]".to_string();
    };
    grading.saturation = value.clamp(0.0, 4.0);
    format!("Saturation set to {:.2}", grading.saturation)
}

fn contrast<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let grading = &mut world.resources.render_settings.color_grading;
    if args.is_empty() {
        return format!("Contrast: {:.2}", grading.contrast);
    }
    let Ok(value) = args[0].parse::<f32>() else {
        return "Usage: contrast [value]".to_string();
    };
    grading.contrast = value.clamp(0.0, 4.0);
    format!("Contrast set to {:.2}", grading.contrast)
}

fn fullscreen<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let graphics = &mut world.resources.renderer_state;
    if args.is_empty() {
        return format!(
            "Fullscreen: {}",
            if graphics.use_fullscreen { "on" } else { "off" }
        );
    }
    let Some(value) = parse_bool(args[0]) else {
        return "Usage: fullscreen [on|off]".to_string();
    };
    graphics.use_fullscreen = value;
    format!("Fullscreen {}", if value { "enabled" } else { "disabled" })
}

fn cursor<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
    let graphics = &mut world.resources.renderer_state;
    if args.is_empty() {
        return format!(
            "Cursor: {}",
            if graphics.show_cursor { "on" } else { "off" }
        );
    }
    let Some(value) = parse_bool(args[0]) else {
        return "Usage: cursor [on|off]".to_string();
    };
    graphics.show_cursor = value;
    format!("Cursor {}", if value { "shown" } else { "hidden" })
}
