use std::collections::{HashMap, HashSet};
use uuid::Uuid;
use super::graph_algorithms::{find_path_dfs, has_path_dfs, topological_sort};
use super::relationship_errors::{GraphError, RemovalError, ValidationError};
use super::{EpisodeRelationship, RelationshipMetadata, RelationshipType};
#[derive(Debug, Clone)]
pub struct RelationshipManager {
adjacency_list: HashMap<Uuid, Vec<EpisodeRelationship>>,
reverse_adjacency: HashMap<Uuid, Vec<EpisodeRelationship>>,
relationship_cache: HashSet<(Uuid, Uuid, RelationshipType)>,
}
impl Default for RelationshipManager {
fn default() -> Self {
Self::new()
}
}
impl RelationshipManager {
#[must_use]
pub fn new() -> Self {
Self {
adjacency_list: HashMap::new(),
reverse_adjacency: HashMap::new(),
relationship_cache: HashSet::new(),
}
}
pub fn load_relationships(&mut self, relationships: Vec<EpisodeRelationship>) {
self.adjacency_list.clear();
self.reverse_adjacency.clear();
self.relationship_cache.clear();
for rel in relationships {
self.add_to_graph(rel);
}
}
pub fn add_with_validation(
&mut self,
from_episode_id: Uuid,
to_episode_id: Uuid,
relationship_type: RelationshipType,
metadata: RelationshipMetadata,
) -> Result<EpisodeRelationship, ValidationError> {
if from_episode_id == to_episode_id {
return Err(ValidationError::SelfRelationship {
episode_id: from_episode_id,
});
}
if self.relationship_exists(from_episode_id, to_episode_id, relationship_type) {
return Err(ValidationError::DuplicateRelationship {
from: from_episode_id,
to: to_episode_id,
rel_type: relationship_type,
});
}
if relationship_type.requires_acyclic() {
let would_create = self
.would_create_cycle(from_episode_id, to_episode_id)
.map_err(|_e| ValidationError::EpisodeNotFound {
episode_id: from_episode_id,
})?;
if would_create {
let cycle = self
.find_cycle_path(from_episode_id, to_episode_id)
.map_err(|_e| ValidationError::EpisodeNotFound {
episode_id: from_episode_id,
})?;
return Err(ValidationError::CycleDetected {
from: from_episode_id,
to: to_episode_id,
cycle_path: cycle,
});
}
}
if let Some(priority) = metadata.priority {
if !(1..=10).contains(&priority) {
return Err(ValidationError::InvalidPriority {
priority,
valid_range: (1, 10),
});
}
}
let relationship =
EpisodeRelationship::new(from_episode_id, to_episode_id, relationship_type, metadata);
self.add_to_graph(relationship.clone());
Ok(relationship)
}
pub fn remove_relationship(&mut self, relationship_id: Uuid) -> Result<(), RemovalError> {
let relationship =
self.find_relationship_by_id(relationship_id)
.ok_or(RemovalError::NotFound {
id: relationship_id,
})?;
if let Some(rels) = self.adjacency_list.get_mut(&relationship.from_episode_id) {
rels.retain(|r| r.id != relationship_id);
}
if let Some(rels) = self.reverse_adjacency.get_mut(&relationship.to_episode_id) {
rels.retain(|r| r.id != relationship_id);
}
self.relationship_cache.remove(&(
relationship.from_episode_id,
relationship.to_episode_id,
relationship.relationship_type,
));
Ok(())
}
#[must_use]
pub fn relationship_exists(
&self,
from_id: Uuid,
to_id: Uuid,
rel_type: RelationshipType,
) -> bool {
self.relationship_cache
.contains(&(from_id, to_id, rel_type))
}
#[must_use]
pub fn get_outgoing(&self, episode_id: Uuid) -> Vec<EpisodeRelationship> {
self.adjacency_list
.get(&episode_id)
.cloned()
.unwrap_or_default()
}
#[must_use]
pub fn get_incoming(&self, episode_id: Uuid) -> Vec<EpisodeRelationship> {
self.reverse_adjacency
.get(&episode_id)
.cloned()
.unwrap_or_default()
}
#[must_use]
pub fn get_by_type(
&self,
episode_id: Uuid,
rel_type: RelationshipType,
) -> Vec<EpisodeRelationship> {
let mut results = Vec::new();
if let Some(rels) = self.adjacency_list.get(&episode_id) {
results.extend(
rels.iter()
.filter(|r| r.relationship_type == rel_type)
.cloned(),
);
}
if let Some(rels) = self.reverse_adjacency.get(&episode_id) {
results.extend(
rels.iter()
.filter(|r| r.relationship_type == rel_type)
.cloned(),
);
}
results
}
pub fn would_create_cycle(&self, from_id: Uuid, to_id: Uuid) -> Result<bool, GraphError> {
has_path_dfs(&self.adjacency_list, to_id, from_id)
}
pub fn find_cycle_path(&self, from_id: Uuid, to_id: Uuid) -> Result<Vec<Uuid>, GraphError> {
find_path_dfs(&self.adjacency_list, to_id, from_id)
}
#[must_use]
pub fn get_all_relationships(&self) -> Vec<EpisodeRelationship> {
let mut result = Vec::new();
let mut seen = HashSet::new();
for rels in self.adjacency_list.values() {
for rel in rels {
if seen.insert(rel.id) {
result.push(rel.clone());
}
}
}
result
}
#[must_use]
pub fn relationship_count(&self) -> usize {
self.relationship_cache.len()
}
#[must_use]
pub fn episode_count(&self) -> usize {
let mut episodes = HashSet::new();
for (from_id, rels) in &self.adjacency_list {
episodes.insert(*from_id);
for rel in rels {
episodes.insert(rel.to_episode_id);
}
}
episodes.len()
}
pub fn topological_order(&self) -> Result<Vec<Uuid>, GraphError> {
topological_sort(&self.adjacency_list)
}
pub fn clear(&mut self) {
self.adjacency_list.clear();
self.reverse_adjacency.clear();
self.relationship_cache.clear();
}
fn add_to_graph(&mut self, relationship: EpisodeRelationship) {
self.adjacency_list
.entry(relationship.from_episode_id)
.or_default()
.push(relationship.clone());
self.reverse_adjacency
.entry(relationship.to_episode_id)
.or_default()
.push(relationship.clone());
self.relationship_cache.insert((
relationship.from_episode_id,
relationship.to_episode_id,
relationship.relationship_type,
));
}
fn find_relationship_by_id(&self, id: Uuid) -> Option<EpisodeRelationship> {
for rels in self.adjacency_list.values() {
if let Some(rel) = rels.iter().find(|r| r.id == id) {
return Some(rel.clone());
}
}
None
}
}