use crate::core::agent::types::AgentType;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::SystemTime;
#[derive(Debug, Clone)]
pub struct PlanningWorkflowState {
is_active: Arc<AtomicBool>,
current_plan_file: Arc<tokio::sync::RwLock<Option<PathBuf>>>,
plan_baseline: Arc<tokio::sync::RwLock<Option<SystemTime>>>,
workspace_root: PathBuf,
}
impl PlanningWorkflowState {
pub fn new(workspace_root: PathBuf) -> Self {
Self {
is_active: Arc::new(AtomicBool::new(false)),
current_plan_file: Arc::new(tokio::sync::RwLock::new(None)),
plan_baseline: Arc::new(tokio::sync::RwLock::new(None)),
workspace_root,
}
}
pub fn is_active(&self) -> bool {
self.is_active.load(Ordering::Relaxed)
}
pub fn enable(&self) {
self.is_active.store(true, Ordering::Relaxed);
}
pub fn disable(&self) {
self.is_active.store(false, Ordering::Relaxed);
}
pub fn effective_agent_type(&self) -> Option<AgentType> {
if self.is_active() { Some(AgentType::Plan) } else { None }
}
pub fn workspace_root(&self) -> Option<PathBuf> {
if self.workspace_root.as_os_str().is_empty() {
None
} else {
Some(self.workspace_root.clone())
}
}
pub fn plans_dir(&self) -> PathBuf {
if self.workspace_root.as_os_str().is_empty() {
std::env::temp_dir()
.join("vtcode-plans")
.join(workspace_slug_for_tmp(&self.workspace_root))
} else {
self.workspace_root.join(".vtcode").join("plans")
}
}
pub async fn set_plan_file(&self, path: Option<PathBuf>) {
let mut guard = self.current_plan_file.write().await;
*guard = path;
}
pub async fn set_plan_baseline(&self, baseline: Option<SystemTime>) {
let mut guard = self.plan_baseline.write().await;
*guard = baseline;
}
pub async fn plan_baseline(&self) -> Option<SystemTime> {
*self.plan_baseline.read().await
}
pub async fn get_plan_file(&self) -> Option<PathBuf> {
self.current_plan_file.read().await.clone()
}
}
fn workspace_slug_for_tmp(workspace_root: &Path) -> String {
let fallback = "workspace".to_string();
let candidate = workspace_root
.file_name()
.and_then(|name| name.to_str())
.map(|name| name.to_string())
.unwrap_or(fallback);
let sanitized = candidate
.chars()
.map(|ch| {
if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
ch
} else {
'-'
}
})
.collect::<String>();
if sanitized.trim_matches('-').is_empty() {
"workspace".to_string()
} else {
sanitized
}
}