use crate::render::wgpu::sprite_texture_atlas::SpriteTextureAtlas;
use nalgebra_glm::Vec2;

pub const SHAPE_TEXTURE_SIZE: u32 = 128;
pub const SHAPE_SLOT_CIRCLE: u32 = 120;
pub const SHAPE_SLOT_SOFT_CIRCLE: u32 = 121;
pub const SHAPE_SLOT_RING: u32 = 122;
pub const SHAPE_SLOT_TRIANGLE: u32 = 123;
pub const SHAPE_SLOT_CAPSULE: u32 = 124;
pub const SHAPE_SLOT_OUTLINED_RECT: u32 = 125;
pub const SHAPE_SLOT_ANTIALIASED_RECT: u32 = 126;

pub fn upload_builtin_shapes(queue: &wgpu::Queue, atlas: &mut SpriteTextureAtlas) {
    atlas.upload_texture(
        queue,
        SHAPE_SLOT_CIRCLE,
        &generate_circle_texture(SHAPE_TEXTURE_SIZE),
        SHAPE_TEXTURE_SIZE,
        SHAPE_TEXTURE_SIZE,
    );

    atlas.upload_texture(
        queue,
        SHAPE_SLOT_SOFT_CIRCLE,
        &generate_soft_circle_texture(SHAPE_TEXTURE_SIZE),
        SHAPE_TEXTURE_SIZE,
        SHAPE_TEXTURE_SIZE,
    );

    atlas.upload_texture(
        queue,
        SHAPE_SLOT_RING,
        &generate_ring_texture(SHAPE_TEXTURE_SIZE),
        SHAPE_TEXTURE_SIZE,
        SHAPE_TEXTURE_SIZE,
    );

    atlas.upload_texture(
        queue,
        SHAPE_SLOT_TRIANGLE,
        &generate_triangle_texture(SHAPE_TEXTURE_SIZE),
        SHAPE_TEXTURE_SIZE,
        SHAPE_TEXTURE_SIZE,
    );

    atlas.upload_texture(
        queue,
        SHAPE_SLOT_CAPSULE,
        &generate_capsule_texture(SHAPE_TEXTURE_SIZE),
        SHAPE_TEXTURE_SIZE,
        SHAPE_TEXTURE_SIZE,
    );

    atlas.upload_texture(
        queue,
        SHAPE_SLOT_OUTLINED_RECT,
        &generate_outlined_rect_texture(SHAPE_TEXTURE_SIZE),
        SHAPE_TEXTURE_SIZE,
        SHAPE_TEXTURE_SIZE,
    );

    atlas.upload_texture(
        queue,
        SHAPE_SLOT_ANTIALIASED_RECT,
        &generate_antialiased_rect_texture(SHAPE_TEXTURE_SIZE),
        SHAPE_TEXTURE_SIZE,
        SHAPE_TEXTURE_SIZE,
    );
}

pub fn shape_uv() -> (Vec2, Vec2) {
    let slot_size = crate::render::wgpu::sprite_texture_atlas::SPRITE_ATLAS_SLOT_SIZE;
    let half_texel_x = 0.5 / slot_size.0 as f32;
    let half_texel_y = 0.5 / slot_size.1 as f32;
    let uv_min = Vec2::new(half_texel_x, half_texel_y);
    let uv_max = Vec2::new(
        SHAPE_TEXTURE_SIZE as f32 / slot_size.0 as f32 - half_texel_x,
        SHAPE_TEXTURE_SIZE as f32 / slot_size.1 as f32 - half_texel_y,
    );
    (uv_min, uv_max)
}

fn generate_circle_texture(size: u32) -> Vec<u8> {
    generate_circle_texture_with_aa(size, 1.0)
}

pub fn generate_circle_texture_with_aa(size: u32, aa_pixels: f32) -> Vec<u8> {
    let mut data = vec![0u8; (size * size * 4) as usize];
    let center = size as f32 / 2.0 - 0.5;
    let radius = size as f32 / 2.0 - 1.0;
    let aa = aa_pixels.max(0.001);
    for pixel_y in 0..size {
        for pixel_x in 0..size {
            let distance_x = pixel_x as f32 - center;
            let distance_y = pixel_y as f32 - center;
            let distance = (distance_x * distance_x + distance_y * distance_y).sqrt();
            let index = ((pixel_y * size + pixel_x) * 4) as usize;
            if distance < radius + aa {
                let alpha = ((radius + aa - distance) / aa).clamp(0.0, 1.0);
                data[index] = 255;
                data[index + 1] = 255;
                data[index + 2] = 255;
                data[index + 3] = (alpha * 255.0) as u8;
            }
        }
    }
    data
}

