use bone_text::{
    FontFace, FontWeight, ShapeRequest, ShapedText, Shaper, TessellatedOutline, append_outline,
    tessellate_path,
};
use lyon_tessellation::{FillTessellator, path::Path as LyonPath};
use swash::{
    FontRef,
    scale::{ScaleContext, outline::Outline},
    zeno::Point as ZenoPoint,
};

pub(crate) fn shape_line(
    text: &str,
    size_px: f32,
    face: FontFace,
    shaper: &mut Shaper,
) -> ShapedText {
    shaper.shape(
        text,
        ShapeRequest {
            face,
            size_px,
            weight: FontWeight::Regular,
            line_height_px: 0.0,
            letter_spacing_px: 0.0,
            max_width: None,
        },
    )
}

pub(crate) fn tessellate_at(
    layout: &ShapedText,
    font: &FontRef<'_>,
    scale_ctx: &mut ScaleContext,
    fill: &mut FillTessellator,
    origin: ZenoPoint,
) -> TessellatedOutline {
    let path = build_path_at(layout, scale_ctx, font, origin);
    tessellate_path(&path, fill).unwrap_or_else(|e| {
        tracing::warn!(error = %e, "text tessellation failed");
        TessellatedOutline::default()
    })
}

fn build_path_at(
    layout: &ShapedText,
    scale_ctx: &mut ScaleContext,
    font: &FontRef<'_>,
    origin: ZenoPoint,
) -> LyonPath {
    let mut scaler = scale_ctx.builder(*font).size(layout.font_size_px).build();
    layout
        .lines
        .iter()
        .flat_map(|line| line.runs.iter())
        .flat_map(|run| {
            let run_origin = run.origin_x_px;
            run.glyphs.iter().map(move |g| (run_origin + g.x_px, g.id))
        })
        .fold(LyonPath::svg_builder(), |mut builder, (x, id)| {
            let Ok(glyph_id_u16) = u16::try_from(id.raw()) else {
                return builder;
            };
            let mut outline = Outline::new();
            if scaler.scale_outline_into(glyph_id_u16, &mut outline) {
                append_outline(
                    &mut builder,
                    &outline,
                    ZenoPoint::new(origin.x + x, origin.y),
                );
            }
            builder
        })
        .build()
}
