use crate::ecs::sprite::commands::spawn_sprite;
use crate::ecs::sprite::slot_allocator::{allocate_sprite_slot, sized_slot_uv};
use crate::ecs::world::commands::WorldCommand;
use crate::ecs::world::{Entity, World};
use crate::render::wgpu::sprite_shapes;
use nalgebra_glm::Vec2;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum SpriteShape {
    Rect,
    Circle,
    SoftCircle,
    Ring,
    Triangle,
    Capsule,
    OutlinedRect,
}

pub fn spawn_shape(
    world: &mut World,
    shape: SpriteShape,
    position: Vec2,
    size: Vec2,
    color: [f32; 4],
) -> Entity {
    let entity = spawn_sprite(world, position, size);
    let (texture_index, uv_min, uv_max) = shape_texture_info(shape);

    if let Some(sprite) = world.get_sprite_mut(entity) {
        sprite.texture_index = texture_index;
        sprite.texture_index2 = texture_index;
        sprite.uv_min = uv_min;
        sprite.uv_max = uv_max;
        sprite.color = color;
    }

    entity
}

pub fn shape_texture_info(shape: SpriteShape) -> (u32, Vec2, Vec2) {
    match shape {
        SpriteShape::Rect => (0, Vec2::new(0.0, 0.0), Vec2::new(1.0, 1.0)),
        SpriteShape::Circle => {
            let (uv_min, uv_max) = sprite_shapes::shape_uv();
            (sprite_shapes::SHAPE_SLOT_CIRCLE, uv_min, uv_max)
        }
        SpriteShape::SoftCircle => {
            let (uv_min, uv_max) = sprite_shapes::shape_uv();
            (sprite_shapes::SHAPE_SLOT_SOFT_CIRCLE, uv_min, uv_max)
        }
        SpriteShape::Ring => {
            let (uv_min, uv_max) = sprite_shapes::shape_uv();
            (sprite_shapes::SHAPE_SLOT_RING, uv_min, uv_max)
        }
        SpriteShape::Triangle => {
            let (uv_min, uv_max) = sprite_shapes::shape_uv();
            (sprite_shapes::SHAPE_SLOT_TRIANGLE, uv_min, uv_max)
        }
        SpriteShape::Capsule => {
            let (uv_min, uv_max) = sprite_shapes::shape_uv();
            (sprite_shapes::SHAPE_SLOT_CAPSULE, uv_min, uv_max)
        }
        SpriteShape::OutlinedRect => {
            let (uv_min, uv_max) = sprite_shapes::shape_uv();
            (sprite_shapes::SHAPE_SLOT_OUTLINED_RECT, uv_min, uv_max)
        }
    }
}

pub fn spawn_rect(world: &mut World, position: Vec2, size: Vec2, color: [f32; 4]) -> Entity {
    spawn_shape(world, SpriteShape::Rect, position, size, color)
}

pub fn spawn_circle(world: &mut World, position: Vec2, radius: f32, color: [f32; 4]) -> Entity {
    let size = Vec2::new(radius * 2.0, radius * 2.0);
    spawn_shape(world, SpriteShape::Circle, position, size, color)
}

pub fn spawn_ring(world: &mut World, position: Vec2, radius: f32, color: [f32; 4]) -> Entity {
    let size = Vec2::new(radius * 2.0, radius * 2.0);
    spawn_shape(world, SpriteShape::Ring, position, size, color)
}

pub fn spawn_triangle(world: &mut World, position: Vec2, size: Vec2, color: [f32; 4]) -> Entity {
    spawn_shape(world, SpriteShape::Triangle, position, size, color)
}

pub fn spawn_capsule(world: &mut World, position: Vec2, size: Vec2, color: [f32; 4]) -> Entity {
    spawn_shape(world, SpriteShape::Capsule, position, size, color)
}

pub fn spawn_outlined_rect(
    world: &mut World,
    position: Vec2,
    size: Vec2,
    color: [f32; 4],
) -> Entity {
    spawn_shape(world, SpriteShape::OutlinedRect, position, size, color)
}