fn generate_soft_circle_texture(size: u32) -> Vec<u8> {
    let mut data = vec![0u8; (size * size * 4) as usize];
    let center = size as f32 / 2.0 - 0.5;
    let radius = size as f32 / 2.0 - 1.0;
    for pixel_y in 0..size {
        for pixel_x in 0..size {
            let distance_x = pixel_x as f32 - center;
            let distance_y = pixel_y as f32 - center;
            let distance = (distance_x * distance_x + distance_y * distance_y).sqrt();
            let index = ((pixel_y * size + pixel_x) * 4) as usize;
            if distance < radius {
                let alpha = (1.0 - distance / radius).powf(1.5);
                data[index] = 255;
                data[index + 1] = 255;
                data[index + 2] = 255;
                data[index + 3] = (alpha * 255.0) as u8;
            }
        }
    }
    data
}

fn generate_ring_texture(size: u32) -> Vec<u8> {
    generate_ring_texture_with_aa(size, 0.4, 1.0)
}

pub fn generate_ring_texture_with_aa(size: u32, thickness: f32, aa_pixels: f32) -> Vec<u8> {
    let mut data = vec![0u8; (size * size * 4) as usize];
    let center = size as f32 / 2.0 - 0.5;
    let outer_radius = size as f32 / 2.0 - 1.0;
    let inner_radius = outer_radius * (1.0 - thickness.clamp(0.0, 1.0));
    let aa = aa_pixels.max(0.001);
    for pixel_y in 0..size {
        for pixel_x in 0..size {
            let distance_x = pixel_x as f32 - center;
            let distance_y = pixel_y as f32 - center;
            let distance = (distance_x * distance_x + distance_y * distance_y).sqrt();
            let index = ((pixel_y * size + pixel_x) * 4) as usize;
            let outer_edge = ((outer_radius + aa - distance) / aa).clamp(0.0, 1.0);
            let inner_edge = ((distance - inner_radius + aa) / aa).clamp(0.0, 1.0);
            let alpha = outer_edge * inner_edge;
            if alpha > 0.0 {
                data[index] = 255;
                data[index + 1] = 255;
                data[index + 2] = 255;
                data[index + 3] = (alpha * 255.0) as u8;
            }
        }
    }
    data
}

fn generate_triangle_texture(size: u32) -> Vec<u8> {
    let mut data = vec![0u8; (size * size * 4) as usize];
    let margin = 2.0;
    let top = nalgebra_glm::Vec2::new(size as f32 / 2.0, margin);
    let bottom_left = nalgebra_glm::Vec2::new(margin, size as f32 - margin);
    let bottom_right = nalgebra_glm::Vec2::new(size as f32 - margin, size as f32 - margin);

    for pixel_y in 0..size {
        for pixel_x in 0..size {
            let point = nalgebra_glm::Vec2::new(pixel_x as f32 + 0.5, pixel_y as f32 + 0.5);
            let distance = signed_distance_to_triangle(point, top, bottom_left, bottom_right);
            let index = ((pixel_y * size + pixel_x) * 4) as usize;
            if distance < 1.0 {
                let alpha = (1.0 - distance).clamp(0.0, 1.0);
                data[index] = 255;
                data[index + 1] = 255;
                data[index + 2] = 255;
                data[index + 3] = (alpha * 255.0) as u8;
            }
        }
    }
    data
}

fn signed_distance_to_triangle(
    point: nalgebra_glm::Vec2,
    vertex_a: nalgebra_glm::Vec2,
    vertex_b: nalgebra_glm::Vec2,
    vertex_c: nalgebra_glm::Vec2,
) -> f32 {
    let edge_ab = vertex_b - vertex_a;
    let edge_bc = vertex_c - vertex_b;
    let edge_ca = vertex_a - vertex_c;
    let to_a = point - vertex_a;
    let to_b = point - vertex_b;
    let to_c = point - vertex_c;

    let cross_ab = edge_ab.x * to_a.y - edge_ab.y * to_a.x;
    let cross_bc = edge_bc.x * to_b.y - edge_bc.y * to_b.x;
    let cross_ca = edge_ca.x * to_c.y - edge_ca.y * to_c.x;

    let inside = cross_ab >= 0.0 && cross_bc >= 0.0 && cross_ca >= 0.0;

    let distance_to_edge = |edge: nalgebra_glm::Vec2, to_vertex: nalgebra_glm::Vec2| -> f32 {
        let edge_length_squared = edge.x * edge.x + edge.y * edge.y;
        let parameter =
            ((to_vertex.x * edge.x + to_vertex.y * edge.y) / edge_length_squared).clamp(0.0, 1.0);
        let closest = nalgebra_glm::Vec2::new(
            to_vertex.x - parameter * edge.x,
            to_vertex.y - parameter * edge.y,
        );
        (closest.x * closest.x + closest.y * closest.y).sqrt()
    };

    let minimum_distance = distance_to_edge(edge_ab, to_a)
        .min(distance_to_edge(edge_bc, to_b))
        .min(distance_to_edge(edge_ca, to_c));

    if inside {
        -minimum_distance
    } else {
        minimum_distance
    }
}

