use crate::color::color_to_pdf_rgb;
use crate::layout::{LayoutRun, LayoutTextItem};
use crate::model::{DecorationStyle, TextDecoration};
use crate::style::ResolvedStyle;
use std::collections::HashMap;
use std::io::Write;

use super::utils::{write_f32, write_pdf};

pub fn render_text_item(
    page_height: f32,
    item: &LayoutTextItem,
    glyph_cache: &HashMap<String, HashMap<char, u16>>,
    custom_font_map: &HashMap<String, String>,
) -> Vec<u8> {
    let mut stream = Vec::new();

    // Calculate justify spacing
    let justify_spacing = if item.is_justify && !item.runs.is_empty() {
        let extra_space = item.available_width - item.width;
        if extra_space > 0.0 {
            let total_text: String = item.runs.iter().map(|r| r.text.as_str()).collect();
            let has_cjk = total_text.chars().any(|c| crate::font::needs_cjk_font(c));
            if has_cjk {
                // CJK: use Tc (character spacing) - distribute among non-whitespace chars
                let char_count = total_text.chars().filter(|c| !c.is_whitespace()).count();
                if char_count > 1 {
                    Some((extra_space / (char_count - 1) as f32, true))
                } else {
                    None
                }
            } else {
                // Latin: use Tw (word spacing) - distribute among word gaps
                let word_count = total_text.split_whitespace().count();
                if word_count > 1 {
                    Some((extra_space / (word_count - 1) as f32, false))
                } else {
                    None
                }
            }
        } else {
            None
        }
    } else {
        None
    };

    // Apply justify spacing
    if let Some((spacing, is_cjk)) = justify_spacing {
        if spacing > 0.0 {
            if is_cjk {
                write_f32(&mut stream, spacing, 4);
                stream.extend_from_slice(b" Tc\n");
            } else {
                write_f32(&mut stream, spacing, 4);
                stream.extend_from_slice(b" Tw\n");
            }
        }
    }

    for run in &item.runs {
        let x = item.x + run.x_offset;
        let y = page_height - item.y - run.font_size;
        stream.extend_from_slice(&render_run(run, x, y, glyph_cache, custom_font_map));
        stream.extend_from_slice(&draw_decoration_at(run, x, y));
    }

    // Reset justify spacing
    if let Some((spacing, is_cjk)) = justify_spacing {
        if spacing > 0.0 {
            if is_cjk {
                stream.extend_from_slice(b"0.00 Tc\n");
            } else {
                stream.extend_from_slice(b"0.00 Tw\n");
            }
        }
    }

    stream
}

fn render_run(
    run: &LayoutRun,
    x: f32,
    y: f32,
    glyph_cache: &HashMap<String, HashMap<char, u16>>,
    custom_font_map: &HashMap<String, String>,
) -> Vec<u8> {
    let mut stream = Vec::with_capacity(128);
    render_run_into(&mut stream, run, x, y, glyph_cache, custom_font_map);
    stream
}

