use indexmap::IndexMap;
use kcl_api::artifact::NamedViewArtifact;
use uuid::Uuid;
use super::Artifact;
use super::ArtifactCameraView;
use super::ArtifactGraph;
use super::ArtifactId;
use super::ModuleId;
use super::ModulePath;
use super::Operation;
use super::visibility;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NamedViewInfo {
pub artifact_id: ArtifactId,
pub name: String,
pub module_id: ModuleId,
pub module_path: ModulePath,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ViewTarget {
Default,
Named { name: String, module_id: ModuleId },
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ViewResolutionError {
#[error("No module path is available for named-view module {module_id}")]
MissingModulePath { module_id: ModuleId },
#[error("No named view matches the requested selection")]
NotFound,
#[error("The requested selection matches multiple named views")]
Ambiguous { candidates: Vec<NamedViewInfo> },
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResolvedView {
Default { visibility: IndexMap<Uuid, bool> },
Named {
visibility: IndexMap<Uuid, bool>,
camera_intent: Box<ArtifactCameraView>,
},
}
impl ResolvedView {
pub fn visibility(&self) -> &IndexMap<Uuid, bool> {
match self {
Self::Default { visibility } | Self::Named { visibility, .. } => visibility,
}
}
}
#[derive(Debug)]
pub struct NamedViewResolver<'a> {
artifact_graph: &'a ArtifactGraph,
operations: &'a IndexMap<ModuleId, Vec<Operation>>,
named_views: Vec<(&'a NamedViewArtifact, NamedViewInfo)>,
}
impl<'a> NamedViewResolver<'a> {
pub fn new(
artifact_graph: &'a ArtifactGraph,
operations: &'a IndexMap<ModuleId, Vec<Operation>>,
filenames: &IndexMap<ModuleId, ModulePath>,
) -> Result<Self, ViewResolutionError> {
let mut named_views = Vec::new();
for artifact in artifact_graph.values() {
let Artifact::NamedView(view) = artifact else {
continue;
};
let module_id = view.declaring_module_id;
let module_path = filenames
.get(&module_id)
.ok_or(ViewResolutionError::MissingModulePath { module_id })?;
named_views.push((
view,
NamedViewInfo {
artifact_id: view.id,
name: view.name.clone(),
module_id,
module_path: module_path.clone(),
},
));
}
Ok(Self {
artifact_graph,
operations,
named_views,
})
}
pub fn list(&self) -> Vec<NamedViewInfo> {
self.named_views.iter().map(|(_, info)| info.clone()).collect()
}
pub fn resolve(&self, target: &ViewTarget) -> Result<ResolvedView, ViewResolutionError> {
match target {
ViewTarget::Default => Ok(ResolvedView::Default {
visibility: visibility::visibility_for_default(self.artifact_graph, self.operations),
}),
ViewTarget::Named { name, module_id } => {
let candidates: Vec<_> = self
.named_views
.iter()
.filter(|(_, info)| info.name == *name && info.module_id == *module_id)
.collect();
match candidates.as_slice() {
[] => Err(ViewResolutionError::NotFound),
[(view, _)] => Ok(ResolvedView::Named {
visibility: visibility::visibility_for_view(self.artifact_graph, view),
camera_intent: Box::new(view.camera.clone()),
}),
_ => Err(ViewResolutionError::Ambiguous {
candidates: candidates.iter().map(|(_, info)| info.clone()).collect(),
}),
}
}
}
}
}