use std::path::{Path, PathBuf};
use agent_migrate_core::{AgentKind, BundleRelativePath, FileKind, Scope};
use crate::error::AdapterError;
#[derive(Debug, Clone, Copy)]
pub(crate) struct TargetLayout {
target: AgentKind,
scope: Scope,
}
impl TargetLayout {
pub(crate) const fn new(target: AgentKind, scope: Scope) -> Self {
Self { target, scope }
}
pub(crate) fn path(
self,
kind: FileKind,
source: &BundleRelativePath,
) -> Result<BundleRelativePath, AdapterError> {
if self.is_materialized(source) {
return Ok(source.clone());
}
let suffix = source.as_path().components().skip(2).collect::<PathBuf>();
if suffix.as_os_str().is_empty() {
return Err(AdapterError::InternalInvariant);
}
let prefix = self.prefix(kind);
let target = if matches!(
kind,
FileKind::Mcp | FileKind::Settings | FileKind::Instructions
) {
PathBuf::from(prefix)
} else {
Path::new(prefix).join(suffix)
};
target
.to_string_lossy()
.parse::<BundleRelativePath>()
.map_err(Into::into)
}
fn is_materialized(self, source: &BundleRelativePath) -> bool {
let value = source.to_string();
match self.target {
AgentKind::Codex => value.starts_with(".codex/") || value == "AGENTS.md",
AgentKind::Pi => value.starts_with(".pi/") || value == "AGENTS.md",
AgentKind::Claude => {
value.starts_with(".claude/") || matches!(value.as_str(), ".mcp.json" | "CLAUDE.md")
}
}
}
fn prefix(self, kind: FileKind) -> &'static str {
match (self.target, self.scope, kind) {
(AgentKind::Codex, _, FileKind::Mcp | FileKind::Settings) => ".codex/config.toml",
(AgentKind::Codex, Scope::Global, FileKind::Instructions) => ".codex/AGENTS.md",
(AgentKind::Codex | AgentKind::Pi, Scope::Project, FileKind::Instructions) => {
"AGENTS.md"
}
(AgentKind::Codex, _, FileKind::Skill) => ".codex/skills",
(AgentKind::Codex, _, FileKind::Command) => ".codex/prompts",
(AgentKind::Codex, _, FileKind::Agent) => ".codex/agents",
(AgentKind::Codex, _, FileKind::Hook) => ".codex/hooks",
(AgentKind::Pi, Scope::Global, FileKind::Settings) => ".pi/agent/settings.json",
(AgentKind::Pi, Scope::Project, FileKind::Settings) => ".pi/settings.json",
(AgentKind::Pi, Scope::Global, FileKind::Mcp) => ".pi/agent/agent-migrate-mcp.json",
(AgentKind::Pi, Scope::Project, FileKind::Mcp) => ".pi/agent-migrate-mcp.json",
(AgentKind::Pi, Scope::Global, FileKind::Instructions) => ".pi/agent/AGENTS.md",
(AgentKind::Pi, Scope::Global, FileKind::Skill) => ".pi/agent/skills",
(AgentKind::Pi, Scope::Project, FileKind::Skill) => ".pi/skills",
(AgentKind::Pi, Scope::Global, FileKind::Command) => ".pi/agent/prompts",
(AgentKind::Pi, Scope::Project, FileKind::Command) => ".pi/prompts",
(AgentKind::Pi, _, FileKind::Agent) => ".pi/agent/prompts/agents",
(AgentKind::Pi, _, FileKind::Hook) => ".agent-migrate/preserved/hooks",
(AgentKind::Claude, _, FileKind::Settings) => ".claude/settings.json",
(AgentKind::Claude, Scope::Global, FileKind::Mcp) => ".claude/mcp.json",
(AgentKind::Claude, Scope::Project, FileKind::Mcp) => ".mcp.json",
(AgentKind::Claude, Scope::Global, FileKind::Instructions) => ".claude/CLAUDE.md",
(AgentKind::Claude, Scope::Project, FileKind::Instructions) => "CLAUDE.md",
(AgentKind::Claude, _, FileKind::Skill) => ".claude/skills",
(AgentKind::Claude, _, FileKind::Command) => ".claude/commands",
(AgentKind::Claude, _, FileKind::Agent) => ".claude/agents",
(AgentKind::Claude, _, FileKind::Hook) => ".claude/hooks",
(_, _, FileKind::Passthrough) => ".agent-migrate/preserved/passthrough",
}
}
}