pub fn render_run_into(
    stream: &mut Vec<u8>,
    run: &LayoutRun,
    x: f32,
    y: f32,
    glyph_cache: &HashMap<String, HashMap<char, u16>>,
    custom_font_map: &HashMap<String, String>,
) {
    let (effective_font_size, y_offset) = if run.style.sup {
        (run.font_size * 0.7, run.font_size * 0.35)
    } else if run.style.sub {
        (run.font_size * 0.7, -run.font_size * 0.15)
    } else {
        (run.font_size, 0.0)
    };
    let adjusted_y = y + y_offset;

    if let Some(cs) = run.style.character_spacing {
        if cs != 0.0 {
            write_f32(stream, cs, 2);
            stream.extend_from_slice(b" Tc\n");
        }
    }

    if let Some(bg) = &run.style.background {
        if let Some((r, g, b)) = color_to_pdf_rgb(bg) {
            let bg_y = y - run.font_size * 0.15;
            let bg_h = run.font_size * 1.05;
            stream.extend_from_slice(b"q ");
            write_f32(stream, r, 3); stream.push(b' ');
            write_f32(stream, g, 3); stream.push(b' ');
            write_f32(stream, b, 3); stream.extend_from_slice(b" rg ");
            write_f32(stream, x, 2); stream.push(b' ');
            write_f32(stream, bg_y, 2); stream.push(b' ');
            write_f32(stream, run.width, 2); stream.push(b' ');
            write_f32(stream, bg_h, 2); stream.extend_from_slice(b" re f Q\n");
        }
    }

    let has_color = if let Some(c) = &run.style.color {
        if let Some((r, g, b)) = color_to_pdf_rgb(c) {
            write_f32(stream, r, 3); stream.push(b' ');
            write_f32(stream, g, 3); stream.push(b' ');
            write_f32(stream, b, 3); stream.extend_from_slice(b" rg\n");
            true
        } else {
            false
        }
    } else {
        false
    };

    if let Some(family) = &run.style.font_family
        && let Some(cache) = glyph_cache.get(family.as_str())
    {
        let pdf_font_name = custom_font_map
            .get(family.as_str())
            .cloned()
            .unwrap_or_else(|| family.replace(' ', ""));
        stream.extend_from_slice(b"BT ");
        stream.extend_from_slice(pdf_font_name.as_bytes());
        stream.extend_from_slice(b" ");
        write_f32(stream, effective_font_size, 2);
        stream.extend_from_slice(b" Tf ");
        write_f32(stream, x, 2); stream.push(b' ');
        write_f32(stream, adjusted_y, 2);
        stream.extend_from_slice(b" Td <");
        // Write hex directly to stream - no intermediate String
        const HEX: &[u8; 16] = b"0123456789ABCDEF";
        for ch in run.text.chars() {
            if let Some(gid) = cache.get(&ch) {
                let v = *gid as u32;
                stream.push(HEX[((v >> 12) & 0xF) as usize]);
                stream.push(HEX[((v >> 8) & 0xF) as usize]);
                stream.push(HEX[((v >> 4) & 0xF) as usize]);
                stream.push(HEX[(v & 0xF) as usize]);
            }
        }
        stream.extend_from_slice(b"> Tj ET\n");
    } else {
        let font = font_name(&run.style);
        stream.extend_from_slice(b"BT /");
        stream.extend_from_slice(font.as_bytes());
        stream.extend_from_slice(b" ");
        write_f32(stream, effective_font_size, 2);
        stream.extend_from_slice(b" Tf ");
        write_f32(stream, x, 2); stream.push(b' ');
        write_f32(stream, adjusted_y, 2);
        stream.extend_from_slice(b" Td (");
        // Write escaped text directly - no intermediate String
        for ch in run.text.chars() {
            if crate::font::needs_cjk_font(ch) {
                stream.push(b'?');
            } else {
                match ch {
                    '\\' => stream.extend_from_slice(b"\\\\"),
                    '(' => stream.extend_from_slice(b"\\("),
                    ')' => stream.extend_from_slice(b"\\)"),
                    c => {
                        let mut buf = [0u8; 4];
                        stream.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
                    }
                }
            }
        }
        stream.extend_from_slice(b") Tj ET\n");
    }

    if has_color {
        stream.extend_from_slice(b"0.000 0.000 0.000 rg\n");
    }

    if run.style.character_spacing.is_some() && run.style.character_spacing.unwrap() != 0.0 {
        stream.extend_from_slice(b"0.00 Tc\n");
    }
}

pub fn font_name(style: &ResolvedStyle) -> &'static str {
    match (style.bold, style.italics) {
        (false, false) => "F1",
        (true, false) => "F2",
        (false, true) => "F3",
        (true, true) => "F4",
    }
}