fn generate_capsule_texture(size: u32) -> Vec<u8> {
    let mut data = vec![0u8; (size * size * 4) as usize];
    let margin = 2.0;
    let cap_radius = size as f32 / 4.0;
    let center_y = size as f32 / 2.0 - 0.5;
    let left_center = margin + cap_radius;
    let right_center = size as f32 - margin - cap_radius;

    for pixel_y in 0..size {
        for pixel_x in 0..size {
            let px = pixel_x as f32;
            let py = pixel_y as f32;
            let dy = py - center_y;

            let distance = if px < left_center {
                let dx = px - left_center;
                (dx * dx + dy * dy).sqrt() - cap_radius
            } else if px > right_center {
                let dx = px - right_center;
                (dx * dx + dy * dy).sqrt() - cap_radius
            } else {
                dy.abs() - cap_radius
            };

            let index = ((pixel_y * size + pixel_x) * 4) as usize;
            if distance < 1.0 {
                let alpha = (1.0 - distance).clamp(0.0, 1.0);
                data[index] = 255;
                data[index + 1] = 255;
                data[index + 2] = 255;
                data[index + 3] = (alpha * 255.0) as u8;
            }
        }
    }
    data
}

pub fn generate_linear_gradient_texture(
    width: u32,
    height: u32,
    color_start: [f32; 4],
    color_end: [f32; 4],
    horizontal: bool,
) -> Vec<u8> {
    let mut data = vec![0u8; (width * height * 4) as usize];
    for pixel_y in 0..height {
        for pixel_x in 0..width {
            let parameter = if horizontal {
                pixel_x as f32 / (width - 1).max(1) as f32
            } else {
                pixel_y as f32 / (height - 1).max(1) as f32
            };
            let red = color_start[0] + (color_end[0] - color_start[0]) * parameter;
            let green = color_start[1] + (color_end[1] - color_start[1]) * parameter;
            let blue = color_start[2] + (color_end[2] - color_start[2]) * parameter;
            let alpha = color_start[3] + (color_end[3] - color_start[3]) * parameter;
            let index = ((pixel_y * width + pixel_x) * 4) as usize;
            data[index] = (red * 255.0) as u8;
            data[index + 1] = (green * 255.0) as u8;
            data[index + 2] = (blue * 255.0) as u8;
            data[index + 3] = (alpha * 255.0) as u8;
        }
    }
    data
}

pub fn generate_radial_gradient_texture(
    size: u32,
    color_center: [f32; 4],
    color_edge: [f32; 4],
) -> Vec<u8> {
    let mut data = vec![0u8; (size * size * 4) as usize];
    let center = size as f32 / 2.0 - 0.5;
    let radius = size as f32 / 2.0;
    for pixel_y in 0..size {
        for pixel_x in 0..size {
            let distance_x = pixel_x as f32 - center;
            let distance_y = pixel_y as f32 - center;
            let distance = (distance_x * distance_x + distance_y * distance_y).sqrt();
            let parameter = (distance / radius).clamp(0.0, 1.0);
            let red = color_center[0] + (color_edge[0] - color_center[0]) * parameter;
            let green = color_center[1] + (color_edge[1] - color_center[1]) * parameter;
            let blue = color_center[2] + (color_edge[2] - color_center[2]) * parameter;
            let alpha = color_center[3] + (color_edge[3] - color_center[3]) * parameter;
            let index = ((pixel_y * size + pixel_x) * 4) as usize;
            data[index] = (red * 255.0) as u8;
            data[index + 1] = (green * 255.0) as u8;
            data[index + 2] = (blue * 255.0) as u8;
            data[index + 3] = (alpha * 255.0) as u8;
        }
    }
    data
}

pub fn generate_gradient_lut(stops: &[(f32, [f32; 4])]) -> Vec<u8> {
    generate_multi_stop_gradient_texture(256, 1, stops, true)
}

