use super::merge::{
codex_hooks_feature_is_enabled, merge_claude_settings_text_with_profile,
merge_codex_hooks_text_with_profile,
};
use super::model::{AssetStatus, RenderedAsset};
use super::profiles::{CCD_COMPAT_PROFILE, LifecycleProfile};
use super::{CLAUDE_TARGET_SETTINGS, CODEX_TARGET_CONFIG, CODEX_TARGET_HOOKS};
pub fn byte_equal_asset_status(
asset: &RenderedAsset,
existing_content: Option<&str>,
existing_mode: Option<u32>,
) -> AssetStatus {
let Some(content) = existing_content else {
return AssetStatus::Missing;
};
if content != asset.contents {
return AssetStatus::Drifted;
}
if let Some(expected) = asset.mode {
match existing_mode {
Some(actual) if actual == expected => {}
_ => return AssetStatus::Drifted,
}
}
AssetStatus::Present
}
pub fn claude_settings_status(existing: Option<&str>) -> AssetStatus {
claude_settings_status_with_profile(existing, &CCD_COMPAT_PROFILE)
}
pub fn claude_settings_status_with_profile(
existing: Option<&str>,
profile: &LifecycleProfile,
) -> AssetStatus {
let Some(content) = existing else {
return AssetStatus::Missing;
};
match merge_claude_settings_text_with_profile(Some(content), false, profile) {
Ok(Some(merged)) if merged.rendered == content => AssetStatus::Present,
Ok(_) | Err(_) => AssetStatus::Drifted,
}
}
pub fn codex_hooks_status(existing: Option<&str>) -> AssetStatus {
codex_hooks_status_with_profile(existing, &CCD_COMPAT_PROFILE)
}
pub fn codex_hooks_status_with_profile(
existing: Option<&str>,
profile: &LifecycleProfile,
) -> AssetStatus {
let Some(content) = existing else {
return AssetStatus::Missing;
};
match merge_codex_hooks_text_with_profile(Some(content), false, profile) {
Ok(Some(merged)) if merged.rendered == content => AssetStatus::Present,
Ok(_) | Err(_) => AssetStatus::Drifted,
}
}
pub fn codex_config_status(existing: Option<&str>) -> AssetStatus {
let Some(content) = existing else {
return AssetStatus::Missing;
};
match content.parse::<toml::Value>() {
Ok(parsed) if codex_hooks_feature_is_enabled(&parsed) => AssetStatus::Present,
Ok(_) | Err(_) => AssetStatus::Drifted,
}
}
pub fn asset_status(
asset: &RenderedAsset,
existing_content: Option<&str>,
existing_mode: Option<u32>,
) -> AssetStatus {
match asset.relative_path {
CLAUDE_TARGET_SETTINGS => claude_settings_status(existing_content),
CODEX_TARGET_CONFIG => codex_config_status(existing_content),
CODEX_TARGET_HOOKS => codex_hooks_status(existing_content),
_ => byte_equal_asset_status(asset, existing_content, existing_mode),
}
}
pub fn aggregate_status<I: IntoIterator<Item = AssetStatus>>(statuses: I) -> AssetStatus {
let mut saw_missing = false;
let mut saw_drift = false;
let mut saw_any = false;
for s in statuses {
saw_any = true;
match s {
AssetStatus::Drifted => saw_drift = true,
AssetStatus::Missing => saw_missing = true,
AssetStatus::InvalidMode => return AssetStatus::InvalidMode,
AssetStatus::Present | AssetStatus::NotApplicable => {}
}
}
if !saw_any {
return AssetStatus::NotApplicable;
}
if saw_drift {
AssetStatus::Drifted
} else if saw_missing {
AssetStatus::Missing
} else {
AssetStatus::Present
}
}