use bone_render::PickedItem;
use bone_types::{SketchDimensionId, SketchEntityId, SketchItemId, SketchRelationId};

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Selection {
    Entities(Vec<SketchEntityId>),
    Relation(SketchRelationId),
    Dimension(SketchDimensionId),
}

impl Default for Selection {
    fn default() -> Self {
        Self::Entities(Vec::new())
    }
}

impl Selection {
    #[must_use]
    pub fn entity_ids(&self) -> &[SketchEntityId] {
        match self {
            Self::Entities(v) => v,
            Self::Relation(_) | Self::Dimension(_) => &[],
        }
    }

    #[must_use]
    pub fn is_empty(&self) -> bool {
        matches!(self, Self::Entities(v) if v.is_empty())
    }

    #[must_use]
    pub fn picked(self, item: Option<SketchItemId>, additive: bool) -> Self {
        match (item, additive) {
            (None, false) => Self::default(),
            (None, true) => self,
            (Some(SketchItemId::Entity(id)), is_additive) => match self {
                Self::Entities(v) if is_additive => Self::Entities(toggle(v, id)),
                _ => Self::Entities(vec![id]),
            },
            (Some(SketchItemId::Relation(id)), _) => Self::Relation(id),
            (Some(SketchItemId::Dimension(id)), _) => Self::Dimension(id),
        }
    }
}

#[must_use]
pub fn picked_to_item(picked: PickedItem) -> Option<SketchItemId> {
    match picked {
        PickedItem::Point(id)
        | PickedItem::Line(id)
        | PickedItem::Arc(id)
        | PickedItem::Circle(id) => Some(SketchItemId::Entity(id)),
        PickedItem::Relation(id) => Some(SketchItemId::Relation(id)),
        PickedItem::Dimension(id) => Some(SketchItemId::Dimension(id)),
        PickedItem::BrepFace(_) | PickedItem::BrepEdge(_) | PickedItem::BrepVertex(_) => None,
    }
}

fn toggle(mut v: Vec<SketchEntityId>, id: SketchEntityId) -> Vec<SketchEntityId> {
    match v.iter().position(|x| *x == id) {
        Some(idx) => {
            v.remove(idx);
        }
        None => v.push(id),
    }
    v
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::sketch_mode::Plane;
    use crate::tools::add_point;
    use bone_document::{
        DimensionKind, EditOutcome, Sketch, SketchDimension, SketchEdit, SketchRelation,
    };
    use bone_types::{Length, Point2};
    use uom::si::length::millimeter;

    fn three_ids() -> (SketchEntityId, SketchEntityId, SketchEntityId) {
        let s = Sketch::new(Plane::Xy.basis());
        let (s, a) = add_point(s, Point2::from_mm(0.0, 0.0));
        let (s, b) = add_point(s, Point2::from_mm(1.0, 0.0));
        let (_, c) = add_point(s, Point2::from_mm(2.0, 0.0));
        (a, b, c)
    }

    fn rel_id() -> SketchRelationId {
        let s = Sketch::new(Plane::Xy.basis());
        let (s, a) = add_point(s, Point2::from_mm(0.0, 0.0));
        let Ok((_, EditOutcome::Relation(id))) =
            s.apply(SketchEdit::AddRelation(SketchRelation::Fix(a)))
        else {
            panic!("expected Relation outcome");
        };
        id
    }

    fn dim_id() -> SketchDimensionId {
        let s = Sketch::new(Plane::Xy.basis());
        let (s, a) = add_point(s, Point2::from_mm(0.0, 0.0));
        let (s, b) = add_point(s, Point2::from_mm(1.0, 0.0));
        let dim = SketchDimension::Linear {
            a,
            b,
            value: Length::new::<millimeter>(1.0),
            kind: DimensionKind::Driving,
        };
        let Ok((_, EditOutcome::Dimension(id))) = s.apply(SketchEdit::AddDimension(dim)) else {
            panic!("expected Dimension outcome");
        };
        id
    }

    #[test]
    fn default_selection_is_empty_entities() {
        assert!(Selection::default().entity_ids().is_empty());
        assert!(matches!(Selection::default(), Selection::Entities(_)));
    }

    #[test]
    fn pick_replace_entity_overwrites_existing() {
        let (a, b, _) = three_ids();
        let s = Selection::default()
            .picked(Some(SketchItemId::Entity(a)), false)
            .picked(Some(SketchItemId::Entity(b)), false);
        assert_eq!(s.entity_ids(), &[b]);
    }

    #[test]
    fn pick_additive_extends_then_toggles_off() {
        let (a, b, _) = three_ids();
        let s = Selection::default()
            .picked(Some(SketchItemId::Entity(a)), true)
            .picked(Some(SketchItemId::Entity(b)), true);
        assert_eq!(s.entity_ids(), &[a, b]);
        let s = s.picked(Some(SketchItemId::Entity(a)), true);
        assert_eq!(s.entity_ids(), &[b]);
    }

    #[test]
    fn pick_replace_with_none_clears() {
        let (a, b, _) = three_ids();
        let s = Selection::default()
            .picked(Some(SketchItemId::Entity(a)), true)
            .picked(Some(SketchItemId::Entity(b)), true)
            .picked(None, false);
        assert!(s.entity_ids().is_empty());
    }

    #[test]
    fn pick_additive_with_none_preserves() {
        let (a, _, _) = three_ids();
        let s = Selection::default()
            .picked(Some(SketchItemId::Entity(a)), true)
            .picked(None, true);
        assert_eq!(s.entity_ids(), &[a]);
    }

    #[test]
    fn pick_relation_replaces_entities() {
        let (a, b, _) = three_ids();
        let r = rel_id();
        let s = Selection::default()
            .picked(Some(SketchItemId::Entity(a)), true)
            .picked(Some(SketchItemId::Entity(b)), true)
            .picked(Some(SketchItemId::Relation(r)), false);
        assert!(s.entity_ids().is_empty());
        assert_eq!(s, Selection::Relation(r));
    }

    #[test]
    fn pick_entity_after_relation_clears_relation() {
        let (a, _, _) = three_ids();
        let r = rel_id();
        let s = Selection::default()
            .picked(Some(SketchItemId::Relation(r)), false)
            .picked(Some(SketchItemId::Entity(a)), false);
        assert_eq!(s.entity_ids(), &[a]);
    }

    #[test]
    fn pick_dimension_replaces_relation() {
        let r = rel_id();
        let d = dim_id();
        let s = Selection::default()
            .picked(Some(SketchItemId::Relation(r)), false)
            .picked(Some(SketchItemId::Dimension(d)), false);
        assert_eq!(s, Selection::Dimension(d));
    }
}