pub fn generate_multi_stop_gradient_texture(
    width: u32,
    height: u32,
    stops: &[(f32, [f32; 4])],
    horizontal: bool,
) -> Vec<u8> {
    let mut data = vec![0u8; (width * height * 4) as usize];
    if stops.is_empty() {
        return data;
    }
    if stops.len() == 1 {
        let color = stops[0].1;
        for pixel_y in 0..height {
            for pixel_x in 0..width {
                let index = ((pixel_y * width + pixel_x) * 4) as usize;
                data[index] = (color[0] * 255.0) as u8;
                data[index + 1] = (color[1] * 255.0) as u8;
                data[index + 2] = (color[2] * 255.0) as u8;
                data[index + 3] = (color[3] * 255.0) as u8;
            }
        }
        return data;
    }
    for pixel_y in 0..height {
        for pixel_x in 0..width {
            let parameter = if horizontal {
                pixel_x as f32 / (width - 1).max(1) as f32
            } else {
                pixel_y as f32 / (height - 1).max(1) as f32
            };
            let (red, green, blue, alpha) = sample_gradient_stops(stops, parameter);
            let index = ((pixel_y * width + pixel_x) * 4) as usize;
            data[index] = (red * 255.0) as u8;
            data[index + 1] = (green * 255.0) as u8;
            data[index + 2] = (blue * 255.0) as u8;
            data[index + 3] = (alpha * 255.0) as u8;
        }
    }
    data
}

fn sample_gradient_stops(stops: &[(f32, [f32; 4])], parameter: f32) -> (f32, f32, f32, f32) {
    if parameter <= stops[0].0 {
        let color = stops[0].1;
        return (color[0], color[1], color[2], color[3]);
    }
    if parameter >= stops[stops.len() - 1].0 {
        let color = stops[stops.len() - 1].1;
        return (color[0], color[1], color[2], color[3]);
    }
    for index in 0..stops.len() - 1 {
        let (position_a, color_a) = stops[index];
        let (position_b, color_b) = stops[index + 1];
        if parameter >= position_a && parameter <= position_b {
            let segment_length = position_b - position_a;
            let local_parameter = if segment_length > 0.0 {
                (parameter - position_a) / segment_length
            } else {
                0.0
            };
            return (
                color_a[0] + (color_b[0] - color_a[0]) * local_parameter,
                color_a[1] + (color_b[1] - color_a[1]) * local_parameter,
                color_a[2] + (color_b[2] - color_a[2]) * local_parameter,
                color_a[3] + (color_b[3] - color_a[3]) * local_parameter,
            );
        }
    }
    let color = stops[stops.len() - 1].1;
    (color[0], color[1], color[2], color[3])
}

fn generate_antialiased_rect_texture(size: u32) -> Vec<u8> {
    let mut data = vec![0u8; (size * size * 4) as usize];
    let edge_width = size as f32 * 0.1;

    for pixel_y in 0..size {
        for pixel_x in 0..size {
            let distance_to_left = pixel_x as f32;
            let distance_to_right = (size - 1 - pixel_x) as f32;
            let distance_to_top = pixel_y as f32;
            let distance_to_bottom = (size - 1 - pixel_y) as f32;

            let distance_to_nearest_edge = distance_to_left
                .min(distance_to_right)
                .min(distance_to_top)
                .min(distance_to_bottom);

            let alpha = (distance_to_nearest_edge / edge_width).clamp(0.0, 1.0);

            let index = ((pixel_y * size + pixel_x) * 4) as usize;
            data[index] = 255;
            data[index + 1] = 255;
            data[index + 2] = 255;
            data[index + 3] = (alpha * 255.0) as u8;
        }
    }
    data
}

pub fn generate_rounded_rect_texture(size: u32, corner_radius: f32) -> Vec<u8> {
    generate_rounded_rect_texture_with_aa(size, corner_radius, 1.0)
}

pub fn generate_rounded_rect_texture_with_aa(
    size: u32,
    corner_radius: f32,
    aa_pixels: f32,
) -> Vec<u8> {
    let mut data = vec![0u8; (size * size * 4) as usize];
    let half = size as f32 / 2.0;
    let rect_half = half - 1.0;
    let radius = corner_radius.min(rect_half);
    let aa = aa_pixels.max(0.001);

    for pixel_y in 0..size {
        for pixel_x in 0..size {
            let px = pixel_x as f32 - half + 0.5;
            let py = pixel_y as f32 - half + 0.5;

            let inner_half = rect_half - radius;
            let qx = px.abs() - inner_half;
            let qy = py.abs() - inner_half;

            let distance = if qx > 0.0 && qy > 0.0 {
                (qx * qx + qy * qy).sqrt() - radius
            } else {
                qx.max(qy).max(0.0) - radius + qx.min(0.0).max(qy.min(0.0))
            };

            let index = ((pixel_y * size + pixel_x) * 4) as usize;
            if distance < aa {
                let alpha = ((aa - distance) / aa).clamp(0.0, 1.0);
                data[index] = 255;
                data[index + 1] = 255;
                data[index + 2] = 255;
                data[index + 3] = (alpha * 255.0) as u8;
            }
        }
    }
    data
}

pub fn generate_filled_polygon_texture(width: u32, height: u32, points: &[[f32; 2]]) -> Vec<u8> {
    generate_filled_polygon_texture_with_aa(width, height, points, 1.0)
}

