use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use supercode_interchange::ontology::ArtifactFidelity;
use supercode_interchange::world::codec::folder::OWNED_FILES;
use supercode_interchange::world::codec::{
carry_unmodeled, from_hermes, from_openclaw, load_home, save_home, to_hermes, to_openclaw,
Flavor, LoadedHome, Refusal,
};
use supercode_interchange::world::World;
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 WorldHarness {
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 WorldRead {
pub world: World,
pub vault_keys: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorldSaved {
pub written: bool,
pub root: PathBuf,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorldDecompiled {
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 WorldImported {
pub world: World,
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(world: &mut World, root: &Path) {
world.root = root.to_path_buf();
for (name, profile) in world.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<WorldRead> {
let loaded = load_home(root, flavor.into())?;
Ok(WorldRead {
vault_keys: keys(&loaded.vault),
world: loaded.world,
})
}
pub fn save(root: &Path, world: World, vault: BTreeMap<String, String>) -> Result<WorldSaved> {
let mut loaded = if root.is_dir() {
load_home(root, Flavor::Orchestrator)?
} else {
LoadedHome {
world: world.clone(),
vault: BTreeMap::new(),
io: BTreeMap::new(),
}
};
loaded.world = world;
repoint(&mut loaded.world, root);
loaded.vault.extend(vault);
save_home(&mut loaded, Some(root))?;
Ok(WorldSaved {
written: true,
root: root.to_path_buf(),
})
}
pub fn compile(from: WorldHarness, home: &Path) -> Result<WorldRead> {
Ok(match from {
WorldHarness::Hermes => {
let loaded = from_hermes(home)?;
WorldRead {
vault_keys: keys(&loaded.vault),
world: loaded.world,
}
}
WorldHarness::Openclaw => {
let loaded = from_openclaw(home)?;
WorldRead {
vault_keys: keys(&loaded.vault),
world: loaded.world,
}
}
})
}
pub fn decompile(
to: WorldHarness,
world: World,
source: &Path,
source_flavor: SourceFlavor,
dest: &Path,
vault: BTreeMap<String, String>,
) -> Result<WorldDecompiled> {
Ok(match (to, source_flavor) {
(WorldHarness::Hermes, SourceFlavor::Orchestrator) => {
let mut loaded = load_home(source, HomeFlavor::Orchestrator.into())?;
loaded.world = world;
loaded.vault.extend(vault);
let report = to_hermes(&loaded, dest, None)?;
WorldDecompiled {
written: report.written,
refused: report.refused.iter().map(RefusalRow::from).collect(),
..WorldDecompiled::default()
}
}
(WorldHarness::Openclaw, SourceFlavor::Orchestrator) => {
let loaded = supercode_interchange::world::codec::OpenclawLoaded::from_world(world, {
let mut v = load_home(source, HomeFlavor::Orchestrator.into())?.vault;
v.extend(vault);
v
});
let report = to_openclaw(&loaded, dest)?;
WorldDecompiled {
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),
}
}
(WorldHarness::Hermes, SourceFlavor::Native) => {
let mut loaded = from_hermes(source)?;
loaded.world = world;
loaded.vault.extend(vault);
let report = to_hermes(&loaded, dest, None)?;
WorldDecompiled {
written: report.written,
refused: report.refused.iter().map(RefusalRow::from).collect(),
..WorldDecompiled::default()
}
}
(WorldHarness::Openclaw, SourceFlavor::Native) => {
let mut loaded = from_openclaw(source)?;
loaded.world = world;
loaded.vault.extend(vault);
let report = to_openclaw(&loaded, dest)?;
WorldDecompiled {
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: WorldHarness, home: &Path, into: &Path) -> Result<WorldImported> {
let (world, vault, sources): (World, BTreeMap<String, String>, BTreeMap<String, PathBuf>) =
match from {
WorldHarness::Hermes => {
let loaded = from_hermes(home)?;
let sources = loaded
.io
.iter()
.filter_map(|(name, io)| Some((name.clone(), io.source_dir.clone()?)))
.collect();
(loaded.world, loaded.vault, sources)
}
WorldHarness::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.world, loaded.vault, sources)
}
};
let mut loaded = LoadedHome {
world,
vault,
io: BTreeMap::new(),
};
repoint(&mut loaded.world, into);
save_home(&mut loaded, Some(into))?;
let mut carried = Vec::new();
for (name, profile) in &loaded.world.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(WorldImported {
vault_keys: keys(&loaded.vault),
world: loaded.world,
root: into.to_path_buf(),
carried,
})
}
pub fn export(to: WorldHarness, root: &Path, dest: &Path) -> Result<WorldDecompiled> {
let loaded = load_home(root, Flavor::Orchestrator)?;
decompile(
to,
loaded.world,
root,
SourceFlavor::Orchestrator,
dest,
loaded.vault,
)
}