//! Basic utilities for converting Typst types to krilla.

use std::fmt::Display;

use ecow::{EcoString, eco_format};
use krilla::color::separation as ks;
use krilla::configure::{Validator, Validators};
use krilla::geom as kg;
use krilla::geom::PathBuilder;
use krilla::paint as kp;
use krilla::tagging as kt;
use smallvec::SmallVec;
use typst_library::foundations::Repr;
use typst_library::layout::{Abs, Point, Sides, Size, Transform};
use typst_library::text::FontInstance;
use typst_library::visualize::SpotColorantName;
use typst_library::visualize::{Curve, CurveItem, FillRule, LineCap, LineJoin};

pub(crate) trait SidesExt<T> {
    /// Map to the [`kt::Sides`] struct assuming [`kt::WritingMode::LrTb`].
    fn to_lrtb_krilla(self) -> kt::Sides<T>;
}

impl<T> SidesExt<T> for Sides<T> {
    fn to_lrtb_krilla(self) -> kt::Sides<T> {
        kt::Sides {
            before: self.top,
            after: self.bottom,
            start: self.left,
            end: self.right,
        }
    }
}

pub(crate) trait SizeExt {
    fn to_krilla(&self) -> Option<kg::Size>;
}

impl SizeExt for Size {
    fn to_krilla(&self) -> Option<kg::Size> {
        kg::Size::from_wh(self.x.to_f32(), self.y.to_f32())
    }
}

pub(crate) trait PointExt {
    fn to_krilla(&self) -> kg::Point;
}

impl PointExt for Point {
    fn to_krilla(&self) -> kg::Point {
        kg::Point::from_xy(self.x.to_f32(), self.y.to_f32())
    }
}

pub(crate) trait LineCapExt {
    fn to_krilla(&self) -> kp::LineCap;
}

impl LineCapExt for LineCap {
    fn to_krilla(&self) -> kp::LineCap {
        match self {
            LineCap::Butt => kp::LineCap::Butt,
            LineCap::Round => kp::LineCap::Round,
            LineCap::Square => kp::LineCap::Square,
        }
    }
}

pub(crate) trait LineJoinExt {
    fn to_krilla(&self) -> kp::LineJoin;
}

impl LineJoinExt for LineJoin {
    fn to_krilla(&self) -> kp::LineJoin {
        match self {
            LineJoin::Miter => kp::LineJoin::Miter,
            LineJoin::Round => kp::LineJoin::Round,
            LineJoin::Bevel => kp::LineJoin::Bevel,
        }
    }
}

pub(crate) trait TransformExt {
    fn to_krilla(&self) -> kg::Transform;
}

impl TransformExt for Transform {
    fn to_krilla(&self) -> kg::Transform {
        kg::Transform::from_row(
            self.sx.get() as f32,
            self.ky.get() as f32,
            self.kx.get() as f32,
            self.sy.get() as f32,
            self.tx.to_f32(),
            self.ty.to_f32(),
        )
    }
}

pub(crate) trait FillRuleExt {
    fn to_krilla(&self) -> kp::FillRule;
}

impl FillRuleExt for FillRule {
    fn to_krilla(&self) -> kp::FillRule {
        match self {
            FillRule::NonZero => kp::FillRule::NonZero,
            FillRule::EvenOdd => kp::FillRule::EvenOdd,
        }
    }
}

pub(crate) trait AbsExt {
    fn to_f32(self) -> f32;
}

impl AbsExt for Abs {
    fn to_f32(self) -> f32 {
        self.to_pt() as f32
    }
}

pub(crate) trait ValidatorsExt {
    /// Formats the validators into a comma separated list.
    fn to_comma_list(self) -> impl Display;

    /// Formats the validators into a comma separated list with a conjunction
    /// before the last item (if more than one item exists). Uses Oxford commas.
    fn to_and_list(self) -> impl Display;
}

impl ValidatorsExt for Validators {
    fn to_comma_list(self) -> impl Display {
        typst_utils::display(move |f| {
            for (i, v) in self.into_iter().enumerate() {
                if i != 0 {
                    f.write_str(", ")?;
                }
                f.write_str(v.as_str())?;
            }
            Ok(())
        })
    }

    fn to_and_list(self) -> impl Display {
        typst_utils::display(move |f| {
            let names: SmallVec<[_; 2]> =
                self.into_iter().map(Validator::as_str).collect();
            match names.as_slice() {
                [] => Ok(()),
                [a] => f.write_str(a),
                [a, b] => write!(f, "{a} and {b}"),
                [rest @ .., last] => {
                    for v in rest.iter() {
                        write!(f, "{v}, ")?;
                    }
                    write!(f, "and {last}")
                }
            }
        })
    }
}

pub(crate) trait SpotColorantToNameExt {
    fn to_krilla(&self) -> ks::SeparationColorant;
}

pub(crate) trait SpotColorantFromNameExt {
    fn from_krilla(colorant: &ks::SeparationColorant) -> Self;
}

impl SpotColorantToNameExt for SpotColorantName {
    fn to_krilla(&self) -> ks::SeparationColorant {
        match self {
            Self::All => ks::SeparationColorant::AllColorants,
            Self::Custom(name) => ks::SeparationColorant::Custom(name.into()),
        }
    }
}

impl<T: SpotColorantToNameExt> SpotColorantToNameExt for Option<T> {
    fn to_krilla(&self) -> ks::SeparationColorant {
        match self {
            Some(s) => s.to_krilla(),
            None => ks::SeparationColorant::NoColorant,
        }
    }
}

impl SpotColorantFromNameExt for Option<SpotColorantName> {
    fn from_krilla(colorant: &ks::SeparationColorant) -> Self {
        match colorant {
            ks::SeparationColorant::AllColorants => Some(SpotColorantName::All),
            ks::SeparationColorant::Custom(c) => Some(SpotColorantName::Custom(c.into())),
            ks::SeparationColorant::NoColorant => None,
        }
    }
}

/// Display the font family of a font.
pub(crate) fn display_font(font: Option<&FontInstance>) -> EcoString {
    match font {
        Some(font) => eco_format!("font `{}`", font.info().family.repr()),
        None => "a font".into(),
    }
}

/// Convert a Typst path to a krilla path.
pub(crate) fn convert_path(path: &Curve, builder: &mut PathBuilder) {
    for item in &path.0 {
        match item {
            CurveItem::Move(p) => builder.move_to(p.x.to_f32(), p.y.to_f32()),
            CurveItem::Line(p) => builder.line_to(p.x.to_f32(), p.y.to_f32()),
            CurveItem::Cubic(p1, p2, p3) => builder.cubic_to(
                p1.x.to_f32(),
                p1.y.to_f32(),
                p2.x.to_f32(),
                p2.y.to_f32(),
                p3.x.to_f32(),
                p3.y.to_f32(),
            ),
            CurveItem::Close => builder.close(),
        }
    }
}