pub fn generate_filled_polygon_texture_with_aa(
    width: u32,
    height: u32,
    points: &[[f32; 2]],
    aa_pixels: f32,
) -> Vec<u8> {
    let mut data = vec![0u8; (width * height * 4) as usize];
    if points.len() < 3 {
        return data;
    }

    let aa = aa_pixels.max(0.001);

    for pixel_y in 0..height {
        for pixel_x in 0..width {
            let px = (pixel_x as f32 + 0.5) / width as f32;
            let py = (pixel_y as f32 + 0.5) / height as f32;

            let mut winding_number = 0i32;
            let mut min_distance = f32::MAX;
            let point_count = points.len();

            for edge_index in 0..point_count {
                let next_index = (edge_index + 1) % point_count;
                let ax = points[edge_index][0];
                let ay = points[edge_index][1];
                let bx = points[next_index][0];
                let by = points[next_index][1];

                let edge_x = bx - ax;
                let edge_y = by - ay;
                let to_point_x = px - ax;
                let to_point_y = py - ay;

                let edge_length_squared = edge_x * edge_x + edge_y * edge_y;
                let parameter = if edge_length_squared > 0.0 {
                    ((to_point_x * edge_x + to_point_y * edge_y) / edge_length_squared)
                        .clamp(0.0, 1.0)
                } else {
                    0.0
                };
                let closest_x = to_point_x - parameter * edge_x;
                let closest_y = to_point_y - parameter * edge_y;
                let segment_distance = (closest_x * closest_x + closest_y * closest_y).sqrt();
                min_distance = min_distance.min(segment_distance);

                if ay <= py {
                    if by > py && edge_x * to_point_y - edge_y * to_point_x > 0.0 {
                        winding_number += 1;
                    }
                } else if by <= py && edge_x * to_point_y - edge_y * to_point_x < 0.0 {
                    winding_number -= 1;
                }
            }

            let pixel_size = 1.0 / width.max(height) as f32;
            let normalized_distance = min_distance / pixel_size;

            let index = ((pixel_y * width + pixel_x) * 4) as usize;
            if winding_number != 0 {
                let alpha = (normalized_distance.min(aa) / aa).clamp(0.0, 1.0);
                data[index] = 255;
                data[index + 1] = 255;
                data[index + 2] = 255;
                data[index + 3] = (alpha * 255.0) as u8;
            } else if normalized_distance < aa {
                let alpha = ((aa - normalized_distance) / aa).clamp(0.0, 1.0);
                data[index] = 255;
                data[index + 1] = 255;
                data[index + 2] = 255;
                data[index + 3] = (alpha * 255.0) as u8;
            }
        }
    }
    data
}

pub fn generate_ring_texture_with_thickness(size: u32, thickness: f32) -> Vec<u8> {
    let mut data = vec![0u8; (size * size * 4) as usize];
    let center = size as f32 / 2.0 - 0.5;
    let outer_radius = size as f32 / 2.0 - 1.0;
    let inner_radius = outer_radius * (1.0 - thickness.clamp(0.0, 1.0));
    for pixel_y in 0..size {
        for pixel_x in 0..size {
            let distance_x = pixel_x as f32 - center;
            let distance_y = pixel_y as f32 - center;
            let distance = (distance_x * distance_x + distance_y * distance_y).sqrt();
            let index = ((pixel_y * size + pixel_x) * 4) as usize;
            let outer_edge = (outer_radius + 1.0 - distance).clamp(0.0, 1.0);
            let inner_edge = (distance - inner_radius + 1.0).clamp(0.0, 1.0);
            let alpha = outer_edge * inner_edge;
            if alpha > 0.0 {
                data[index] = 255;
                data[index + 1] = 255;
                data[index + 2] = 255;
                data[index + 3] = (alpha * 255.0) as u8;
            }
        }
    }
    data
}

pub fn generate_outlined_rect_texture_with_border(size: u32, border_width: f32) -> Vec<u8> {
    let mut data = vec![0u8; (size * size * 4) as usize];
    let margin = 2.0;

    for pixel_y in 0..size {
        for pixel_x in 0..size {
            let px = pixel_x as f32;
            let py = pixel_y as f32;

            let distance_to_left = px - margin;
            let distance_to_right = (size as f32 - margin) - px;
            let distance_to_top = py - margin;
            let distance_to_bottom = (size as f32 - margin) - py;

            let distance_to_nearest_edge = distance_to_left
                .min(distance_to_right)
                .min(distance_to_top)
                .min(distance_to_bottom);

            let outside = distance_to_nearest_edge < 0.0;
            let inside_border = distance_to_nearest_edge < border_width;

            let index = ((pixel_y * size + pixel_x) * 4) as usize;
            if outside {
                let alpha = (1.0 + distance_to_nearest_edge).clamp(0.0, 1.0);
                if alpha > 0.0 {
                    data[index] = 255;
                    data[index + 1] = 255;
                    data[index + 2] = 255;
                    data[index + 3] = (alpha * 255.0) as u8;
                }
            } else if inside_border {
                let inner_alpha = (border_width - distance_to_nearest_edge).clamp(0.0, 1.0);
                data[index] = 255;
                data[index + 1] = 255;
                data[index + 2] = 255;
                data[index + 3] = (inner_alpha * 255.0) as u8;
            }
        }
    }
    data
}

