use core::ops::Range;
use std::sync::Arc;

use parley::fontique::{Blob, Collection, CollectionOptions, SourceCache};
use parley::{
    Alignment, AlignmentOptions, FontContext, FontFamily, GlyphRun, Layout, LayoutContext,
    LineHeight as ParleyLineHeight, PositionedLayoutItem, StyleProperty,
};

use crate::fonts::{MONO_DATA, MONO_FAMILY, SANS_DATA, SANS_FAMILY, family_for, parley_weight};
use crate::{FontFace, FontWeight};

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct GlyphId(u32);

impl GlyphId {
    #[must_use]
    pub const fn new(value: u32) -> Self {
        Self(value)
    }

    #[must_use]
    pub const fn raw(self) -> u32 {
        self.0
    }
}

#[derive(
    Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
#[serde(transparent)]
pub struct SourceByteIndex(usize);

impl SourceByteIndex {
    #[must_use]
    pub const fn new(value: usize) -> Self {
        Self(value)
    }

    #[must_use]
    pub const fn value(self) -> usize {
        self.0
    }
}

#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct MaxWidth(f32);

impl MaxWidth {
    #[must_use]
    pub fn new(width_px: f32) -> Option<Self> {
        if width_px > 0.0 && width_px.is_finite() {
            Some(Self(width_px))
        } else {
            None
        }
    }

