use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use supercode_interchange::ontology::ArtifactFidelity;
use supercode_interchange::orchestration::codec::folder::OWNED_FILES;
use supercode_interchange::orchestration::codec::{
carry_unmodeled, from_hermes, from_openclaw, load_home, save_home, to_hermes, to_openclaw,
Flavor, LoadedHome, Refusal,
};
use supercode_interchange::orchestration::Orchestration;
use crate::Result;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HomeFlavor {
#[default]
Orchestrator,
Hermes,
}
impl From<HomeFlavor> for Flavor {
fn from(flavor: HomeFlavor) -> Self {
match flavor {
HomeFlavor::Orchestrator => Flavor::Orchestrator,
HomeFlavor::Hermes => Flavor::Hermes,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceFlavor {
#[default]
Native,
Orchestrator,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OrchestrationHarness {
Hermes,
Openclaw,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RefusalRow {
pub file: String,
pub reason: String,
}
impl From<&Refusal> for RefusalRow {
fn from(refusal: &Refusal) -> Self {
Self {
file: refusal.file.clone(),
reason: refusal.reason.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OrchestrationRead {
pub orchestration: Orchestration,
pub vault_keys: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OrchestrationSaved {
pub written: bool,
pub root: PathBuf,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct OrchestrationDecompiled {
pub written: Vec<ArtifactFidelity>,
pub refused: Vec<RefusalRow>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub notes: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rows_byte: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rows_emitted: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OrchestrationImported {
pub orchestration: Orchestration,
pub vault_keys: Vec<String>,
pub root: PathBuf,
pub carried: Vec<String>,
}
fn keys(vault: &BTreeMap<String, String>) -> Vec<String> {
vault.keys().cloned().collect()
}
fn repoint(orchestration: &mut Orchestration, root: &Path) {
orchestration.root = root.to_path_buf();
for (name, profile) in orchestration.profiles.iter_mut() {
profile.dir = if name == "default" {
root.to_path_buf()
} else {
root.join("profiles").join(name)
};
}
}
pub fn load(root: &Path, flavor: HomeFlavor) -> Result<OrchestrationRead> {
let loaded = load_home(root, flavor.into())?;
Ok(OrchestrationRead {
vault_keys: keys(&loaded.vault),
orchestration: loaded.orchestration,
})
}
pub fn save(
root: &Path,
orchestration: Orchestration,
vault: BTreeMap<String, String>,
) -> Result<OrchestrationSaved> {
let mut loaded = if root.is_dir() {
load_home(root, Flavor::Orchestrator)?
} else {
LoadedHome {
orchestration: orchestration.clone(),
vault: BTreeMap::new(),
io: BTreeMap::new(),
}
};
loaded.orchestration = orchestration;
repoint(&mut loaded.orchestration, root);
loaded.vault.extend(vault);
save_home(&mut loaded, Some(root))?;
Ok(OrchestrationSaved {
written: true,
root: root.to_path_buf(),
})
}
pub fn compile(from: OrchestrationHarness, home: &Path) -> Result<OrchestrationRead> {
Ok(match from {
OrchestrationHarness::Hermes => {
let loaded = from_hermes(home)?;
OrchestrationRead {
vault_keys: keys(&loaded.vault),
orchestration: loaded.orchestration,
}
}
OrchestrationHarness::Openclaw => {
let loaded = from_openclaw(home)?;
OrchestrationRead {
vault_keys: keys(&loaded.vault),
orchestration: loaded.orchestration,
}
}
})
}
pub fn decompile(
to: OrchestrationHarness,
orchestration: Orchestration,
source: &Path,
source_flavor: SourceFlavor,
dest: &Path,
vault: BTreeMap<String, String>,
) -> Result<OrchestrationDecompiled> {
Ok(match (to, source_flavor) {
(OrchestrationHarness::Hermes, SourceFlavor::Orchestrator) => {
let mut loaded = load_home(source, HomeFlavor::Orchestrator.into())?;
loaded.orchestration = orchestration;
loaded.vault.extend(vault);
let report = to_hermes(&loaded, dest, None)?;
OrchestrationDecompiled {
written: report.written,
refused: report.refused.iter().map(RefusalRow::from).collect(),
..OrchestrationDecompiled::default()
}
}
(OrchestrationHarness::Openclaw, SourceFlavor::Orchestrator) => {
let loaded =
supercode_interchange::orchestration::codec::OpenclawLoaded::from_orchestration(
orchestration,
{
let mut v = load_home(source, HomeFlavor::Orchestrator.into())?.vault;
v.extend(vault);
v
},
);
let report = to_openclaw(&loaded, dest)?;
OrchestrationDecompiled {
written: report.written,
refused: report.refused.iter().map(RefusalRow::from).collect(),
notes: report.notes,
rows_byte: Some(report.rows_byte),
rows_emitted: Some(report.rows_emitted),
}
}
(OrchestrationHarness::Hermes, SourceFlavor::Native) => {
let mut loaded = from_hermes(source)?;
loaded.orchestration = orchestration;
loaded.vault.extend(vault);
let report = to_hermes(&loaded, dest, None)?;
OrchestrationDecompiled {
written: report.written,
refused: report.refused.iter().map(RefusalRow::from).collect(),
..OrchestrationDecompiled::default()
}
}
(OrchestrationHarness::Openclaw, SourceFlavor::Native) => {
let mut loaded = from_openclaw(source)?;
loaded.orchestration = orchestration;
loaded.vault.extend(vault);
let report = to_openclaw(&loaded, dest)?;
OrchestrationDecompiled {
written: report.written,
refused: report.refused.iter().map(RefusalRow::from).collect(),
notes: report.notes,
rows_byte: Some(report.rows_byte),
rows_emitted: Some(report.rows_emitted),
}
}
})
}
pub fn import(
from: OrchestrationHarness,
home: &Path,
into: &Path,
) -> Result<OrchestrationImported> {
let (orchestration, vault, sources): (
Orchestration,
BTreeMap<String, String>,
BTreeMap<String, PathBuf>,
) = match from {
OrchestrationHarness::Hermes => {
let loaded = from_hermes(home)?;
let sources = loaded
.io
.iter()
.filter_map(|(name, io)| Some((name.clone(), io.source_dir.clone()?)))
.collect();
(loaded.orchestration, loaded.vault, sources)
}
OrchestrationHarness::Openclaw => {
let loaded = from_openclaw(home)?;
let sources = loaded
.profiles
.iter()
.map(|(name, io)| {
let src = if name == "default" {
loaded.root.state_dir.clone()
} else {
io.source_dir.clone()
};
(name.clone(), src)
})
.collect();
(loaded.orchestration, loaded.vault, sources)
}
};
let mut loaded = LoadedHome {
orchestration,
vault,
io: BTreeMap::new(),
};
repoint(&mut loaded.orchestration, into);
save_home(&mut loaded, Some(into))?;
let mut carried = Vec::new();
for (name, profile) in &loaded.orchestration.profiles {
let Some(src) = sources.get(name) else {
continue;
};
let files: Vec<String> = profile
.residue
.files
.iter()
.filter(|rel| !OWNED_FILES.contains(&rel.as_str()))
.cloned()
.collect();
for rel in carry_unmodeled(&files, src, &profile.dir)? {
carried.push(if name == "default" {
rel
} else {
format!("profiles/{name}/{rel}")
});
}
}
Ok(OrchestrationImported {
vault_keys: keys(&loaded.vault),
orchestration: loaded.orchestration,
root: into.to_path_buf(),
carried,
})
}
pub fn export(
to: OrchestrationHarness,
root: &Path,
dest: &Path,
) -> Result<OrchestrationDecompiled> {
let loaded = load_home(root, Flavor::Orchestrator)?;
decompile(
to,
loaded.orchestration,
root,
SourceFlavor::Orchestrator,
dest,
loaded.vault,
)
}