use crate::ecs::text::resources::GlyphInfo;
use fontdue::{Font, FontSettings};
use nalgebra_glm::{vec2, vec4};
use std::collections::HashMap;

const SDF_PADDING: usize = 8;
const SDF_SCALE: f32 = 64.0;
const SDF_SPREAD: f32 = 8.0;

pub struct FontAtlas {
    pub texture: wgpu::Texture,
    pub texture_view: wgpu::TextureView,
    pub texture_index: u32,
    pub glyphs: HashMap<char, GlyphInfo>,
    pub width: u32,
    pub height: u32,
    pub font_size: f32,
    pub sdf_range: f32,
}

#[derive(Default)]
pub struct FontRegistry {
    pub atlases: Vec<FontAtlas>,
}

impl FontRegistry {
    pub fn register(&mut self, mut atlas: FontAtlas) -> usize {
        let index = self.atlases.len();
        atlas.texture_index = index as u32;
        self.atlases.push(atlas);
        index
    }

    pub fn get(&self, index: usize) -> Option<&FontAtlas> {
        self.atlases.get(index)
    }

    pub fn load_font_from_bytes(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        font_data: &[u8],
        font_size: f32,
    ) -> Result<usize, Box<dyn std::error::Error>> {
        let atlas = generate_font_atlas_sdf(device, queue, font_data, font_size)?;
        Ok(self.register(atlas))
    }
}

pub fn generate_sdf(input: &[u8], width: usize, height: usize, spread: f32) -> Vec<u8> {
    let mut output = vec![0u8; width * height];
    let spread_pixels = spread as isize;

    for y in 0..height {
        for x in 0..width {
            let idx = y * width + x;
            let is_inside = input[idx] > 127;

            let mut min_dist_sq = (spread_pixels * spread_pixels) as f32;

            for dy in -spread_pixels..=spread_pixels {
                for dx in -spread_pixels..=spread_pixels {
                    let ny = y as isize + dy;
                    let nx = x as isize + dx;

                    if ny >= 0 && ny < height as isize && nx >= 0 && nx < width as isize {
                        let nidx = (ny as usize) * width + (nx as usize);
                        let neighbor_inside = input[nidx] > 127;

                        if is_inside != neighbor_inside {
                            let dist_sq = (dx * dx + dy * dy) as f32;
                            min_dist_sq = min_dist_sq.min(dist_sq);
                        }
                    }
                }
            }

            let dist = min_dist_sq.sqrt();
            let normalized_dist = if is_inside {
                0.5 + (dist / (2.0 * spread))
            } else {
                0.5 - (dist / (2.0 * spread))
            };

            output[idx] = (normalized_dist.clamp(0.0, 1.0) * 255.0) as u8;
        }
    }

    output
}

pub struct AtlasPacker {
    width: u32,
    height: u32,
    current_x: u32,
    current_y: u32,
    row_height: u32,
}

impl AtlasPacker {
    pub fn new(width: u32, height: u32) -> Self {
        Self {
            width,
            height,
            current_x: 0,
            current_y: 0,
            row_height: 0,
        }
    }

    pub fn pack(&mut self, glyph_width: u32, glyph_height: u32) -> Option<(u32, u32)> {
        if self.current_x + glyph_width > self.width {
            self.current_x = 0;
            self.current_y += self.row_height;
            self.row_height = 0;
        }

        if self.current_y + glyph_height > self.height {
            return None;
        }

        let position = (self.current_x, self.current_y);
        self.current_x += glyph_width;
        self.row_height = self.row_height.max(glyph_height);

        Some(position)
    }
}

