use std::io::Write;

macro_rules! write_pdf {
    ($buf:expr, $($arg:tt)*) => {
        write!($buf, $($arg)*).unwrap()
    };
}
pub(crate) use write_pdf;

/// Fast float formatting with N decimal places using ryu
pub fn write_f32(buf: &mut Vec<u8>, val: f32, decimals: usize) {
    // Fast path for common values
    if decimals == 2 {
        // Check integer part to avoid ryu overhead for common PDF values
        let bits = val.to_bits();
        // Exact 0.0
        if bits == 0 {
            buf.extend_from_slice(b"0.00");
            return;
        }
        // Exact 0.5
        if bits == 0x3F000000 {
            buf.extend_from_slice(b"0.50");
            return;
        }
        // Exact 1.0
        if bits == 0x3F800000 {
            buf.extend_from_slice(b"1.00");
            return;
        }
        // Exact 2.0
        if bits == 0x40000000 {
            buf.extend_from_slice(b"2.00");
            return;
        }
    }

    let mut buffer = ryu::Buffer::new();
    let s = buffer.format(val);
    // Find decimal point position
    if let Some(dot_pos) = s.find('.') {
        let after_dot = s.len() - dot_pos - 1;
        if after_dot > decimals {
            // Truncate
            buf.extend_from_slice(s[..dot_pos + 1 + decimals].as_bytes());
        } else {
            buf.extend_from_slice(s.as_bytes());
            // Pad with zeros
            for _ in 0..(decimals - after_dot) {
                buf.push(b'0');
            }
        }
    } else {
        buf.extend_from_slice(s.as_bytes());
        buf.push(b'.');
        for _ in 0..decimals {
            buf.push(b'0');
        }
    }
}

#[allow(dead_code)]
pub fn write_i32(buf: &mut Vec<u8>, val: i32) {
    let mut buffer = itoa::Buffer::new();
    buf.extend_from_slice(buffer.format(val).as_bytes());
}

#[allow(dead_code)]
pub fn escape_pdf_text(text: &str) -> String {
    text.replace('\\', "\\\\")
        .replace('(', "\\(")
        .replace(')', "\\)")
}

pub fn escape_pdf_text_to(buf: &mut Vec<u8>, text: &str) {
    for b in text.bytes() {
        match b {
            b'\\' => buf.extend_from_slice(b"\\\\"),
            b'(' => buf.extend_from_slice(b"\\("),
            b')' => buf.extend_from_slice(b"\\)"),
            other => buf.push(other),
        }
    }
}

pub fn escape_pdf_uri(text: &str) -> String {
    let mut out = String::with_capacity(text.len());
    for b in text.bytes() {
        match b {
            b'\\' => out.push_str("\\\\"),
            b'(' => out.push_str("\\("),
            b')' => out.push_str("\\)"),
            0x20..=0x7e => out.push(b as char),
            _ => {
                out.push('\\');
                let d1 = (b >> 6) & 0o7;
                let d2 = (b >> 3) & 0o7;
                let d3 = b & 0o7;
                if d1 != 0 { out.push((b'0' + d1) as char); }
                out.push((b'0' + d2) as char);
                out.push((b'0' + d3) as char);
            }
        }
    }
    out
}

pub fn format_width_array(glyphs: &[crate::font_subset::EmbeddedGlyph]) -> String {
    let widths = glyphs.iter()
        .map(|glyph| format!("{} [{}]", glyph.embedded_cid, glyph.width))
        .collect::<Vec<_>>()
        .join(" ");
    format!("/W [{}]", widths)
}

pub fn pdf_fill_color(buf: &mut Vec<u8>, color: &str) {
    if let Some((r, g, b)) = crate::color::parse_color(color) {
        write_pdf!(buf, "{:.2} {:.2} {:.2} rg\n", r, g, b);
    }
}

pub fn pdf_stroke_color(buf: &mut Vec<u8>, color: &str) {
    if let Some((r, g, b)) = crate::color::parse_color(color) {
        write_pdf!(buf, "{:.2} {:.2} {:.2} RG\n", r, g, b);
    }
}
