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

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

struct ColoredChar {
    ch: char,
    color: Vec4,
}

fn parse_colored_line(line: &str, default_color: Vec4) -> Vec<ColoredChar> {
    let mut result = Vec::new();
    let mut current_color = default_color;
    let mut chars = line.chars().peekable();

    while let Some(ch) = chars.next() {
        if ch == '\x1b' && chars.peek() == Some(&'[') {
            chars.next();
            let mut sequence = String::new();
            while let Some(&c) = chars.peek() {
                if c == 'm' {
                    chars.next();
                    break;
                }
                sequence.push(chars.next().unwrap());
            }

            if sequence == "0" {
                current_color = default_color;
            } else if let Some(rgb_part) = sequence.strip_prefix("38;2;") {
                let parts: Vec<&str> = rgb_part.split(';').collect();
                if parts.len() >= 3
                    && let (Ok(r), Ok(g), Ok(b)) = (
                        parts[0].parse::<u8>(),
                        parts[1].parse::<u8>(),
                        parts[2].parse::<u8>(),
                    )
                {
                    current_color =
                        Vec4::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0);
                }
            }
            continue;
        }

        result.push(ColoredChar {
            ch,
            color: current_color,
        });
    }

    result
}

fn count_visible_chars(line: &str) -> usize {
    let mut count = 0;
    let mut chars = line.chars().peekable();

    while let Some(ch) = chars.next() {
        if ch == '\x1b' && chars.peek() == Some(&'[') {
            chars.next();
            while let Some(&c) = chars.peek() {
                if c == 'm' {
                    chars.next();
                    break;
                }
                chars.next();
            }
            continue;
        }
        count += 1;
    }

    count
}

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,
                properties.monospace_width,
            )
        })
        .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,
            properties.monospace_width,
        );
        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,
            monospace_width: properties.monospace_width,
            default_color: Vec4::new(1.0, 1.0, 1.0, 1.0),
            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,
    monospace_width: Option<f32>,
) -> f32 {
    const PIXELS_TO_WORLD: f32 = 0.01;
    let mut width = 0.0;
    let visible_count = count_visible_chars(line);

    if let Some(fixed_width) = monospace_width {
        let fixed_advance = fixed_width * PIXELS_TO_WORLD;
        return visible_count as f32 * fixed_advance;
    }

    let colored_chars = parse_colored_line(line, Vec4::new(1.0, 1.0, 1.0, 1.0));
    let char_count = colored_chars.len();

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

    width
}

fn generate_line_mesh(params: LineMeshParams) {
    const PIXELS_TO_WORLD: f32 = 0.01;
    let mut cursor_x = params.x_offset;
    let fixed_advance = params.monospace_width.map(|w| w * PIXELS_TO_WORLD);

    let colored_chars = parse_colored_line(params.line, params.default_color);

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

            let char_center_offset = if let Some(advance) = fixed_advance {
                (advance - glyph.size.x * params.scale) * 0.5
            } else {
                0.0
            };

            let x = cursor_x + glyph.bearing.x * params.scale + char_center_offset;
            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),
                color: colored.color,
            });
            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),
                color: colored.color,
            });
            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,
                ),
                color: colored.color,
            });
            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),
                color: colored.color,
            });

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

            let advance = fixed_advance
                .unwrap_or(glyph.advance * params.scale + params.letter_spacing * PIXELS_TO_WORLD);
            cursor_x += advance;
        }
    }
}
