use ab_glyph::{Font, FontVec, PxScale, ScaleFont};
use glam::Vec3;

use crate::paths;

use super::text_pipeline::TextVertex;

const ATLAS_COLS: u32 = 16;
const ATLAS_ROWS: u32 = 8;
const FIRST_CHAR: u32 = 32;
const LAST_CHAR: u32 = 126;
const NUM_GLYPHS: u32 = LAST_CHAR - FIRST_CHAR + 1;
const FONT_PX: f32 = 16.0;

struct GlyphMetrics
{
    uv_min: [f32; 2],
    uv_max: [f32; 2],
}

pub struct FontAtlas
{
    pub texture_view: wgpu::TextureView,
    pub sampler: wgpu::Sampler,
    glyphs: Vec<GlyphMetrics>,
    pub cell_w: f32,
    pub cell_h: f32,
}

impl FontAtlas
{
    pub fn load(device: &wgpu::Device, queue: &wgpu::Queue) -> Self
    {
        let path = paths::fonts::departure_mono();
        let data = std::fs::read(&path).unwrap_or_else(|_| panic!("Failed to read font: {path}"));
        let font = FontVec::try_from_vec(data).expect("Failed to parse font");

        let scale = PxScale::from(FONT_PX);
        let scaled = font.as_scaled(scale);

        let ascent = scaled.ascent();
        let descent = scaled.descent();
        let cell_h = (ascent - descent).ceil() as u32;
        let cell_w = scaled.h_advance(font.glyph_id(' ')).ceil() as u32;

        let atlas_w = ATLAS_COLS * cell_w;
        let atlas_h = ATLAS_ROWS * cell_h;

        let mut pixels = vec![0u8; (atlas_w * atlas_h) as usize];
        let mut glyphs = Vec::with_capacity(NUM_GLYPHS as usize);

        for idx in 0..NUM_GLYPHS
        {
            let ch = char::from_u32(FIRST_CHAR + idx).unwrap_or(' ');
            let col = idx % ATLAS_COLS;
            let row = idx / ATLAS_COLS;
            let cell_x = col * cell_w;
            let cell_y = row * cell_h;

            glyphs.push(GlyphMetrics {
                uv_min: [cell_x as f32 / atlas_w as f32, cell_y as f32 / atlas_h as f32],
                uv_max: [
                    (cell_x + cell_w) as f32 / atlas_w as f32,
                    (cell_y + cell_h) as f32 / atlas_h as f32,
                ],
            });

            let glyph_id = font.glyph_id(ch);
            let glyph = glyph_id.with_scale_and_position(
                scale,
                ab_glyph::point(cell_x as f32, cell_y as f32 + ascent),
            );

            if let Some(outlined) = font.outline_glyph(glyph)
            {
                let bounds = outlined.px_bounds();
                outlined.draw(|x, y, coverage| {
                    let abs_x = bounds.min.x.floor() as i32 + x as i32;
                    let abs_y = bounds.min.y.floor() as i32 + y as i32;
                    if abs_x >= 0 && abs_y >= 0
                    {
                        let ax = abs_x as u32;
                        let ay = abs_y as u32;
                        if ax < atlas_w && ay < atlas_h
                        {
                            let i = (ay * atlas_w + ax) as usize;
                            let v = (coverage * 255.0 + 0.5) as u8;
                            pixels[i] = pixels[i].max(v);
                        }
                    }
                });
            }
        }

        let extent = wgpu::Extent3d {
            width: atlas_w,
            height: atlas_h,
            depth_or_array_layers: 1,
        };
        let texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Font Atlas"),
            size: extent,
            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 {
                texture: &texture,
                mip_level: 0,
                origin: wgpu::Origin3d::ZERO,
                aspect: wgpu::TextureAspect::All,
            },
            &pixels,
            wgpu::TexelCopyBufferLayout {
                offset: 0,
                bytes_per_row: Some(atlas_w),
                rows_per_image: Some(atlas_h),
            },
            extent,
        );

        let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("Font Atlas Sampler"),
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Nearest,
            min_filter: wgpu::FilterMode::Nearest,
            ..Default::default()
        });

        Self {
            texture_view,
            sampler,
            glyphs,
            cell_w: cell_w as f32,
            cell_h: cell_h as f32,
        }
    }

    pub fn aspect(&self) -> f32
    {
        self.cell_w / self.cell_h
    }

    /// Build billboard-space text quads for a single dialog bubble.
    ///
    /// `anchor`      – world-space centre of the bubble body  
    /// `right`/`up`  – billboard orientation vectors  
    /// `inner_half_w` – half-width of the text area in world units  
    /// `inner_top_y`  – y-offset (up-axis) of the top edge of the text area  
    /// `char_world_h` – world-space height of one character cell  
    /// `line_spacing` – extra vertical gap between lines in world units  
    pub fn build_bubble_text(
        &self,
        text: &str,
        anchor: Vec3,
        right: Vec3,
        up: Vec3,
        inner_half_w: f32,
        inner_top_y: f32,
        char_world_h: f32,
        line_spacing: f32,
    ) -> Vec<TextVertex>
    {
        let char_w = char_world_h * self.aspect();
        let chars_per_line = ((inner_half_w * 2.0) / char_w).floor() as usize;

        if chars_per_line == 0
        {
            return Vec::new();
        }

        let lines = word_wrap(text, chars_per_line);
        let half_char_h = char_world_h * 0.5;
        let half_char_w = char_w * 0.5;
        let text_left = -inner_half_w;

        let mut verts = Vec::new();

        for (row, line) in lines.iter().enumerate()
        {
            let y = inner_top_y - half_char_h - row as f32 * (char_world_h + line_spacing);
            if y < -inner_top_y + half_char_h
            {
                break;
            }

            for (col, ch) in line.chars().enumerate()
            {
                if ch == ' '
                {
                    continue;
                }

                let code = ch as u32;
                if code < FIRST_CHAR || code > LAST_CHAR
                {
                    continue;
                }
                let g = &self.glyphs[(code - FIRST_CHAR) as usize];

                let x = text_left + half_char_w + col as f32 * char_w;
                let centre = anchor + right * x + up * y;

                let tl = centre - right * half_char_w + up * half_char_h;
                let tr = centre + right * half_char_w + up * half_char_h;
                let br = centre + right * half_char_w - up * half_char_h;
                let bl = centre - right * half_char_w - up * half_char_h;

                verts.extend_from_slice(&[
                    TextVertex { position: tl.to_array(), uv: g.uv_min },
                    TextVertex {
                        position: tr.to_array(),
                        uv: [g.uv_max[0], g.uv_min[1]],
                    },
                    TextVertex { position: br.to_array(), uv: g.uv_max },
                    TextVertex {
                        position: bl.to_array(),
                        uv: [g.uv_min[0], g.uv_max[1]],
                    },
                ]);
            }
        }

        verts
    }
}

fn word_wrap(text: &str, chars_per_line: usize) -> Vec<String>
{
    let mut lines: Vec<String> = Vec::new();
    let mut current = String::new();

    for word in text.split_whitespace()
    {
        if current.is_empty()
        {
            current.push_str(word);
        }
        else if current.len() + 1 + word.len() <= chars_per_line
        {
            current.push(' ');
            current.push_str(word);
        }
        else
        {
            lines.push(current.clone());
            current = word.to_string();
        }
    }

    if !current.is_empty()
    {
        lines.push(current);
    }

    lines
}