pub fn spawn_line(
    world: &mut World,
    start: Vec2,
    end: Vec2,
    thickness: f32,
    color: [f32; 4],
) -> Entity {
    let delta = end - start;
    let length = nalgebra_glm::length(&delta);
    let angle = delta.y.atan2(delta.x);
    let midpoint = (start + end) * 0.5;
    let aa_padding = (thickness * 0.3).max(1.0);
    let (uv_min, uv_max) = sprite_shapes::shape_uv();
    let entity = spawn_sprite(
        world,
        midpoint,
        Vec2::new(length + aa_padding, thickness + aa_padding),
    );
    if let Some(sprite) = world.get_sprite_mut(entity) {
        sprite.rotation = angle;
        sprite.color = color;
        sprite.texture_index = sprite_shapes::SHAPE_SLOT_ANTIALIASED_RECT;
        sprite.texture_index2 = sprite_shapes::SHAPE_SLOT_ANTIALIASED_RECT;
        sprite.uv_min = uv_min;
        sprite.uv_max = uv_max;
    }
    entity
}

pub fn spawn_arrow(
    world: &mut World,
    start: Vec2,
    end: Vec2,
    thickness: f32,
    head_size: f32,
    color: [f32; 4],
) -> (Entity, Entity) {
    let delta = end - start;
    let angle = delta.y.atan2(delta.x);
    let line_entity = spawn_line(world, start, end, thickness, color);
    let head_entity = spawn_triangle(world, end, Vec2::new(head_size, head_size), color);
    if let Some(sprite) = world.get_sprite_mut(head_entity) {
        sprite.rotation = angle - std::f32::consts::FRAC_PI_2;
    }
    (line_entity, head_entity)
}

pub fn spawn_path(
    world: &mut World,
    points: &[Vec2],
    thickness: f32,
    color: [f32; 4],
    closed: bool,
) -> Vec<Entity> {
    let mut entities = Vec::new();
    if points.len() < 2 {
        return entities;
    }
    for index in 0..points.len() - 1 {
        entities.push(spawn_line(
            world,
            points[index],
            points[index + 1],
            thickness,
            color,
        ));
    }
    if closed && points.len() > 2 {
        entities.push(spawn_line(
            world,
            points[points.len() - 1],
            points[0],
            thickness,
            color,
        ));
    }
    entities
}

fn evaluate_quadratic_bezier(start: Vec2, control: Vec2, end: Vec2, parameter: f32) -> Vec2 {
    let inverse = 1.0 - parameter;
    let inverse_squared = inverse * inverse;
    let parameter_squared = parameter * parameter;
    Vec2::new(
        inverse_squared * start.x
            + 2.0 * inverse * parameter * control.x
            + parameter_squared * end.x,
        inverse_squared * start.y
            + 2.0 * inverse * parameter * control.y
            + parameter_squared * end.y,
    )
}

fn evaluate_cubic_bezier(
    start: Vec2,
    control_a: Vec2,
    control_b: Vec2,
    end: Vec2,
    parameter: f32,
) -> Vec2 {
    let inverse = 1.0 - parameter;
    let inverse_squared = inverse * inverse;
    let inverse_cubed = inverse_squared * inverse;
    let parameter_squared = parameter * parameter;
    let parameter_cubed = parameter_squared * parameter;
    Vec2::new(
        inverse_cubed * start.x
            + 3.0 * inverse_squared * parameter * control_a.x
            + 3.0 * inverse * parameter_squared * control_b.x
            + parameter_cubed * end.x,
        inverse_cubed * start.y
            + 3.0 * inverse_squared * parameter * control_a.y
            + 3.0 * inverse * parameter_squared * control_b.y
            + parameter_cubed * end.y,
    )
}

