use bone_types::{FeatureId, SketchId};
use serde::{Deserialize, Serialize};
use slotmap::{Key, KeyData};

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PrincipalPlane {
    Xy,
    Yz,
    Zx,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum FeatureNode {
    Origin,
    PrincipalPlane(PrincipalPlane),
    Sketch(SketchId),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct FeatureEntry {
    id: FeatureId,
    node: FeatureNode,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct FeatureTree {
    entries: Vec<FeatureEntry>,
}

impl FeatureTree {
    #[must_use]
    pub fn seeded() -> Self {
        let seeds = [
            FeatureNode::Origin,
            FeatureNode::PrincipalPlane(PrincipalPlane::Xy),
            FeatureNode::PrincipalPlane(PrincipalPlane::Yz),
            FeatureNode::PrincipalPlane(PrincipalPlane::Zx),
        ];
        let entries = seeds
            .into_iter()
            .zip(1u32..)
            .map(|(node, idx)| FeatureEntry {
                id: feature_id_from_idx(idx),
                node,
            })
            .collect();
        Self { entries }
    }

    #[must_use]
    pub fn node(&self, id: FeatureId) -> Option<FeatureNode> {
        self.entries.iter().find(|e| e.id == id).map(|e| e.node)
    }

    pub fn iter(&self) -> impl Iterator<Item = (FeatureId, FeatureNode)> + '_ {
        self.entries.iter().map(|e| (e.id, e.node))
    }

    #[must_use]
    pub fn feature_of_sketch(&self, sketch: SketchId) -> Option<FeatureId> {
        self.entries
            .iter()
            .find(|e| matches!(e.node, FeatureNode::Sketch(s) if s == sketch))
            .map(|e| e.id)
    }

    pub fn push_sketch(&mut self, sketch: SketchId) -> FeatureId {
        if let Some(existing) = self.feature_of_sketch(sketch) {
            return existing;
        }
        let id = self.allocate();
        self.entries.push(FeatureEntry {
            id,
            node: FeatureNode::Sketch(sketch),
        });
        id
    }

    pub fn remove_sketch(&mut self, sketch: SketchId) -> Option<FeatureId> {
        let id = self.feature_of_sketch(sketch)?;
        self.entries.retain(|e| e.id != id);
        Some(id)
    }

    fn allocate(&self) -> FeatureId {
        let highest = self.entries.iter().map(|e| idx_of(e.id)).max().unwrap_or(0);
        let Some(next) = highest.checked_add(1) else {
            panic!("FeatureTree exhausted 32-bit feature id space");
        };
        feature_id_from_idx(next)
    }
}

fn feature_id_from_idx(idx: u32) -> FeatureId {
    FeatureId::from(KeyData::from_ffi((1u64 << 32) | u64::from(idx)))
}

fn idx_of(id: FeatureId) -> u32 {
    let Ok(idx) = u32::try_from(id.data().as_ffi() & 0xFFFF_FFFF) else {
        panic!("lower 32 bits of ffi key fit in u32");
    };
    idx
}