pub fn boolean_union(a: &[u8], b: &[u8], width: u32, height: u32) -> Vec<u8> {
    let pixel_count = (width * height) as usize;
    let expected_length = pixel_count * 4;
    assert!(
        a.len() >= expected_length && b.len() >= expected_length,
        "boolean_union: buffer too short (expected {expected_length}, got a={} b={})",
        a.len(),
        b.len()
    );
    let mut output = vec![0u8; expected_length];
    for pixel_index in 0..pixel_count {
        let byte_offset = pixel_index * 4;
        let alpha_a = a[byte_offset + 3] as f32 / 255.0;
        let alpha_b = b[byte_offset + 3] as f32 / 255.0;
        let alpha_out = alpha_a.max(alpha_b);
        if alpha_out > 0.0 {
            let weight_a = alpha_a / (alpha_a + alpha_b).max(0.001);
            let weight_b = 1.0 - weight_a;
            output[byte_offset] =
                (a[byte_offset] as f32 * weight_a + b[byte_offset] as f32 * weight_b) as u8;
            output[byte_offset + 1] =
                (a[byte_offset + 1] as f32 * weight_a + b[byte_offset + 1] as f32 * weight_b) as u8;
            output[byte_offset + 2] =
                (a[byte_offset + 2] as f32 * weight_a + b[byte_offset + 2] as f32 * weight_b) as u8;
        }
        output[byte_offset + 3] = (alpha_out * 255.0) as u8;
    }
    output
}

pub fn boolean_subtract(a: &[u8], b: &[u8], width: u32, height: u32) -> Vec<u8> {
    let pixel_count = (width * height) as usize;
    let expected_length = pixel_count * 4;
    assert!(
        a.len() >= expected_length && b.len() >= expected_length,
        "boolean_subtract: buffer too short (expected {expected_length}, got a={} b={})",
        a.len(),
        b.len()
    );
    let mut output = vec![0u8; expected_length];
    for pixel_index in 0..pixel_count {
        let byte_offset = pixel_index * 4;
        let alpha_a = a[byte_offset + 3] as f32 / 255.0;
        let alpha_b = b[byte_offset + 3] as f32 / 255.0;
        let alpha_out = (alpha_a - alpha_b).max(0.0);
        output[byte_offset] = a[byte_offset];
        output[byte_offset + 1] = a[byte_offset + 1];
        output[byte_offset + 2] = a[byte_offset + 2];
        output[byte_offset + 3] = (alpha_out * 255.0) as u8;
    }
    output
}

pub fn boolean_intersect(a: &[u8], b: &[u8], width: u32, height: u32) -> Vec<u8> {
    let pixel_count = (width * height) as usize;
    let expected_length = pixel_count * 4;
    assert!(
        a.len() >= expected_length && b.len() >= expected_length,
        "boolean_intersect: buffer too short (expected {expected_length}, got a={} b={})",
        a.len(),
        b.len()
    );
    let mut output = vec![0u8; expected_length];
    for pixel_index in 0..pixel_count {
        let byte_offset = pixel_index * 4;
        let alpha_a = a[byte_offset + 3] as f32 / 255.0;
        let alpha_b = b[byte_offset + 3] as f32 / 255.0;
        let alpha_out = alpha_a.min(alpha_b);
        if alpha_out > 0.0 {
            let weight_a = alpha_a / (alpha_a + alpha_b).max(0.001);
            let weight_b = 1.0 - weight_a;
            output[byte_offset] =
                (a[byte_offset] as f32 * weight_a + b[byte_offset] as f32 * weight_b) as u8;
            output[byte_offset + 1] =
                (a[byte_offset + 1] as f32 * weight_a + b[byte_offset + 1] as f32 * weight_b) as u8;
            output[byte_offset + 2] =
                (a[byte_offset + 2] as f32 * weight_a + b[byte_offset + 2] as f32 * weight_b) as u8;
        }
        output[byte_offset + 3] = (alpha_out * 255.0) as u8;
    }
    output
}