pub fn spawn_quadratic_bezier(
    world: &mut World,
    start: Vec2,
    control: Vec2,
    end: Vec2,
    segments: u32,
    thickness: f32,
    color: [f32; 4],
) -> Vec<Entity> {
    let mut entities = Vec::with_capacity(segments as usize);
    let mut previous = start;
    for index in 1..=segments {
        let parameter = index as f32 / segments as f32;
        let current = evaluate_quadratic_bezier(start, control, end, parameter);
        entities.push(spawn_line(world, previous, current, thickness, color));
        previous = current;
    }
    entities
}

pub struct CubicBezier {
    pub start: Vec2,
    pub control_a: Vec2,
    pub control_b: Vec2,
    pub end: Vec2,
}

pub fn spawn_cubic_bezier(
    world: &mut World,
    curve: &CubicBezier,
    segments: u32,
    thickness: f32,
    color: [f32; 4],
) -> Vec<Entity> {
    let mut entities = Vec::with_capacity(segments as usize);
    let mut previous = curve.start;
    for index in 1..=segments {
        let parameter = index as f32 / segments as f32;
        let current = evaluate_cubic_bezier(
            curve.start,
            curve.control_a,
            curve.control_b,
            curve.end,
            parameter,
        );
        entities.push(spawn_line(world, previous, current, thickness, color));
        previous = current;
    }
    entities
}

pub fn spawn_ellipse(
    world: &mut World,
    position: Vec2,
    radius_x: f32,
    radius_y: f32,
    color: [f32; 4],
) -> Entity {
    let safe_radius_x = radius_x.max(0.001);
    let diameter = safe_radius_x * 2.0;
    let entity = spawn_circle(world, position, safe_radius_x, color);
    if let Some(sprite) = world.get_sprite_mut(entity) {
        sprite.size = Vec2::new(diameter, diameter);
        sprite.scale = Vec2::new(1.0, radius_y / safe_radius_x);
    }
    entity
}

pub fn spawn_rounded_rect(
    world: &mut World,
    position: Vec2,
    size: Vec2,
    texture_slot: u32,
    color: [f32; 4],
) -> Entity {
    let (uv_min, uv_max) = sprite_shapes::shape_uv();
    let entity = spawn_sprite(world, position, size);
    if let Some(sprite) = world.get_sprite_mut(entity) {
        sprite.texture_index = texture_slot;
        sprite.texture_index2 = texture_slot;
        sprite.uv_min = uv_min;
        sprite.uv_max = uv_max;
        sprite.color = color;
    }
    entity
}

pub fn spawn_filled_polygon(
    world: &mut World,
    position: Vec2,
    size: Vec2,
    texture_slot: u32,
    color: [f32; 4],
) -> Entity {
    let (uv_min, uv_max) = sprite_shapes::shape_uv();
    let entity = spawn_sprite(world, position, size);
    if let Some(sprite) = world.get_sprite_mut(entity) {
        sprite.texture_index = texture_slot;
        sprite.texture_index2 = texture_slot;
        sprite.uv_min = uv_min;
        sprite.uv_max = uv_max;
        sprite.color = color;
    }
    entity
}

pub fn spawn_dashed_line(
    world: &mut World,
    start: Vec2,
    end: Vec2,
    thickness: f32,
    dash_length: f32,
    gap_length: f32,
    color: [f32; 4],
) -> Vec<Entity> {
    let mut entities = Vec::new();
    let delta = end - start;
    let total_length = nalgebra_glm::length(&delta);
    if total_length < 0.001 {
        return entities;
    }
    let direction = delta / total_length;
    let stride = dash_length + gap_length;
    let mut offset = 0.0;

    while offset < total_length {
        let dash_end_offset = (offset + dash_length).min(total_length);
        let dash_start = start + direction * offset;
        let dash_end = start + direction * dash_end_offset;
        entities.push(spawn_line(world, dash_start, dash_end, thickness, color));
        offset += stride;
    }

    entities
}

