use glam::{Mat4, Vec3};

use crate::components::dialog::DialogBubbleComponent;
use crate::render::billboard::{BillboardDrawCall, BillboardVertex, BubbleUniforms};
use crate::render::{with_font_atlas, TextVertex};
use crate::utility::transform::Transform;
use crate::world::Storage;

const MAX_BUBBLE_WIDTH: f32 = 8.0;
const MIN_BUBBLE_WIDTH: f32 = 0.5;
const TAIL_HEIGHT: f32 = 0.242;
const CORNER_R: f32 = 0.18;
const BORDER_W: f32 = 0.06;
const HEIGHT_OFFSET: f32 = 0.0;

const FILL_COLOR: [f32; 4] = [0.05, 0.05, 0.05, 1.0];
const BORDER_COLOR: [f32; 4] = [1.0, 1.0, 1.0, 1.0];

const CHAR_WORLD_HEIGHT: f32 = 0.84;
const TEXT_PADDING: f32 = 0.06;
const LINE_SPACING: f32 = 0.01;

pub fn dialog_bubble_render_system(
    transforms: &Storage<Transform>,
    dialog_bubbles: &Storage<DialogBubbleComponent>,
    bubble_tags: &Storage<()>,
    camera_pos: Vec3,
    view_proj: Mat4,
) -> (Vec<BillboardDrawCall>, Vec<TextVertex>)
{
    let mut calls = Vec::new();
    let mut all_text: Vec<TextVertex> = Vec::new();

    for bubble_entity in bubble_tags.all()
    {
        let bubble_pos = match transforms.with(bubble_entity, |t| t.position)
        {
            Some(p) => p,
            None => continue,
        };

        let text = match dialog_bubbles.with(bubble_entity, |b| b.current_text.clone())
        {
            Some(t) => t,
            None => continue,
        };

        let (draw_call, text_verts) = with_font_atlas(|atlas| {
            let char_w = CHAR_WORLD_HEIGHT * atlas.aspect();

            // First-pass: measure text at max width using a conservative padding estimate.
            // BORDER_W is applied against body_height (the smaller dimension); TAIL_HEIGHT
            // is a safe lower-bound for body_height, giving a slight over-estimate of padding.
            let approx_pad = BORDER_W * TAIL_HEIGHT + TEXT_PADDING;
            let max_inner_half_w = ((MAX_BUBBLE_WIDTH - 2.0 * approx_pad) * 0.5).max(char_w);
            let (n_lines, max_line_chars) =
                atlas.measure_text(&text, CHAR_WORLD_HEIGHT, max_inner_half_w);

            // Compute body height analytically.
            // border_world = BORDER_W * body_height  (body is the smaller dimension)
            // body_height = text_h + 2 * (border_world + TEXT_PADDING)
            // -> body_height * (1 - 2*BORDER_W) = text_h + 2*TEXT_PADDING
            let text_h = n_lines as f32 * CHAR_WORLD_HEIGHT
                + n_lines.saturating_sub(1) as f32 * LINE_SPACING;
            let body_height = (text_h + 2.0 * TEXT_PADDING) / (1.0 - 2.0 * BORDER_W);

            // Bubble width: fit the longest line exactly, then clamp to [MIN, MAX].
            let border_world = BORDER_W * body_height;
            let needed_width = max_line_chars as f32 * char_w + 2.0 * (border_world + TEXT_PADDING);
            let bubble_width = needed_width.clamp(MIN_BUBBLE_WIDTH, MAX_BUBBLE_WIDTH);

            let bubble_height = body_height + TAIL_HEIGHT;
            let body_frac = body_height / bubble_height;

            // Billboard orientation
            let anchor = bubble_pos;

            let to_camera = camera_pos - anchor;
            let forward = if to_camera.length_squared() > 1e-6
            {
                to_camera.normalize()
            }
            else
            {
                Vec3::Z
            };

            let up_ref = Vec3::Y;
            let right = if forward.abs().dot(up_ref) > 0.99
            {
                Vec3::X
            }
            else
            {
                up_ref.cross(forward).normalize()
            };
            let up = forward.cross(right).normalize();

            let half_w = bubble_width * 0.5;

            let tl = anchor - right * half_w + up * body_height;
            let tr = anchor + right * half_w + up * body_height;
            let br = anchor + right * half_w - up * TAIL_HEIGHT;
            let bl = anchor - right * half_w - up * TAIL_HEIGHT;

            let vertices = [
                BillboardVertex {
                    position: tl.to_array(),
                    uv: [0.0, 0.0],
                },
                BillboardVertex {
                    position: tr.to_array(),
                    uv: [1.0, 0.0],
                },
                BillboardVertex {
                    position: br.to_array(),
                    uv: [1.0, 1.0],
                },
                BillboardVertex {
                    position: bl.to_array(),
                    uv: [0.0, 1.0],
                },
            ];

            let uniforms = BubbleUniforms {
                view_proj: view_proj.to_cols_array_2d(),
                size: [bubble_width, bubble_height],
                body_frac,
                corner_r: CORNER_R,
                border_w: BORDER_W,
                _pad1: [0.0; 3],
                fill_color: FILL_COLOR,
                border_color: BORDER_COLOR,
                _pad2: [0.0; 32],
            };

            let inner_half_w = bubble_width * 0.5 - border_world - TEXT_PADDING;
            let inner_top_y = body_height - border_world - TEXT_PADDING;

            let text_verts = atlas.build_bubble_text(
                &text,
                anchor,
                right,
                up,
                inner_half_w,
                inner_top_y,
                CHAR_WORLD_HEIGHT,
                LINE_SPACING,
            );

            (BillboardDrawCall { vertices, uniforms }, text_verts)
        });

        calls.push(draw_call);
        all_text.extend(text_verts);
    }

    (calls, all_text)
}