pub fn apply_alpha_mask(source: &[u8], mask: &[u8], width: u32, height: u32) -> Vec<u8> {
    let pixel_count = (width * height) as usize;
    let expected_length = pixel_count * 4;
    assert!(
        source.len() >= expected_length && mask.len() >= expected_length,
        "apply_alpha_mask: buffer too short (expected {expected_length}, got source={} mask={})",
        source.len(),
        mask.len()
    );
    let mut output = vec![0u8; expected_length];
    for pixel_index in 0..pixel_count {
        let byte_offset = pixel_index * 4;
        let source_alpha = source[byte_offset + 3] as f32 / 255.0;
        let mask_alpha = mask[byte_offset + 3] as f32 / 255.0;
        output[byte_offset] = source[byte_offset];
        output[byte_offset + 1] = source[byte_offset + 1];
        output[byte_offset + 2] = source[byte_offset + 2];
        output[byte_offset + 3] = (source_alpha * mask_alpha * 255.0) as u8;
    }
    output
}

pub fn apply_luminance_mask(source: &[u8], mask: &[u8], width: u32, height: u32) -> Vec<u8> {
    let pixel_count = (width * height) as usize;
    let expected_length = pixel_count * 4;
    assert!(
        source.len() >= expected_length && mask.len() >= expected_length,
        "apply_luminance_mask: buffer too short (expected {expected_length}, got source={} mask={})",
        source.len(),
        mask.len()
    );
    let mut output = vec![0u8; expected_length];
    for pixel_index in 0..pixel_count {
        let byte_offset = pixel_index * 4;
        let source_alpha = source[byte_offset + 3] as f32 / 255.0;
        let luminance = mask[byte_offset] as f32 * 0.299 / 255.0
            + mask[byte_offset + 1] as f32 * 0.587 / 255.0
            + mask[byte_offset + 2] as f32 * 0.114 / 255.0;
        output[byte_offset] = source[byte_offset];
        output[byte_offset + 1] = source[byte_offset + 1];
        output[byte_offset + 2] = source[byte_offset + 2];
        output[byte_offset + 3] = (source_alpha * luminance * 255.0) as u8;
    }
    output
}

pub struct CompositeLayer {
    pub texture: Vec<u8>,
    pub width: u32,
    pub height: u32,
    pub offset_x: i32,
    pub offset_y: i32,
    pub opacity: f32,
}

pub fn composite_layers(
    layers: &[CompositeLayer],
    output_width: u32,
    output_height: u32,
) -> Vec<u8> {
    let mut output = vec![0u8; (output_width * output_height * 4) as usize];
    for layer in layers {
        let opacity = layer.opacity.clamp(0.0, 1.0);
        if opacity <= 0.0 {
            continue;
        }
        for source_y in 0..layer.height {
            let destination_y = source_y as i32 + layer.offset_y;
            if destination_y < 0 || destination_y >= output_height as i32 {
                continue;
            }
            for source_x in 0..layer.width {
                let destination_x = source_x as i32 + layer.offset_x;
                if destination_x < 0 || destination_x >= output_width as i32 {
                    continue;
                }
                let source_offset = ((source_y * layer.width + source_x) * 4) as usize;
                let destination_offset =
                    ((destination_y as u32 * output_width + destination_x as u32) * 4) as usize;

                let source_alpha = layer.texture[source_offset + 3] as f32 / 255.0 * opacity;
                if source_alpha <= 0.0 {
                    continue;
                }

                let destination_alpha = output[destination_offset + 3] as f32 / 255.0;
                let out_alpha = source_alpha + destination_alpha * (1.0 - source_alpha);

                if out_alpha > 0.0 {
                    let source_weight = source_alpha / out_alpha;
                    let destination_weight = destination_alpha * (1.0 - source_alpha) / out_alpha;
                    output[destination_offset] = (layer.texture[source_offset] as f32
                        * source_weight
                        + output[destination_offset] as f32 * destination_weight)
                        as u8;
                    output[destination_offset + 1] = (layer.texture[source_offset + 1] as f32
                        * source_weight
                        + output[destination_offset + 1] as f32 * destination_weight)
                        as u8;
                    output[destination_offset + 2] = (layer.texture[source_offset + 2] as f32
                        * source_weight
                        + output[destination_offset + 2] as f32 * destination_weight)
                        as u8;
                }
                output[destination_offset + 3] = (out_alpha * 255.0) as u8;
            }
        }
    }
    output
}