pub fn generate_font_atlas_sdf(
    device: &wgpu::Device,
    queue: &wgpu::Queue,
    font_data: &[u8],
    font_size: f32,
) -> Result<FontAtlas, Box<dyn std::error::Error>> {
    let font = Font::from_bytes(font_data, FontSettings::default())?;

    let atlas_width = 1024;
    let atlas_height = 1024;
    let mut atlas_data = vec![0u8; (atlas_width * atlas_height) as usize];
    let mut packer = AtlasPacker::new(atlas_width, atlas_height);
    let mut glyphs = HashMap::new();

    let chars_to_render: Vec<char> = (32u8..127u8).map(|c| c as char).collect();

    let scale = SDF_SCALE;

    for ch in chars_to_render {
        let (metrics, bitmap) = font.rasterize(ch, scale);

        if metrics.width > 0 && metrics.height > 0 {
            let padded_width = metrics.width + SDF_PADDING * 2;
            let padded_height = metrics.height + SDF_PADDING * 2;

            if let Some((x, y)) = packer.pack(padded_width as u32, padded_height as u32) {
                let mut padded_bitmap = vec![0u8; padded_width * padded_height];

                for src_y in 0..metrics.height {
                    for src_x in 0..metrics.width {
                        let src_idx = src_y * metrics.width + src_x;
                        let dst_x = src_x + SDF_PADDING;
                        let dst_y = src_y + SDF_PADDING;
                        let dst_idx = dst_y * padded_width + dst_x;
                        padded_bitmap[dst_idx] = bitmap[src_idx];
                    }
                }

                let sdf_bitmap =
                    generate_sdf(&padded_bitmap, padded_width, padded_height, SDF_SPREAD);

                for src_y in 0..padded_height {
                    for src_x in 0..padded_width {
                        let src_idx = src_y * padded_width + src_x;
                        let dst_x = (x as usize) + src_x;
                        let dst_y = (y as usize) + src_y;
                        let dst_idx = dst_y * (atlas_width as usize) + dst_x;
                        if dst_idx < atlas_data.len() {
                            atlas_data[dst_idx] = sdf_bitmap[src_idx];
                        }
                    }
                }

                let scale_factor = font_size / scale;
                glyphs.insert(
                    ch,
                    GlyphInfo {
                        uv_rect: vec4(
                            x as f32 / atlas_width as f32,
                            y as f32 / atlas_height as f32,
                            padded_width as f32 / atlas_width as f32,
                            padded_height as f32 / atlas_height as f32,
                        ),
                        size: vec2(
                            padded_width as f32 * scale_factor,
                            padded_height as f32 * scale_factor,
                        ),
                        bearing: vec2(
                            (metrics.xmin as f32 - SDF_PADDING as f32) * scale_factor,
                            (metrics.ymin as f32 + metrics.height as f32 + SDF_PADDING as f32)
                                * scale_factor,
                        ),
                        advance: metrics.advance_width * scale_factor,
                    },
                );
            }
        } else if ch == ' ' {
            let space_width = font_size * 0.3;
            glyphs.insert(
                ch,
                GlyphInfo {
                    uv_rect: vec4(0.0, 0.0, 0.0, 0.0),
                    size: vec2(0.0, 0.0),
                    bearing: vec2(0.0, 0.0),
                    advance: space_width,
                },
            );
        }
    }

    let size = wgpu::Extent3d {
        width: atlas_width,
        height: atlas_height,
        depth_or_array_layers: 1,
    };

    let texture = device.create_texture(&wgpu::TextureDescriptor {
        label: Some("Font Atlas SDF"),
        size,
        mip_level_count: 1,
        sample_count: 1,
        dimension: wgpu::TextureDimension::D2,
        format: wgpu::TextureFormat::R8Unorm,
        usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
        view_formats: &[],
    });

    queue.write_texture(
        wgpu::TexelCopyTextureInfo {
            aspect: wgpu::TextureAspect::All,
            texture: &texture,
            mip_level: 0,
            origin: wgpu::Origin3d::ZERO,
        },
        &atlas_data,
        wgpu::TexelCopyBufferLayout {
            offset: 0,
            bytes_per_row: Some(atlas_width),
            rows_per_image: Some(atlas_height),
        },
        size,
    );

    let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());

    Ok(FontAtlas {
        texture,
        texture_view,
        texture_index: 0,
        glyphs,
        width: atlas_width,
        height: atlas_height,
        font_size,
        sdf_range: SDF_SPREAD,
    })
}

pub const DEFAULT_FONT_DATA: &[u8] = include_bytes!("fonts/Roboto-Regular.ttf");
