use crate::style::ResolvedStyle;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use rustc_hash::FxHasher;

/// Helvetica glyph advance widths in PDF units (1/1000 of em).
/// Based on the Adobe standard Helvetica font metrics (Type1 / BaseFont).
const HELVETICA_WIDTHS: &[(std::ops::RangeInclusive<char>, u16)] = &[
    (' '..=' ', 278),
    ('!'..='!', 278),
    ('"'..='"', 355),
    ('#'..='$', 556),
    ('%'..='%', 889),
    ('&'..='&', 667),
    ('\''..='\'', 191),
    ('('..=')', 333),
    ('*'..='*', 389),
    ('+'..='+', 584),
    (','..='-', 278),
    ('.'..='.', 278),
    ('/'..='/', 278),
    ('0'..='9', 556),
    (':'..=';', 278),
    ('<'..='>', 584),
    ('?'..='?', 611),
    ('@'..='@', 975),
    ('A'..='A', 667),
    ('B'..='B', 667),
    ('C'..='C', 722),
    ('D'..='D', 722),
    ('E'..='E', 667),
    ('F'..='F', 611),
    ('G'..='G', 778),
    ('H'..='H', 722),
    ('I'..='I', 278),
    ('J'..='J', 500),
    ('K'..='K', 667),
    ('L'..='L', 556),
    ('M'..='M', 833),
    ('N'..='N', 722),
    ('O'..='O', 778),
    ('P'..='P', 667),
    ('Q'..='Q', 778),
    ('R'..='R', 722),
    ('S'..='S', 667),
    ('T'..='T', 611),
    ('U'..='U', 722),
    ('V'..='V', 667),
    ('W'..='W', 944),
    ('X'..='Y', 667),
    ('Z'..='Z', 611),
    ('['..='\\', 278),
    (']'..=']', 278),
    ('^'..='^', 469),
    ('_'..='_', 556),
    ('`'..='`', 333),
    ('a'..='a', 556),
    ('b'..='b', 556),
    ('c'..='c', 500),
    ('d'..='e', 556),
    ('f'..='f', 278),
    ('g'..='h', 556),
    ('i'..='j', 222),
    ('k'..='k', 500),
    ('l'..='l', 222),
    ('m'..='m', 833),
    ('n'..='o', 556),
    ('p'..='q', 556),
    ('r'..='r', 333),
    ('s'..='s', 500),
    ('t'..='t', 278),
    ('u'..='u', 556),
    ('v'..='v', 500),
    ('w'..='w', 722),
    ('x'..='z', 500),
    ('{'..='{', 334),
    ('|'..='|', 260),
    ('}'..='}', 334),
    ('~'..='~', 584),
];

/// Measure text using Helvetica proportional glyph widths.
pub fn measure_helvetica(text: &str, font_size: f32) -> f32 {
    let mut total = 0u32;
    for ch in text.chars() {
        let mut glyph_width = 500; // default for unknown chars
        for (range, width) in HELVETICA_WIDTHS {
            if range.contains(&ch) {
                glyph_width = *width as u32;
                break;
            }
        }
        total += glyph_width;
    }
    total as f32 * font_size / 1000.0
}

#[derive(Debug, Clone, PartialEq)]
pub struct NormalizedInline {
    pub text: String,
    pub style: ResolvedStyle,
}

#[derive(Debug, Clone, PartialEq)]
pub struct InlineRun {
    pub text: String,
    pub width: f32,
    pub style: ResolvedStyle,
}

#[derive(Debug, Clone, PartialEq)]
pub struct InlineLine {
    pub runs: Vec<InlineRun>,
    pub width: f32,
    pub line_height: f32,
}

