use anyhow::{Context, Result};
use colored::*;
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use std::path::Path;
use std::process::Command;
use std::sync::mpsc::channel;
use std::time::Duration;
pub fn watch_project(platform: &str) -> Result<()> {
println!("{}", format!("👀 Rust watch mode for {}...", platform).bright_green().bold());
println!();
println!(" This watches your Rust code (core/ and ffi/) and rebuilds on changes.");
println!(" For Swift changes, use Xcode directly - it has native hot reload.");
println!();
println!("{}", " → Initial Rust build...".bright_blue());
crate::commands::build::build_platform(platform, false)?;
println!("{}", " ✓ Build complete!".green());
println!();
if platform == "ios" || platform == "macos" {
println!("{}", " → Opening Xcode...".bright_blue());
open_xcode_project(platform)?;
println!();
println!("{}", " ✓ Xcode opened!".green());
println!();
println!("{}", " 🚀 IMPORTANT: Press Cmd+R in Xcode to RUN the app!".bright_yellow().bold());
println!(" (Don't just look at the preview - actually run it)");
println!();
println!(" Development workflow:");
println!(" • Edit Swift files → Changes appear instantly (native hot reload)");
println!(" • Edit Rust files → This watcher rebuilds → Press Cmd+B in Xcode");
println!();
println!(" Press Ctrl+C to stop watching");
println!();
} else if platform == "android" {
println!("{}", " → Opening Android Studio...".bright_blue());
open_android_studio()?;
println!();
println!("{}", " ✓ Android Studio opened!".green());
println!();
println!("{}", " 🚀 IMPORTANT: Press ▶️ in Android Studio to RUN the app!".bright_yellow().bold());
println!();
println!(" Development workflow:");
println!(" • Edit Kotlin files → Changes appear on rebuild (Compose hot reload)");
println!(" • Edit Rust files → This watcher rebuilds → Rebuild in Android Studio");
println!();
println!(" Press Ctrl+C to stop watching");
println!();
} else if platform == "linux" {
println!("{}", " → Launching Linux app...".bright_blue());
launch_linux_app_background()?;
println!();
println!("{}", " ✓ App launched!".green());
println!();
println!("{}", " 🚀 Development mode active!".bright_yellow().bold());
println!();
println!(" Development workflow:");
println!(" • Edit Python files → App auto-restarts");
println!(" • Edit Rust files → This watcher rebuilds → App auto-restarts");
println!();
println!(" Press Ctrl+C to stop watching");
println!();
}
let (tx, rx) = channel();
let mut watcher = RecommendedWatcher::new(
move |res: Result<Event, notify::Error>| {
if let Ok(event) = res {
let _ = tx.send(event);
}
},
Config::default(),
)?;
watcher.watch(Path::new("core/src"), RecursiveMode::Recursive)?;
watcher.watch(Path::new("ffi/src"), RecursiveMode::Recursive)?;
println!("{}", " 👀 Watching Rust files...".bright_cyan());
println!();
let mut last_rebuild = std::time::Instant::now();
let debounce_duration = Duration::from_millis(500);
loop {
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(event) => {
if matches!(
event.kind,
notify::EventKind::Modify(_) | notify::EventKind::Create(_)
) {
let now = std::time::Instant::now();
if now.duration_since(last_rebuild) > debounce_duration {
println!();
println!("{}", " 🔄 Rust changes detected, rebuilding...".yellow());
match crate::commands::build::build_platform(platform, false) {
Ok(_) => {
println!();
if platform == "linux" {
println!("{}", " ✓ Rust rebuild complete! Restarting app...".green());
match restart_linux_app() {
Ok(_) => {
println!("{}", " ✓ App restarted!".green());
}
Err(e) => {
println!("{}", format!(" ✗ Failed to restart app: {}", e).red());
}
}
} else {
println!("{}", " ✓ Rust rebuild complete! Press Cmd+B in Xcode to use new code.".green());
}
println!();
}
Err(e) => {
println!();
println!("{}", format!(" ✗ Build failed: {}", e).red());
println!("{}", " → Fix the error and save again...".yellow());
println!();
}
}
last_rebuild = now;
}
}
}
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
continue;
}
Err(e) => {
anyhow::bail!("Watch error: {}", e);
}
}
}
}
fn open_xcode_project(platform: &str) -> Result<()> {
let platform_dir = match platform {
"ios" => "platforms/ios",
"macos" => "platforms/macos",
_ => return Ok(()),
};
if !std::path::Path::new(platform_dir).exists() {
anyhow::bail!(
"Platform directory '{}' not found. Make sure you're in the project root directory.",
platform_dir
);
}
let xcodeproj = std::fs::read_dir(platform_dir)
.context(format!("Failed to read directory '{}'", platform_dir))?
.filter_map(|e| e.ok())
.find(|e| {
e.path()
.extension()
.and_then(|s| s.to_str())
.map(|s| s == "xcodeproj")
.unwrap_or(false)
})
.map(|e| e.path())
.context(format!("Could not find .xcodeproj file in '{}'", platform_dir))?;
Command::new("open")
.arg(xcodeproj)
.status()
.context("Failed to open Xcode")?;
Ok(())
}
fn open_android_studio() -> Result<()> {
let android_dir = "platforms/android";
if !std::path::Path::new(android_dir).exists() {
anyhow::bail!(
"Android directory '{}' not found. Make sure you're in the project root directory.",
android_dir
);
}
Command::new("open")
.arg("-a")
.arg("Android Studio")
.arg(android_dir)
.status()
.context("Failed to open Android Studio")?;
Ok(())
}
fn launch_linux_app_background() -> Result<()> {
use std::process::Stdio;
let lib_path = std::fs::read_dir("target/debug")
.context("Failed to read target directory")?
.filter_map(|e| e.ok())
.find(|e| {
let name = e.file_name();
let name_str = name.to_string_lossy();
name_str.starts_with("lib") && name_str.ends_with("ffi.so")
})
.map(|e| e.path())
.context("Could not find FFI library")?;
std::fs::copy(&lib_path, "platforms/linux/libffi.so")
.context("Failed to copy library to platforms/linux")?;
Command::new("python3")
.arg("main.py")
.current_dir("platforms/linux")
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.context("Failed to launch app")?;
std::thread::sleep(Duration::from_millis(500));
Ok(())
}
fn restart_linux_app() -> Result<()> {
let _ = Command::new("pkill")
.args(&["-f", "python3.*main.py"])
.status();
std::thread::sleep(Duration::from_millis(200));
launch_linux_app_background()
}