use crate::ecs::text::components::{
    TextAlignment, TextMesh, TextProperties, TextVertex, VerticalAlignment,
};
use crate::ecs::text::resources::FontAtlasData;
use nalgebra_glm::vec2;

struct LineMeshParams<'a> {
    line: &'a str,
    atlas: &'a FontAtlasData,
    x_offset: f32,
    y_offset: f32,
    scale: f32,
    letter_spacing: f32,
    vertices: &'a mut Vec<TextVertex>,
    indices: &'a mut Vec<u32>,
}

pub fn generate_text_mesh(
    text: &str,
    atlas: &FontAtlasData,
    properties: &TextProperties,
) -> TextMesh {
    let mut vertices = Vec::new();
    let mut indices = Vec::new();

    const PIXELS_TO_WORLD: f32 = 0.01;

    let scale = properties.font_size / atlas.font_size * PIXELS_TO_WORLD;

    let lines = layout_text(text);
    let line_height = properties.font_size * properties.line_height * PIXELS_TO_WORLD;
    let total_height = lines.len() as f32 * line_height;

    let max_width = lines
        .iter()
        .map(|line| calculate_line_width(line, atlas, scale, properties.letter_spacing))
        .fold(0.0f32, |a, b| a.max(b));

    let mut y_offset = match properties.vertical_alignment {
        VerticalAlignment::Top => 0.0,
        VerticalAlignment::Middle => -total_height * 0.5,
        VerticalAlignment::Bottom => -total_height,
        VerticalAlignment::Baseline => -properties.font_size * 0.8 * PIXELS_TO_WORLD,
    };

    for line in &lines {
        let line_width = calculate_line_width(line, atlas, scale, properties.letter_spacing);
        let x_offset = match properties.alignment {
            TextAlignment::Left => 0.0,
            TextAlignment::Center => -line_width * 0.5,
            TextAlignment::Right => -line_width,
        };

        generate_line_mesh(LineMeshParams {
            line,
            atlas,
            x_offset,
            y_offset,
            scale,
            letter_spacing: properties.letter_spacing,
            vertices: &mut vertices,
            indices: &mut indices,
        });

        y_offset -= line_height;
    }

    TextMesh {
        vertices,
        indices,
        bounds: vec2(max_width, total_height),
    }
}

fn layout_text(text: &str) -> Vec<String> {
    text.lines().map(|s| s.to_string()).collect()
}

fn calculate_line_width(line: &str, atlas: &FontAtlasData, scale: f32, letter_spacing: f32) -> f32 {
    const PIXELS_TO_WORLD: f32 = 0.01;
    let mut width = 0.0;
    let char_count = line.chars().count();

    for (i, ch) in line.chars().enumerate() {
        if let Some(glyph) = atlas.glyphs.get(&ch) {
            width += glyph.advance * scale;
            if i < char_count - 1 {
                width += letter_spacing * PIXELS_TO_WORLD;
            }
        }
    }

    width
}

fn generate_line_mesh(params: LineMeshParams) {
    let mut cursor_x = params.x_offset;

    for ch in params.line.chars() {
        if let Some(glyph) = params.atlas.glyphs.get(&ch) {
            let vertex_offset = params.vertices.len() as u32;

            let x = cursor_x + glyph.bearing.x * params.scale;
            let w = glyph.size.x * params.scale;
            let h = glyph.size.y * params.scale;

            let top = params.y_offset + glyph.bearing.y * params.scale;
            let bottom = top - h;

            params.vertices.push(TextVertex {
                position: nalgebra_glm::vec3(x, top, 0.0),
                tex_coords: nalgebra_glm::vec2(glyph.uv_rect.x, glyph.uv_rect.y),
            });
            params.vertices.push(TextVertex {
                position: nalgebra_glm::vec3(x + w, top, 0.0),
                tex_coords: nalgebra_glm::vec2(glyph.uv_rect.x + glyph.uv_rect.z, glyph.uv_rect.y),
            });
            params.vertices.push(TextVertex {
                position: nalgebra_glm::vec3(x + w, bottom, 0.0),
                tex_coords: nalgebra_glm::vec2(
                    glyph.uv_rect.x + glyph.uv_rect.z,
                    glyph.uv_rect.y + glyph.uv_rect.w,
                ),
            });
            params.vertices.push(TextVertex {
                position: nalgebra_glm::vec3(x, bottom, 0.0),
                tex_coords: nalgebra_glm::vec2(glyph.uv_rect.x, glyph.uv_rect.y + glyph.uv_rect.w),
            });

            params.indices.extend_from_slice(&[
                vertex_offset,
                vertex_offset + 1,
                vertex_offset + 2,
                vertex_offset,
                vertex_offset + 2,
                vertex_offset + 3,
            ]);

            const PIXELS_TO_WORLD: f32 = 0.01;
            cursor_x += glyph.advance * params.scale + params.letter_spacing * PIXELS_TO_WORLD;
        }
    }
}