pub fn spawn_variable_width_path(
    world: &mut World,
    points: &[Vec2],
    pressures: &[f32],
    base_thickness: f32,
    color: [f32; 4],
) -> Vec<Entity> {
    let mut entities = Vec::new();
    if points.len() < 2 || pressures.len() < points.len() {
        return entities;
    }

    for index in 0..points.len() - 1 {
        let pressure = (pressures[index] + pressures[index + 1]) * 0.5;
        let thickness = base_thickness * pressure;
        entities.push(spawn_line(
            world,
            points[index],
            points[index + 1],
            thickness,
            color,
        ));
    }

    entities
}

pub fn spawn_filled_and_stroked_circle(
    world: &mut World,
    position: Vec2,
    radius: f32,
    fill_color: [f32; 4],
    stroke_color: [f32; 4],
) -> (Entity, Entity) {
    let fill_entity = spawn_circle(world, position, radius, fill_color);
    if let Some(sprite) = world.get_sprite_mut(fill_entity) {
        sprite.depth = 0.01;
    }
    let stroke_entity = spawn_ring(world, position, radius, stroke_color);
    (fill_entity, stroke_entity)
}

pub fn spawn_filled_and_stroked_rect(
    world: &mut World,
    position: Vec2,
    size: Vec2,
    fill_color: [f32; 4],
    stroke_color: [f32; 4],
) -> (Entity, Entity) {
    let fill_entity = spawn_rect(world, position, size, fill_color);
    if let Some(sprite) = world.get_sprite_mut(fill_entity) {
        sprite.depth = 0.01;
    }
    let stroke_entity = spawn_outlined_rect(world, position, size, stroke_color);
    (fill_entity, stroke_entity)
}

pub fn spawn_filled_and_stroked_ellipse(
    world: &mut World,
    position: Vec2,
    radius_x: f32,
    radius_y: f32,
    fill_color: [f32; 4],
    stroke_color: [f32; 4],
    ring_texture_slot: u32,
) -> (Entity, Entity) {
    let fill_entity = spawn_ellipse(world, position, radius_x, radius_y, fill_color);
    if let Some(sprite) = world.get_sprite_mut(fill_entity) {
        sprite.depth = 0.01;
    }

    let (uv_min, uv_max) = sprite_shapes::shape_uv();
    let diameter_x = radius_x * 2.0;
    let diameter_y = radius_y * 2.0;
    let entity = spawn_sprite(world, position, Vec2::new(diameter_x, diameter_y));
    if let Some(sprite) = world.get_sprite_mut(entity) {
        sprite.texture_index = ring_texture_slot;
        sprite.texture_index2 = ring_texture_slot;
        sprite.uv_min = uv_min;
        sprite.uv_max = uv_max;
        sprite.color = stroke_color;
    }

    (fill_entity, entity)
}

pub fn screen_pixels_to_world_size(world: &World, pixels: f32) -> f32 {
    let viewport_width = world
        .resources
        .window
        .cached_viewport_size
        .map(|(width, _)| width as f32)
        .unwrap_or(1920.0);

    let ortho_x_mag = world
        .resources
        .active_camera
        .and_then(|entity| world.get_camera(entity))
        .and_then(|camera| {
            if let crate::ecs::camera::components::Projection::Orthographic(ortho) =
                &camera.projection
            {
                Some(ortho.x_mag)
            } else {
                None
            }
        })
        .unwrap_or(480.0);

    pixels * (ortho_x_mag * 2.0) / viewport_width
}

pub fn tessellate_quadratic_bezier(
    start: Vec2,
    control: Vec2,
    end: Vec2,
    segments: u32,
) -> Vec<Vec2> {
    let mut points = Vec::with_capacity(segments as usize + 1);
    for index in 0..=segments {
        let parameter = index as f32 / segments as f32;
        points.push(evaluate_quadratic_bezier(start, control, end, parameter));
    }
    points
}