fn hash_str(s: &str) -> u64 {
    let mut hasher = FxHasher::default();
    s.hash(&mut hasher);
    hasher.finish()
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct MeasureKey {
    text_hash: u64,
    font_key_hash: u64,
    font_size_bits: u32,
}

#[derive(Debug, Clone)]
pub struct TextMeasurer {
    cache: HashMap<MeasureKey, f32>,
    #[allow(dead_code)]
    wrap_cache: HashMap<WrapKey, Vec<InlineLine>>,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct WrapKey {
    /// Hash of all inline texts concatenated
    text_hash: u64,
    /// Hash of all inline styles (font_family, font_size, bold, italics)
    style_hash: u64,
    /// Available width as bits (for exact float comparison)
    width_bits: u32,
}

#[allow(dead_code)]
fn hash_inline_style(style: &ResolvedStyle) -> u64 {
    let mut hasher = FxHasher::default();
    style.font_size.to_bits().hash(&mut hasher);
    style.bold.hash(&mut hasher);
    style.italics.hash(&mut hasher);
    style.font_family.hash(&mut hasher);
    style.line_height.to_bits().hash(&mut hasher);
    hasher.finish()
}

impl TextMeasurer {
    pub fn new() -> Self {
        Self { cache: HashMap::new(), wrap_cache: HashMap::new() }
    }

    pub fn measure(&mut self, text: &str, font_size: f32) -> f32 {
        let key = MeasureKey {
            text_hash: hash_str(text),
            font_key_hash: 0,
            font_size_bits: font_size.to_bits(),
        };
        if let Some(width) = self.cache.get(&key) {
            return *width;
        }
        let width = measure_helvetica(text, font_size);
        self.cache.insert(key, width);
        width
    }

    pub fn measure_with_font(&mut self, text: &str, font_family: Option<&str>, font_size: f32, fonts: &crate::font::FontRegistry) -> crate::Result<f32> {
        let key = MeasureKey {
            text_hash: hash_str(text),
            font_key_hash: font_family.map(hash_str).unwrap_or(0),
            font_size_bits: font_size.to_bits(),
        };
        if let Some(width) = self.cache.get(&key) {
            return Ok(*width);
        }
        let width = if let Some(family) = font_family {
            fonts.measure(family, text, font_size)?
        } else {
            measure_helvetica(text, font_size)
        };
        self.cache.insert(key, width);
        Ok(width)
    }
}

impl Default for TextMeasurer {
    fn default() -> Self {
        Self::new()
    }
}

pub fn measure_text_width(text: &str, style: ResolvedStyle) -> f32 {
    let effective_size = effective_font_size(&style);
    text.chars().count() as f32 * effective_size * 0.5
}

pub fn line_height(style: &ResolvedStyle) -> f32 {
    style.font_size * style.line_height * 1.4
}

/// Calculate effective font size considering sup/sub
pub fn effective_font_size(style: &ResolvedStyle) -> f32 {
    if style.sup || style.sub {
        style.font_size * 0.7
    } else {
        style.font_size
    }
}

pub fn wrap_text(text: &str, available_width: f32, style: ResolvedStyle) -> Vec<String> {
    if text.is_empty() {
        return vec![String::new()];
    }

    wrap_inlines(&[NormalizedInline { text: text.to_string(), style }], available_width, &mut TextMeasurer::new())
        .into_iter()
        .map(|line| line.runs.into_iter().map(|run| run.text).collect::<Vec<_>>().join("").trim_end().to_string())
        .collect()
}

pub fn wrap_inlines(inlines: &[NormalizedInline], available_width: f32, measurer: &mut TextMeasurer) -> Vec<InlineLine> {
    // If any inline has no_wrap, treat entire text as single line
    let should_wrap = !inlines.iter().any(|i| i.style.no_wrap);
    if !should_wrap {
        let mut runs = Vec::new();
        let mut width = 0.0;
        let mut height: f32 = 0.0;
        for inline in inlines {
            let fs = effective_font_size(&inline.style);
            let w = measurer.measure(&inline.text, fs);
            height = height.max(line_height(&inline.style));
            runs.push(InlineRun { text: inline.text.clone(), width: w, style: inline.style.clone() });
            width += w;
        }
        return vec![InlineLine { runs, width, line_height: height }];
    }

    let mut lines = Vec::new();
    let mut merged_runs: Vec<InlineRun> = Vec::new();
    let mut width = 0.0;
    let mut height = 0.0;

    for inline in inlines {
        let fs = effective_font_size(&inline.style);
        let is_break_all = inline.style.word_break.as_deref() == Some("break-all");

        if is_break_all {
            // word-break: break-all - break at every character
            for ch in inline.text.chars() {
                let ch_str = ch.to_string();
                let ch_w = measurer.measure(&ch_str, fs);

                if width + ch_w > available_width && !merged_runs.is_empty() {
                    lines.push(InlineLine { runs: merged_runs, width, line_height: height });
                    merged_runs = Vec::new();
                    width = 0.0;
                    height = 0.0;
                }

                height = height.max(line_height(&inline.style));
                width += ch_w;
                if let Some(last) = merged_runs.last_mut() {
                    if last.style == inline.style {
                        last.text.push_str(&ch_str);
                        last.width += ch_w;
                        continue;
                    }
                }
                merged_runs.push(InlineRun { text: ch_str, width: ch_w, style: inline.style.clone() });
            }
        } else {
            // Normal word wrapping
            let parts: Vec<&str> = split_preserving_spaces(&inline.text);
            for (pi, part) in parts.iter().enumerate() {
                let part_width = measurer.measure(part, fs);
                let is_whitespace = part.chars().all(|c| c.is_whitespace());

                // Lookahead: if this is a trailing space before a wrap, skip it
                // (don't count trailing whitespace toward line width)
                if is_whitespace && pi + 1 < parts.len() {
                    let next_part_width = measurer.measure(parts[pi + 1], fs);
                    if width + part_width + next_part_width > available_width && !merged_runs.is_empty() {
                        // The next word won't fit even with this space — wrap now, skip the space
                        lines.push(InlineLine { runs: merged_runs, width, line_height: height });
                        merged_runs = Vec::new();
                        width = 0.0;
                        height = 0.0;
                        continue;
                    }
                }

                // 当前行有内容且放不下 → 换行
                if width + part_width > available_width && !merged_runs.is_empty() {
                    // Strip trailing whitespace before emitting
                    strip_trailing_whitespace(&mut merged_runs, &mut width);
                    lines.push(InlineLine { runs: merged_runs, width, line_height: height });
                    merged_runs = Vec::new();
                    width = 0.0;
                    height = 0.0;
                }

                // #2846: 单个 part 超过行宽 → 逐字符断开
                if part_width > available_width {
                    height = height.max(line_height(&inline.style));
                    for ch in part.chars() {
                        let ch_str = ch.to_string();
                        let ch_w = measurer.measure(&ch_str, fs);
                        if width + ch_w > available_width && !merged_runs.is_empty() {
                            lines.push(InlineLine { runs: merged_runs, width, line_height: height });
                            merged_runs = Vec::new();
                            width = 0.0;
                        }
                        width += ch_w;
                        if let Some(last) = merged_runs.last_mut() {
                            if last.style == inline.style {
                                last.text.push_str(&ch_str);
                                last.width += ch_w;
                                continue;
                            }
                        }
                        merged_runs.push(InlineRun { text: ch_str, width: ch_w, style: inline.style.clone() });
                    }
                    continue;
                }

                height = height.max(line_height(&inline.style));
                width += part_width;
                if let Some(last) = merged_runs.last_mut() {
                    if last.style == inline.style {
                        last.text.push_str(&part);
                        last.width += part_width;
                        continue;
                    }
                }
                merged_runs.push(InlineRun { text: part.to_string(), width: part_width, style: inline.style.clone() });
            }
        }
    }

    if !merged_runs.is_empty() {
        lines.push(InlineLine { runs: merged_runs, width, line_height: height });
    }

    if lines.is_empty() {
        lines.push(InlineLine { runs: Vec::new(), width: 0.0, line_height: 0.0 });
    }

    lines
}

pub fn wrap_inlines_with_fonts(
    inlines: &[NormalizedInline],
    available_width: f32,
    measurer: &mut TextMeasurer,
    fonts: &crate::font::FontRegistry,
) -> crate::Result<Vec<InlineLine>> {
    // If any inline has no_wrap, treat entire text as single line
    let should_wrap = !inlines.iter().any(|i| i.style.no_wrap);
    if !should_wrap {
        let mut runs = Vec::new();
        let mut width = 0.0;
        let mut height: f32 = 0.0;
        for inline in inlines {
            let fs = effective_font_size(&inline.style);
            let w = measurer.measure_with_font(&inline.text, inline.style.font_family.as_deref(), fs, fonts)?;
            height = height.max(line_height(&inline.style));
            runs.push(InlineRun { text: inline.text.clone(), width: w, style: inline.style.clone() });
            width += w;
        }
        return Ok(vec![InlineLine { runs, width, line_height: height }]);
    }

    let mut lines = Vec::new();
    let mut merged_runs: Vec<InlineRun> = Vec::new();
    let mut width = 0.0;
    let mut height = 0.0;

    for inline in inlines {
        let fs = effective_font_size(&inline.style);
        let is_break_all = inline.style.word_break.as_deref() == Some("break-all");

        if is_break_all {
            // word-break: break-all - break at every character
            for ch in inline.text.chars() {
                let ch_str = ch.to_string();
                let ch_w = measurer.measure_with_font(&ch_str, inline.style.font_family.as_deref(), fs, fonts)?;

                if width + ch_w > available_width && !merged_runs.is_empty() {
                    lines.push(InlineLine { runs: merged_runs, width, line_height: height });
                    merged_runs = Vec::new();
                    width = 0.0;
                    height = 0.0;
                }

                height = height.max(line_height(&inline.style));
                width += ch_w;
                if let Some(last) = merged_runs.last_mut() {
                    if last.style == inline.style {
                        last.text.push_str(&ch_str);
                        last.width += ch_w;
                        continue;
                    }
                }
                merged_runs.push(InlineRun { text: ch_str, width: ch_w, style: inline.style.clone() });
            }
        } else {
            // Normal word wrapping
            let parts: Vec<&str> = split_preserving_spaces(&inline.text);
            for (pi, part) in parts.iter().enumerate() {
                let part_width = measurer.measure_with_font(part, inline.style.font_family.as_deref(), fs, fonts)?;
                let is_whitespace = part.chars().all(|c| c.is_whitespace());

                // Lookahead: if this is a trailing space before a wrap, skip it
                if is_whitespace && pi + 1 < parts.len() {
                    let next_part_width = measurer.measure_with_font(parts[pi + 1], inline.style.font_family.as_deref(), fs, fonts)?;
                    if width + part_width + next_part_width > available_width && !merged_runs.is_empty() {
                        lines.push(InlineLine { runs: merged_runs, width, line_height: height });
                        merged_runs = Vec::new();
                        width = 0.0;
                        height = 0.0;
                        continue;
                    }
                }

                // 当前行有内容且放不下 → 换行
                if width + part_width > available_width && !merged_runs.is_empty() {
                    strip_trailing_whitespace(&mut merged_runs, &mut width);
                    lines.push(InlineLine { runs: merged_runs, width, line_height: height });
                    merged_runs = Vec::new();
                    width = 0.0;
                    height = 0.0;
                }

                // #2846: 单个 part 超过行宽 → 逐字符断开
                if part_width > available_width {
                    height = height.max(line_height(&inline.style));
                    let mut ch_start = 0;
                    for (ch_end, ch) in part.char_indices() {
                        let ch_str = &part[ch_start..ch_end + ch.len_utf8()];
                        let ch_w = measurer.measure_with_font(ch_str, inline.style.font_family.as_deref(), fs, fonts)?;
                        if width + ch_w > available_width && !merged_runs.is_empty() {
                            lines.push(InlineLine { runs: merged_runs, width, line_height: height });
                            merged_runs = Vec::new();
                            width = 0.0;
                        }
                        width += ch_w;
                        if let Some(last) = merged_runs.last_mut() {
                            if last.style == inline.style {
                                last.text.push_str(ch_str);
                                last.width += ch_w;
                                ch_start = ch_end + ch.len_utf8();
                                continue;
                            }
                        }
                        merged_runs.push(InlineRun { text: ch_str.to_string(), width: ch_w, style: inline.style.clone() });
                        ch_start = ch_end + ch.len_utf8();
                    }
                    continue;
                }

                height = height.max(line_height(&inline.style));
                width += part_width;
                if let Some(last) = merged_runs.last_mut() {
                    if last.style == inline.style {
                        last.text.push_str(&part);
                        last.width += part_width;
                        continue;
                    }
                }
                merged_runs.push(InlineRun { text: part.to_string(), width: part_width, style: inline.style.clone() });
            }
        }
    }

    if !merged_runs.is_empty() {
        lines.push(InlineLine { runs: merged_runs, width, line_height: height });
    }

    if lines.is_empty() {
        lines.push(InlineLine { runs: Vec::new(), width: 0.0, line_height: 0.0 });
    }

    Ok(lines)
}

/// Strip trailing whitespace from a line's runs, adjusting width accordingly.
fn strip_trailing_whitespace(runs: &mut Vec<InlineRun>, width: &mut f32) {
    while let Some(last) = runs.last_mut() {
        let trimmed = last.text.trim_end();
        if trimmed.is_empty() {
            *width -= last.width;
            runs.pop();
        } else if trimmed.len() < last.text.len() {
            let removed_chars: String = last.text[trimmed.len()..].to_string();
            let removed_width: f32 = removed_chars.chars().map(|_c| {
                // Approximate: use the same ratio
                last.width * (removed_chars.len() as f32 / last.text.len() as f32)
            }).sum();
            *width -= removed_width;
            last.text = trimmed.to_string();
            last.width -= removed_width;
            break;
        } else {
            break;
        }
    }
}

fn split_preserving_spaces(text: &str) -> Vec<&str> {
    let mut parts = Vec::new();
    let mut start = 0;
    let mut current_is_whitespace = None;

    for (i, ch) in text.char_indices() {
        let is_whitespace = ch.is_whitespace();
        if let Some(prev_ws) = current_is_whitespace {
            if prev_ws != is_whitespace {
                if start < i {
                    parts.push(&text[start..i]);
                }
                start = i;
            }
        }
        current_is_whitespace = Some(is_whitespace);
    }

    if start < text.len() {
        parts.push(&text[start..]);
    }

    parts
}