use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
pub const STATE_FILE_NAME: &str = ".tru-ols-state.json";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SavedState {
pub stained: PathBuf,
pub mixing_source: usize,
pub controls: Option<PathBuf>,
pub single_stain_controls: Option<PathBuf>,
pub mixing_matrix: Option<PathBuf>,
pub use_spill: bool,
pub unstained: Option<PathBuf>,
pub detectors: Vec<String>,
pub endmembers: Vec<String>,
pub cutoff_percentile: f64,
pub strategy: String,
pub autofluorescence: String,
pub control_assignments: Option<Vec<(String, PathBuf)>>,
pub output: Option<PathBuf>,
pub auto_gate: bool,
pub plot: bool,
pub plot_format: String,
pub plot_output_dir: Option<PathBuf>,
pub compare_ols: bool,
pub plot_both: bool,
pub debug_control_plots: bool,
pub peak_detection: bool,
pub peak_threshold: f64,
pub peak_bias: f64,
pub peak_bias_negative: f64,
pub use_negative_events: bool,
pub autofluorescence_mode: String,
pub af_weight: f64,
pub min_negative_events: usize,
pub export_mixing_matrix: Option<PathBuf>,
}
pub fn state_file_path() -> Result<PathBuf> {
let cwd = std::env::current_dir().context("Failed to read current working directory")?;
Ok(cwd.join(STATE_FILE_NAME))
}
pub fn load() -> Result<Option<SavedState>> {
let path = state_file_path()?;
if !path.exists() {
return Ok(None);
}
let raw = std::fs::read_to_string(&path)
.with_context(|| format!("Failed to read state file {}", path.display()))?;
let state = serde_json::from_str::<SavedState>(&raw)
.with_context(|| format!("Failed to parse state file {}", path.display()))?;
Ok(Some(state))
}
pub fn save(state: &SavedState) -> Result<PathBuf> {
let path = state_file_path()?;
let tmp = path.with_extension("json.tmp");
let json = serde_json::to_string_pretty(state).context("Failed to serialize state")?;
std::fs::write(&tmp, json)
.with_context(|| format!("Failed to write {}", tmp.display()))?;
std::fs::rename(&tmp, &path)
.with_context(|| format!("Failed to rename state file to {}", path.display()))?;
Ok(path)
}
pub fn short_path(p: &Path) -> String {
p.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| p.display().to_string())
}