pub fn tessellate_cubic_bezier(
    start: Vec2,
    control_a: Vec2,
    control_b: Vec2,
    end: Vec2,
    segments: u32,
) -> Vec<Vec2> {
    let mut points = Vec::with_capacity(segments as usize + 1);
    for index in 0..=segments {
        let parameter = index as f32 / segments as f32;
        points.push(evaluate_cubic_bezier(
            start, control_a, control_b, end, parameter,
        ));
    }
    points
}

pub fn spawn_filled_path(
    world: &mut World,
    points: &[Vec2],
    color: [f32; 4],
    position: Vec2,
    size: Vec2,
    depth: f32,
) -> Entity {
    let normalized_points: Vec<[f32; 2]> = points.iter().map(|point| [point.x, point.y]).collect();
    let texture_size = 256u32;
    let texture_data = sprite_shapes::generate_filled_polygon_texture(
        texture_size,
        texture_size,
        &normalized_points,
    );
    let slot = allocate_sprite_slot(world);
    let (uv_min, uv_max) = sized_slot_uv(texture_size, texture_size);
    world
        .resources
        .command_queue
        .push(WorldCommand::UploadSpriteTexture {
            slot,
            rgba_data: texture_data,
            width: texture_size,
            height: texture_size,
        });
    let entity = spawn_sprite(world, position, size);
    if let Some(sprite) = world.get_sprite_mut(entity) {
        sprite.texture_index = slot;
        sprite.texture_index2 = slot;
        sprite.uv_min = uv_min;
        sprite.uv_max = uv_max;
        sprite.color = color;
        sprite.depth = depth;
    }
    entity
}

pub struct BlurSource<'a> {
    pub texture: &'a [u8],
    pub width: u32,
    pub height: u32,
    pub blur_radius: u32,
}

fn pad_texture(source: &[u8], width: u32, height: u32, padding: u32) -> Vec<u8> {
    let padded_width = width + padding * 2;
    let padded_height = height + padding * 2;
    let mut padded = vec![0u8; (padded_width * padded_height * 4) as usize];
    for row in 0..height {
        let source_row_start = (row * width * 4) as usize;
        let destination_row_start = ((row + padding) * padded_width + padding) as usize * 4;
        let row_bytes = (width * 4) as usize;
        padded[destination_row_start..destination_row_start + row_bytes]
            .copy_from_slice(&source[source_row_start..source_row_start + row_bytes]);
    }
    padded
}

pub fn spawn_shadow(
    world: &mut World,
    source: &BlurSource,
    shadow_color: [f32; 4],
    offset: Vec2,
    position: Vec2,
    size: Vec2,
    depth: f32,
) -> Entity {
    let padding = source.blur_radius;
    let padded_width = source.width + padding * 2;
    let padded_height = source.height + padding * 2;
    let padded = pad_texture(source.texture, source.width, source.height, padding);
    let mut blurred = sprite_shapes::generate_blurred_texture(
        &padded,
        padded_width,
        padded_height,
        source.blur_radius,
    );
    let pixel_count = (padded_width * padded_height) as usize;
    for pixel_index in 0..pixel_count {
        let byte_offset = pixel_index * 4;
        blurred[byte_offset] = (shadow_color[0] * 255.0) as u8;
        blurred[byte_offset + 1] = (shadow_color[1] * 255.0) as u8;
        blurred[byte_offset + 2] = (shadow_color[2] * 255.0) as u8;
        blurred[byte_offset + 3] =
            (blurred[byte_offset + 3] as f32 / 255.0 * shadow_color[3] * 255.0) as u8;
    }
    let slot = allocate_sprite_slot(world);
    let (uv_min, uv_max) = sized_slot_uv(padded_width, padded_height);
    world
        .resources
        .command_queue
        .push(WorldCommand::UploadSpriteTexture {
            slot,
            rgba_data: blurred,
            width: padded_width,
            height: padded_height,
        });
    let entity = spawn_sprite(world, position + offset, size);
    if let Some(sprite) = world.get_sprite_mut(entity) {
        sprite.texture_index = slot;
        sprite.texture_index2 = slot;
        sprite.uv_min = uv_min;
        sprite.uv_max = uv_max;
        sprite.color = [1.0, 1.0, 1.0, 1.0];
        sprite.depth = depth;
    }
    entity
}

