use grust::prelude::*;
use grust::typed::garde;
use serde::Serialize;

#[derive(Debug, Serialize, garde::Validate)]
#[garde(allow_unvalidated)]
struct Person {
    #[garde(length(min = 1))]
    id: String,
    #[garde(length(min = 1))]
    name: String,
    #[garde(length(min = 1), inner(length(min = 1)))]
    skills: Vec<String>,
}

impl TypedNode for Person {
    const LABEL: &'static str = "Person";

    fn node_id(&self) -> NodeId {
        format!("person:{}", self.id).into()
    }
}

#[derive(Debug, Serialize, garde::Validate)]
#[garde(allow_unvalidated)]
struct Project {
    #[garde(length(min = 1))]
    id: String,
    #[garde(length(min = 1))]
    title: String,
}

impl TypedNode for Project {
    const LABEL: &'static str = "Project";

    fn node_id(&self) -> NodeId {
        format!("project:{}", self.id).into()
    }
}

#[derive(Debug, Serialize, garde::Validate)]
#[garde(allow_unvalidated)]
struct WorksOn {
    #[garde(length(min = 1))]
    person_id: String,
    #[garde(length(min = 1))]
    project_id: String,
    #[garde(range(min = 1, max = 100))]
    allocation_percent: u8,
}

impl TypedEdge for WorksOn {
    const LABEL: &'static str = "WORKS_ON";

    fn from_node_id(&self) -> NodeId {
        format!("person:{}", self.person_id).into()
    }

    fn to_node_id(&self) -> NodeId {
        format!("project:{}", self.project_id).into()
    }
}

#[derive(Debug, Serialize, garde::Validate)]
#[garde(allow_unvalidated)]
struct Team {
    #[garde(length(min = 1))]
    id: String,
    #[garde(length(min = 1))]
    name: String,
}

impl TypedNode for Team {
    const LABEL: &'static str = "Team";

    fn node_id(&self) -> NodeId {
        format!("team:{}", self.id).into()
    }
}

#[derive(Debug, Serialize, garde::Validate)]
#[garde(allow_unvalidated)]
struct MemberOf {
    #[garde(length(min = 1))]
    person_id: String,
    #[garde(length(min = 1))]
    team_id: String,
}

impl TypedEdge for MemberOf {
    const LABEL: &'static str = "MEMBER_OF";

    fn from_node_id(&self) -> NodeId {
        format!("person:{}", self.person_id).into()
    }

    fn to_node_id(&self) -> NodeId {
        format!("team:{}", self.team_id).into()
    }
}

fn main() -> Result<()> {
    let mut builder = TypedGraphBuilder::new();

    builder.add_node(&Person {
        id: "nia".to_string(),
        name: "Nia".to_string(),
        skills: vec!["rust".to_string(), "graphs".to_string()],
    })?;
    builder.add_node(&Project {
        id: "grust".to_string(),
        title: "Grust".to_string(),
    })?;
    builder.add_edge(&WorksOn {
        person_id: "nia".to_string(),
        project_id: "grust".to_string(),
        allocation_percent: 80,
    })?;

    builder.add_node(&Team {
        id: "platform".to_string(),
        name: "Platform".to_string(),
    })?;
    builder.add_edge(&MemberOf {
        person_id: "nia".to_string(),
        team_id: "platform".to_string(),
    })?;

    let graph = builder.build();
    println!("{}", graph.to_yaml()?);

    Ok(())
}