    #[must_use]
    pub const fn px(self) -> f32 {
        self.0
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ShapeRequest {
    pub face: FontFace,
    pub size_px: f32,
    pub weight: FontWeight,
    pub line_height_px: f32,
    pub letter_spacing_px: f32,
    pub max_width: Option<MaxWidth>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct ShapedText {
    pub face: FontFace,
    pub font_size_px: f32,
    pub lines: Vec<ShapedLine>,
    pub width_px: f32,
    pub height_px: f32,
}

#[derive(Clone, Debug, PartialEq)]
pub struct ShapedLine {
    pub runs: Vec<ShapedRun>,
    pub baseline_px: f32,
    pub ascent_px: f32,
    pub descent_px: f32,
    pub advance_px: f32,
    pub trailing_whitespace_px: f32,
    pub source_range: Range<SourceByteIndex>,
}

impl ShapedLine {
    #[must_use]
    pub fn visible_advance_px(&self) -> f32 {
        (self.advance_px - self.trailing_whitespace_px).max(0.0)
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct ShapedRun {
    pub glyphs: Vec<ShapedGlyph>,
    pub origin_x_px: f32,
    pub advance_px: f32,
    pub is_rtl: bool,
    pub source_range: Range<SourceByteIndex>,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ShapedGlyph {
    pub id: GlyphId,
    pub x_px: f32,
    pub y_px: f32,
    pub advance_px: f32,
    pub cluster: SourceByteIndex,
}

pub struct Shaper {
    fonts: FontContext,
    layout: LayoutContext<()>,
}

impl Shaper {
    #[must_use]
    pub fn new() -> Self {
        let mut collection = Collection::new(CollectionOptions {
            shared: false,
            system_fonts: false,
        });
        register(&mut collection, SANS_DATA, SANS_FAMILY);
        register(&mut collection, MONO_DATA, MONO_FAMILY);
        Self {
            fonts: FontContext {
                collection,
                source_cache: SourceCache::default(),
            },
            layout: LayoutContext::new(),
        }
    }

    pub fn shape(&mut self, text: &str, request: ShapeRequest) -> ShapedText {
        let family = family_for(request.face);
        let max_advance = request.max_width.map(MaxWidth::px);
        let mut builder = self.layout.ranged_builder(&mut self.fonts, text, 1.0, true);
        builder.push_default(StyleProperty::FontFamily(FontFamily::named(family)));
        builder.push_default(StyleProperty::FontSize(request.size_px));
        builder.push_default(StyleProperty::FontWeight(parley_weight(request.weight)));
        builder.push_default(StyleProperty::Brush(()));
        if request.line_height_px > 0.0 {
            builder.push_default(StyleProperty::LineHeight(ParleyLineHeight::Absolute(
                request.line_height_px,
            )));
        }
        if request.letter_spacing_px != 0.0 {
            builder.push_default(StyleProperty::LetterSpacing(request.letter_spacing_px));
        }
        let mut layout: Layout<()> = builder.build(text);
        layout.break_all_lines(max_advance);
        layout.align(Alignment::Start, AlignmentOptions::default());
        ShapedText::from_layout(request.face, request.size_px, &layout)
    }
}

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

impl core::fmt::Debug for Shaper {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Shaper").finish_non_exhaustive()
    }
}

impl ShapedText {
    fn from_layout(face: FontFace, font_size_px: f32, layout: &Layout<()>) -> Self {
        let lines = layout.lines().map(ShapedLine::from_line).collect();
        Self {
            face,
            font_size_px,
            lines,
            width_px: layout.width().max(0.0),
            height_px: layout.height().max(0.0),
        }
    }

    #[must_use]
    pub fn line_count(&self) -> usize {
        self.lines.len()
    }

    #[must_use]
    pub fn glyph_count(&self) -> usize {
        self.lines
            .iter()
            .flat_map(|line| line.runs.iter())
            .map(|run| run.glyphs.len())
            .sum()
    }
}

impl ShapedLine {
    fn from_line(line: parley::Line<'_, ()>) -> Self {
        let metrics = line.metrics();
        let runs = line
            .items()
            .filter_map(|item| match item {
                PositionedLayoutItem::GlyphRun(run) => Some(ShapedRun::from_glyph_run(&run)),
                PositionedLayoutItem::InlineBox(_) => None,
            })
            .collect();
        Self {
            runs,
            baseline_px: metrics.baseline.max(0.0),
            ascent_px: metrics.ascent.max(0.0),
            descent_px: metrics.descent.max(0.0),
            advance_px: metrics.advance.max(0.0),
            trailing_whitespace_px: metrics.trailing_whitespace.max(0.0),
            source_range: byte_range(line.text_range()),
        }
    }
}

impl ShapedRun {
    fn from_glyph_run(run: &GlyphRun<'_, ()>) -> Self {
        let parley_run = run.run();
        let glyphs = parley_run
            .visual_clusters()
            .flat_map(|cluster| {
                let byte = SourceByteIndex::new(cluster.text_range().start);
                cluster.glyphs().map(move |g| (byte, g))
            })
            .scan(0.0_f32, |cursor, (byte, g)| {
                let item = ShapedGlyph {
                    id: GlyphId::new(g.id),
                    x_px: *cursor + g.x,
                    y_px: g.y,
                    advance_px: g.advance.max(0.0),
                    cluster: byte,
                };
                *cursor += g.advance;
                Some(item)
            })
            .collect();
        Self {
            glyphs,
            origin_x_px: run.offset(),
            advance_px: parley_run.advance().max(0.0),
            is_rtl: parley_run.is_rtl(),
            source_range: byte_range(parley_run.text_range()),
        }
    }
}

fn byte_range(r: Range<usize>) -> Range<SourceByteIndex> {
    SourceByteIndex::new(r.start)..SourceByteIndex::new(r.end)
}

fn register(collection: &mut Collection, data: &'static [u8], family: &'static str) {
    let blob = Blob::new(Arc::new(data));
    let registered = collection.register_fonts(blob, None);
    assert!(
        !registered.is_empty(),
        "bundled font `{family}` failed to register; binary asset is broken",
    );
}

#[cfg(test)]
mod tests {
    use super::{
        FontFace, FontWeight, MaxWidth, ShapeRequest, ShapedText, Shaper, SourceByteIndex,
    };

    fn req(face: FontFace, size_px: f32, max_width: Option<MaxWidth>) -> ShapeRequest {
        ShapeRequest {
            face,
            size_px,
            weight: FontWeight::Regular,
            line_height_px: 0.0,
            letter_spacing_px: 0.0,
            max_width,
        }
    }

    fn shape(text: &str, request: ShapeRequest) -> ShapedText {
        Shaper::new().shape(text, request)
    }

    #[test]
    fn max_width_rejects_zero_and_negative_and_non_finite() {
        assert!(MaxWidth::new(0.0).is_none());
        assert!(MaxWidth::new(-1.0).is_none());
        assert!(MaxWidth::new(f32::NAN).is_none());
        assert!(MaxWidth::new(f32::INFINITY).is_none());
        assert!(MaxWidth::new(0.5).is_some());
    }

    #[test]
    fn empty_text_yields_one_empty_line_with_positive_height() {
        let out = shape("", req(FontFace::Sans, 13.0, None));
        assert_eq!(out.line_count(), 1);
        assert_eq!(out.glyph_count(), 0);
        assert!(out.lines[0].runs.is_empty());
        assert!(out.height_px > 0.0);
        assert!(out.width_px.abs() < f32::EPSILON);
    }

    #[test]
    fn shaped_text_carries_request_face_and_size() {
        let out = shape("hi", req(FontFace::Mono, 15.0, None));
        assert_eq!(out.face, FontFace::Mono);
        assert!((out.font_size_px - 15.0).abs() < 0.01);
    }

    #[test]
    fn ascii_run_emits_one_glyph_per_char() {
        let text = "Sketch";
        let out = shape(text, req(FontFace::Sans, 13.0, None));
        assert_eq!(out.line_count(), 1);
        assert_eq!(out.glyph_count(), text.chars().count());
        let line = &out.lines[0];
        assert_eq!(
            line.source_range,
            SourceByteIndex::new(0)..SourceByteIndex::new(text.len()),
        );
        assert_eq!(line.runs.len(), 1);
        assert!(!line.runs[0].is_rtl);
        assert!(out.width_px > 0.0);
    }

    #[test]
    fn ascii_clusters_track_byte_offsets_left_to_right() {
        let out = shape("AB", req(FontFace::Sans, 13.0, None));
        let glyphs = &out.lines[0].runs[0].glyphs;
        assert_eq!(glyphs.len(), 2);
        assert_eq!(glyphs[0].cluster, SourceByteIndex::new(0));
        assert_eq!(glyphs[1].cluster, SourceByteIndex::new(1));
        assert!(glyphs[1].x_px > glyphs[0].x_px);
    }

    #[test]
    fn line_metrics_expose_positive_ascent_and_descent() {
        let out = shape("Ag", req(FontFace::Sans, 13.0, None));
        let line = &out.lines[0];
        assert!(line.ascent_px > 0.0);
        assert!(line.descent_px > 0.0);
        assert!(line.baseline_px > 0.0);
    }

    #[test]
    fn glyph_y_offsets_lie_within_line_metrics() {
        let out = shape("Ag", req(FontFace::Sans, 13.0, None));
        let line = &out.lines[0];
        let max_dy = line.descent_px + 0.5;
        let min_dy = -line.ascent_px - 0.5;
        line.runs
            .iter()
            .flat_map(|r| r.glyphs.iter())
            .for_each(|glyph| {
                let dy = glyph.y_px;
                assert!(
                    dy >= min_dy && dy <= max_dy,
                    "glyph dy {dy} outside [{min_dy}, {max_dy}]",
                );
            });
    }

    #[test]
    fn arabic_run_marks_rtl() {
        let out = shape("مرحبا", req(FontFace::Sans, 13.0, None));
        assert_eq!(out.line_count(), 1);
        let runs = &out.lines[0].runs;
        assert!(!runs.is_empty());
        assert!(runs.iter().all(|r| r.is_rtl));
        assert!(runs.iter().any(|r| !r.glyphs.is_empty()));
    }

    #[test]
    fn mixed_direction_runs_partition_source_bytes_without_overlap() {
        let text = "abc مرحبا def";
        let out = shape(text, req(FontFace::Sans, 13.0, None));
        assert_eq!(out.line_count(), 1);
        let runs = &out.lines[0].runs;
        assert!(runs.iter().any(|r| !r.is_rtl), "expected an LTR run");
        assert!(runs.iter().any(|r| r.is_rtl), "expected an RTL run");
        let mut spans: Vec<_> = runs
            .iter()
            .map(|r| (r.source_range.start.value(), r.source_range.end.value()))
            .collect();
        spans.sort_unstable();
        assert_eq!(spans.first().map(|s| s.0), Some(0));
        assert_eq!(spans.last().map(|s| s.1), Some(text.len()));
        spans.windows(2).for_each(|pair| {
            let (_, prev_end) = pair[0];
            let (next_start, _) = pair[1];
            assert!(
                next_start >= prev_end,
                "runs overlap: prev end {prev_end} > next start {next_start}",
            );
        });
    }

    #[test]
    fn multi_glyph_cluster_keeps_shared_source_byte() {
        let text = "i\u{0307}\u{0301}";
        let out = shape(text, req(FontFace::Sans, 13.0, None));
        let glyphs = &out.lines[0].runs[0].glyphs;
        assert!(
            glyphs.len() >= 2,
            "expected multi-glyph cluster, got {}",
            glyphs.len(),
        );
        assert!(glyphs.iter().all(|g| g.cluster == SourceByteIndex::new(0)));
    }

    #[test]
    fn precomposed_combining_mark_collapses_to_single_glyph() {
        let out = shape("e\u{0301}", req(FontFace::Sans, 13.0, None));
        let glyphs = &out.lines[0].runs[0].glyphs;
        assert_eq!(glyphs.len(), 1);
        assert_eq!(glyphs[0].cluster, SourceByteIndex::new(0));
    }

    #[test]
    fn long_text_wraps_when_max_width_set() {
        let text = "the quick brown fox jumps over the lazy dog";
        let single = shape(text, req(FontFace::Sans, 13.0, None));
        assert_eq!(single.line_count(), 1);
        let cap = MaxWidth::new(60.0);
        let wrapped = shape(text, req(FontFace::Sans, 13.0, cap));
        assert!(
            wrapped.line_count() >= 2,
            "expected wrap into multiple lines, got {}",
            wrapped.line_count(),
        );
        let total: usize = wrapped
            .lines
            .iter()
            .flat_map(|l| l.runs.iter())
            .map(|r| r.glyphs.len())
            .sum();
        assert_eq!(total, single.glyph_count());
    }

    #[test]
    fn trailing_whitespace_surfaces_on_line() {
        let bare = shape("abc", req(FontFace::Mono, 13.0, None));
        let trailed = shape("abc   ", req(FontFace::Mono, 13.0, None));
        let bare_line = &bare.lines[0];
        let trailed_line = &trailed.lines[0];
        assert!(bare_line.trailing_whitespace_px < 0.5);
        assert!(trailed_line.trailing_whitespace_px > bare_line.trailing_whitespace_px);
        assert!(trailed_line.advance_px > trailed_line.visible_advance_px());
        assert!(
            (trailed_line.visible_advance_px() - bare_line.advance_px).abs() < 0.5,
            "visible advance with trailing spaces should match bare advance",
        );
    }

    #[test]
    fn explicit_newline_breaks_into_separate_lines() {
        let out = shape("a\nb", req(FontFace::Sans, 13.0, None));
        assert_eq!(out.line_count(), 2);
        assert!(
            out.lines
                .iter()
                .all(|l| l.runs.iter().any(|r| !r.glyphs.is_empty()))
        );
    }

    #[test]
    fn tab_character_emits_one_glyph_per_codepoint() {
        let out = shape("a\tb", req(FontFace::Mono, 13.0, None));
        assert_eq!(out.line_count(), 1);
        assert_eq!(out.glyph_count(), 3);
    }

    #[test]
    fn mono_face_advances_uniformly() {
        let out = shape("iWiW", req(FontFace::Mono, 12.0, None));
        let glyphs = &out.lines[0].runs[0].glyphs;
        assert_eq!(glyphs.len(), 4);
        let advances: Vec<f32> = glyphs.iter().map(|g| g.advance_px).collect();
        let first = advances[0];
        assert!(
            advances.iter().all(|a| (a - first).abs() < 1e-3),
            "monospace advances should match: {advances:?}",
        );
    }

    #[test]
    fn sans_collapses_fi_ligature_mono_does_not() {
        let sans = shape("fi", req(FontFace::Sans, 13.0, None));
        let mono = shape("fi", req(FontFace::Mono, 13.0, None));
        assert_eq!(sans.glyph_count(), 1, "DejaVu Sans should ligate fi");
        assert_eq!(mono.glyph_count(), 2, "DejaVu Sans Mono should not ligate");
    }

    #[test]
    fn trailing_newline_emits_empty_terminal_line() {
        let out = shape("a\n", req(FontFace::Sans, 13.0, None));
        assert_eq!(out.line_count(), 2);
        assert_eq!(out.glyph_count(), 1);
        assert!(out.lines[1].runs.is_empty());
    }

    #[test]
    fn sub_glyph_max_width_does_not_split_a_glyph() {
        let cap = MaxWidth::new(0.5);
        assert!(cap.is_some());
        let out = shape("abc", req(FontFace::Sans, 13.0, cap));
        assert_eq!(out.line_count(), 1);
        assert_eq!(out.glyph_count(), 3);
    }

    #[test]
    fn explicit_line_height_drives_line_advance() {
        let mut request = req(FontFace::Sans, 13.0, None);
        let baseline = shape("a", request);
        request.line_height_px = 48.0;
        let tall = shape("a", request);
        assert!(
            tall.height_px > baseline.height_px + 20.0,
            "tall line height must stretch bounds: baseline {} vs tall {}",
            baseline.height_px,
            tall.height_px,
        );
    }

    #[test]
    fn positive_letter_spacing_widens_advance() {
        let bare = req(FontFace::Sans, 13.0, None);
        let mut spaced = bare;
        spaced.letter_spacing_px = 4.0;
        let bare_out = shape("abc", bare);
        let spaced_out = shape("abc", spaced);
        assert!(
            spaced_out.width_px > bare_out.width_px + 6.0,
            "letter spacing must widen advance: bare {} vs spaced {}",
            bare_out.width_px,
            spaced_out.width_px,
        );
    }

    #[test]
    fn rtl_run_emits_glyphs_visual_left_to_right_with_descending_source_bytes() {
        let out = shape("مرحبا", req(FontFace::Sans, 13.0, None));
        let line = &out.lines[0];
        assert_eq!(line.runs.len(), 1);
        let run = &line.runs[0];
        assert!(run.is_rtl);
        assert!(run.glyphs.len() >= 2);
        let dxs: Vec<f32> = run.glyphs.iter().map(|g| g.x_px).collect();
        dxs.windows(2)
            .for_each(|p| assert!(p[1] >= p[0], "non-monotonic visual dx: {dxs:?}"));
        let bytes: Vec<usize> = run.glyphs.iter().map(|g| g.cluster.value()).collect();
        let last_idx = bytes.len() - 1;
        assert!(
            bytes[0] > bytes[last_idx],
            "expected RTL visual-leftmost glyph to map to later source byte: {bytes:?}",
        );
    }
}