pub fn spawn_glow(
    world: &mut World,
    source: &BlurSource,
    glow_color: [f32; 4],
    position: Vec2,
    size: Vec2,
    depth: f32,
) -> Entity {
    let padding = source.blur_radius;
    let padded_width = source.width + padding * 2;
    let padded_height = source.height + padding * 2;
    let padded = pad_texture(source.texture, source.width, source.height, padding);
    let mut blurred = sprite_shapes::generate_blurred_texture(
        &padded,
        padded_width,
        padded_height,
        source.blur_radius,
    );
    let pixel_count = (padded_width * padded_height) as usize;
    for pixel_index in 0..pixel_count {
        let byte_offset = pixel_index * 4;
        blurred[byte_offset] = (glow_color[0] * 255.0) as u8;
        blurred[byte_offset + 1] = (glow_color[1] * 255.0) as u8;
        blurred[byte_offset + 2] = (glow_color[2] * 255.0) as u8;
        blurred[byte_offset + 3] =
            (blurred[byte_offset + 3] as f32 / 255.0 * glow_color[3] * 255.0) as u8;
    }
    let slot = allocate_sprite_slot(world);
    let (uv_min, uv_max) = sized_slot_uv(padded_width, padded_height);
    world
        .resources
        .command_queue
        .push(WorldCommand::UploadSpriteTexture {
            slot,
            rgba_data: blurred,
            width: padded_width,
            height: padded_height,
        });
    let entity = spawn_sprite(world, position, size);
    if let Some(sprite) = world.get_sprite_mut(entity) {
        sprite.texture_index = slot;
        sprite.texture_index2 = slot;
        sprite.uv_min = uv_min;
        sprite.uv_max = uv_max;
        sprite.color = [1.0, 1.0, 1.0, 1.0];
        sprite.depth = depth;
        sprite.blend_mode = crate::ecs::sprite::components::SpriteBlendMode::Additive;
    }
    entity
}

pub fn spawn_shape_with_aa(
    world: &mut World,
    shape_texture: Vec<u8>,
    texture_size: u32,
    color: [f32; 4],
    position: Vec2,
    size: Vec2,
    depth: f32,
) -> Entity {
    let slot = allocate_sprite_slot(world);
    let (uv_min, uv_max) = sized_slot_uv(texture_size, texture_size);
    world
        .resources
        .command_queue
        .push(WorldCommand::UploadSpriteTexture {
            slot,
            rgba_data: shape_texture,
            width: texture_size,
            height: texture_size,
        });
    let entity = spawn_sprite(world, position, size);
    if let Some(sprite) = world.get_sprite_mut(entity) {
        sprite.texture_index = slot;
        sprite.texture_index2 = slot;
        sprite.uv_min = uv_min;
        sprite.uv_max = uv_max;
        sprite.color = color;
        sprite.depth = depth;
    }
    entity
}

#[cfg(feature = "assets")]
pub fn spawn_sprite_from_image_bytes(
    world: &mut World,
    bytes: &[u8],
    position: Vec2,
    size: Vec2,
    depth: f32,
) -> Entity {
    let (rgba_data, width, height) =
        sprite_shapes::load_image_rgba(bytes).expect("Failed to decode image");
    let slot = allocate_sprite_slot(world);
    let (uv_min, uv_max) = sized_slot_uv(width, height);
    world
        .resources
        .command_queue
        .push(WorldCommand::UploadSpriteTexture {
            slot,
            rgba_data,
            width,
            height,
        });
    let entity = spawn_sprite(world, position, size);
    if let Some(sprite) = world.get_sprite_mut(entity) {
        sprite.texture_index = slot;
        sprite.texture_index2 = slot;
        sprite.uv_min = uv_min;
        sprite.uv_max = uv_max;
        sprite.depth = depth;
    }
    entity
}