fn draw_decoration_at(run: &LayoutRun, x1: f32, baseline_y: f32) -> Vec<u8> {
    let mut stream = Vec::with_capacity(128);
    draw_decoration_at_into(&mut stream, run, x1, baseline_y);
    stream
}

pub fn draw_decoration_at_into(stream: &mut Vec<u8>, run: &LayoutRun, x1: f32, baseline_y: f32) {
    let x2 = x1 + run.width;
    let y = match run.style.decoration {
        Some(TextDecoration::Underline) => baseline_y - 2.0,
        Some(TextDecoration::Strikethrough) => baseline_y + run.font_size * 0.35,
        Some(TextDecoration::Overline) => baseline_y + run.font_size,
        None => return,
    };

    let style = run.style.decoration_style.unwrap_or(DecorationStyle::Solid);
    let thickness = run.style.decoration_thickness.unwrap_or_else(|| {
        0.5 + ((run.font_size - 8.0).max(0.0) / 2.0 * 0.12)
    });

    if let Some(ref dc) = run.style.decoration_color {
        if let Some((r, g, b)) = color_to_pdf_rgb(dc) {
            write_pdf!(stream, "{:.3} {:.3} {:.3} RG\n", r, g, b);
        }
    }

    match style {
        DecorationStyle::Solid => {
            write_pdf!(stream, "{:.2} w {:.2} {:.2} m {:.2} {:.2} l S\n",
                thickness, x1, y, x2, y);
        }
        DecorationStyle::Double => {
            let gap = 0.5f32.max(thickness).max(run.font_size * 0.15);
            write_pdf!(stream, "{:.2} w {:.2} {:.2} m {:.2} {:.2} l S\n",
                thickness / 2.0, x1, y, x2, y);
            write_pdf!(stream, "{:.2} w {:.2} {:.2} m {:.2} {:.2} l S\n",
                thickness / 2.0, x1, y + gap, x2, y + gap);
        }
        DecorationStyle::Dashed => {
            let dash_len = 3.96;
            let gap_len = 2.84;
            let mut rx = x1;
            write_pdf!(stream, "{:.2} w\n", thickness);
            while rx < x2 {
                let end = (rx + dash_len).min(x2);
                write_pdf!(stream, "{:.2} {:.2} m {:.2} {:.2} l S\n", rx, y, end, y);
                rx += dash_len + gap_len;
            }
        }
        DecorationStyle::Dotted => {
            let dot_spacing = thickness * 3.0;
            let mut rx = x1;
            write_pdf!(stream, "{:.2} w\n", thickness);
            while rx < x2 {
                let end = (rx + thickness).min(x2);
                write_pdf!(stream, "{:.2} {:.2} m {:.2} {:.2} l S\n", rx, y, end, y);
                rx += dot_spacing;
            }
        }
        DecorationStyle::Wavy => {
            let sh = 0.7;
            let sv = 1.0;
            let cycle = sh * 6.0;
            let nb_waves = ((x2 - x1) / cycle).ceil() as i32;
            let mut rwx = x1;
            write_pdf!(stream, "{:.2} w\n", thickness / 3.0);
            write_pdf!(stream, "{:.2} {:.2} m\n", rwx, y);
            for _ in 0..nb_waves {
                write_pdf!(stream, "{:.2} {:.2} {:.2} {:.2} {:.2} {:.2} c\n",
                    rwx + sh, y - sv, rwx + sh * 2.0, y - sv, rwx + sh * 3.0, y);
                write_pdf!(stream, "{:.2} {:.2} {:.2} {:.2} {:.2} {:.2} c\n",
                    rwx + sh * 4.0, y + sv, rwx + sh * 5.0, y + sv, rwx + sh * 6.0, y);
                rwx += cycle;
            }
            stream.extend_from_slice(b"S\n");
        }
    }

    if run.style.decoration_color.is_some() {
        stream.extend_from_slice(b"0 0 0 RG\n");
    }
}
