use anyhow::Result;
use rusqlite::Connection;
use std::io::{self, Write};
use crate::config::Config;
use crate::model::Project;
use crate::project::find_git_root;
pub fn detect_stack(path: &str) -> String {
let root = std::path::Path::new(path);
let mut stacks = vec![];
let markers = [
("Cargo.toml", "Rust"),
("package.json", "Node.js/JS"),
("pyproject.toml", "Python"),
("requirements.txt", "Python"),
("go.mod", "Go"),
("pom.xml", "Java/Maven"),
("build.gradle", "Java/Gradle"),
("Gemfile", "Ruby"),
("composer.json", "PHP"),
("pubspec.yaml", "Dart/Flutter"),
("*.swift", "Swift"),
("CMakeLists.txt", "C/C++"),
("mix.exs", "Elixir"),
];
for (file, label) in &markers {
if file.contains('*') {
} else if root.join(file).exists() {
stacks.push(*label);
}
}
if let Ok(rd) = std::fs::read_dir(root) {
for entry in rd.flatten() {
if entry
.path()
.extension()
.and_then(|e| e.to_str())
.map(|e| e == "swift")
.unwrap_or(false)
&& !stacks.contains(&"Swift")
{
stacks.push("Swift");
}
}
}
if stacks.is_empty() {
"unknown".to_string()
} else {
stacks.join(", ")
}
}
fn prompt(msg: &str, default: Option<&str>) -> Result<String> {
let prompt_str = if let Some(d) = default {
format!("{msg} [{d}]: ")
} else {
format!("{msg}: ")
};
print!("{prompt_str}");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let trimmed = input.trim().to_string();
if trimmed.is_empty() {
Ok(default.unwrap_or("").to_string())
} else {
Ok(trimmed)
}
}
pub fn run(
conn: &Connection,
cfg: &Config,
name_override: Option<&str>,
goal_override: Option<&str>,
yes: bool,
) -> Result<()> {
let cwd = std::env::current_dir()?;
let git_root = find_git_root(&cwd);
let (resolved_name, resolved_path) = crate::project::project_identity_for_dir(&cwd, cfg);
let project_name = name_override.map(str::to_string).unwrap_or(resolved_name);
let project_path = Some(resolved_path);
if git_root.is_none() {
println!(
"Note: not inside a git repo — initializing the current folder as project '{}'.",
project_name
);
}
let detected_stack = project_path
.as_deref()
.map(detect_stack)
.unwrap_or_else(|| "unknown".to_string());
println!("Initializing project: {}", project_name);
println!("Detected stack: {}", detected_stack);
let existing = crate::db::get_project(conn, &project_name)?;
let goal = if let Some(g) = goal_override {
g.to_string()
} else if yes {
existing
.as_ref()
.and_then(|p| p.goal.clone())
.unwrap_or_default()
} else {
let current_goal = existing.as_ref().and_then(|p| p.goal.as_deref());
prompt("What is this project? (one-line goal)", current_goal)?
};
let notes = if yes {
existing
.as_ref()
.and_then(|p| p.notes.clone())
.unwrap_or_default()
} else {
let current_notes = existing.as_ref().and_then(|p| p.notes.as_deref());
prompt("Any conventions or notes? (optional)", current_notes)?
};
let project = Project {
name: project_name.clone(),
path: project_path,
goal: if goal.is_empty() {
None
} else {
Some(goal.clone())
},
stack: Some(detected_stack.clone()),
conventions: None,
notes: if notes.is_empty() { None } else { Some(notes) },
initialized_at: Some(chrono::Utc::now()),
last_seen: Some(chrono::Utc::now()),
};
crate::db::save_project_profile(conn, &project)?;
println!("✔ Project '{}' profile saved.", project_name);
if let Some(g) = &project.goal {
println!(" Goal: {g}");
}
println!(" Stack: {detected_stack}");
Ok(())
}