pub fn generate_blurred_texture(source: &[u8], width: u32, height: u32, radius: u32) -> Vec<u8> {
    if radius == 0 {
        return source.to_vec();
    }

    let kernel_size = radius * 2 + 1;
    let sigma = (radius as f32 + 0.5) / 2.0;
    let mut weights = Vec::with_capacity(kernel_size as usize);
    let mut weight_sum = 0.0f32;
    for index in 0..kernel_size {
        let offset = index as f32 - radius as f32;
        let weight = (-offset * offset / (2.0 * sigma * sigma)).exp();
        weights.push(weight);
        weight_sum += weight;
    }
    for weight in &mut weights {
        *weight /= weight_sum;
    }

    let pixel_count = (width * height) as usize;
    let mut intermediate = vec![0.0f32; pixel_count * 4];

    for pixel_y in 0..height {
        for pixel_x in 0..width {
            let mut red = 0.0f32;
            let mut green = 0.0f32;
            let mut blue = 0.0f32;
            let mut alpha = 0.0f32;
            for kernel_index in 0..kernel_size {
                let sample_x = (pixel_x as i32 + kernel_index as i32 - radius as i32)
                    .clamp(0, width as i32 - 1) as u32;
                let source_offset = ((pixel_y * width + sample_x) * 4) as usize;
                let weight = weights[kernel_index as usize];
                red += source[source_offset] as f32 * weight;
                green += source[source_offset + 1] as f32 * weight;
                blue += source[source_offset + 2] as f32 * weight;
                alpha += source[source_offset + 3] as f32 * weight;
            }
            let destination_offset = ((pixel_y * width + pixel_x) * 4) as usize;
            intermediate[destination_offset] = red;
            intermediate[destination_offset + 1] = green;
            intermediate[destination_offset + 2] = blue;
            intermediate[destination_offset + 3] = alpha;
        }
    }

    let mut output = vec![0u8; pixel_count * 4];

    for pixel_y in 0..height {
        for pixel_x in 0..width {
            let mut red = 0.0f32;
            let mut green = 0.0f32;
            let mut blue = 0.0f32;
            let mut alpha = 0.0f32;
            for kernel_index in 0..kernel_size {
                let sample_y = (pixel_y as i32 + kernel_index as i32 - radius as i32)
                    .clamp(0, height as i32 - 1) as u32;
                let source_offset = ((sample_y * width + pixel_x) * 4) as usize;
                let weight = weights[kernel_index as usize];
                red += intermediate[source_offset] * weight;
                green += intermediate[source_offset + 1] * weight;
                blue += intermediate[source_offset + 2] * weight;
                alpha += intermediate[source_offset + 3] * weight;
            }
            let destination_offset = ((pixel_y * width + pixel_x) * 4) as usize;
            output[destination_offset] = red.clamp(0.0, 255.0) as u8;
            output[destination_offset + 1] = green.clamp(0.0, 255.0) as u8;
            output[destination_offset + 2] = blue.clamp(0.0, 255.0) as u8;
            output[destination_offset + 3] = alpha.clamp(0.0, 255.0) as u8;
        }
    }

    output
}

#[cfg(feature = "assets")]
pub fn load_image_rgba(bytes: &[u8]) -> Result<(Vec<u8>, u32, u32), String> {
    let dynamic_image = image::load_from_memory(bytes).map_err(|error| error.to_string())?;
    let slot_max = crate::render::wgpu::sprite_texture_atlas::SPRITE_ATLAS_SLOT_SIZE;
    let max_width = slot_max.0;
    let max_height = slot_max.1;

    let resized = if dynamic_image.width() > max_width || dynamic_image.height() > max_height {
        dynamic_image.resize(max_width, max_height, image::imageops::FilterType::Lanczos3)
    } else {
        dynamic_image
    };

    let rgba = resized.to_rgba8();
    let width = rgba.width();
    let height = rgba.height();
    Ok((rgba.into_raw(), width, height))
}

fn generate_outlined_rect_texture(size: u32) -> Vec<u8> {
    let mut data = vec![0u8; (size * size * 4) as usize];
    let margin = 2.0;
    let border_width = size as f32 * 0.08;

    for pixel_y in 0..size {
        for pixel_x in 0..size {
            let px = pixel_x as f32;
            let py = pixel_y as f32;

            let distance_to_left = px - margin;
            let distance_to_right = (size as f32 - margin) - px;
            let distance_to_top = py - margin;
            let distance_to_bottom = (size as f32 - margin) - py;

            let distance_to_nearest_edge = distance_to_left
                .min(distance_to_right)
                .min(distance_to_top)
                .min(distance_to_bottom);

            let outside = distance_to_nearest_edge < 0.0;
            let inside_border = distance_to_nearest_edge < border_width;

            let index = ((pixel_y * size + pixel_x) * 4) as usize;
            if outside {
                let alpha = (1.0 + distance_to_nearest_edge).clamp(0.0, 1.0);
                if alpha > 0.0 {
                    data[index] = 255;
                    data[index + 1] = 255;
                    data[index + 2] = 255;
                    data[index + 3] = (alpha * 255.0) as u8;
                }
            } else if inside_border {
                let inner_alpha = (border_width - distance_to_nearest_edge).clamp(0.0, 1.0);
                data[index] = 255;
                data[index + 1] = 255;
                data[index + 2] = 255;
                data[index + 3] = (inner_alpha * 255.0) as u8;
            }
        }
    }
    data
}
