chuggin 0.5.3

A terminal harness for persistent coding projects with fresh-context local model stages
use crate::{runner, setup};
use anyhow::{Context, Result};
use dialoguer::{Select, theme::ColorfulTheme};
use std::{
    io::{self, IsTerminal, Write},
    sync::{
        Arc,
        atomic::{AtomicBool, Ordering},
    },
};

pub fn select(title: &str, items: &[String], default: usize) -> Result<Option<usize>> {
    if crate::ui::active() {
        return crate::ui::select(title, items, default);
    }
    Ok(Select::with_theme(&ColorfulTheme::default())
        .with_prompt(title)
        .items(items)
        .default(default)
        .interact_opt()?)
}
pub fn pause() -> Result<()> {
    if crate::ui::active() {
        crate::ui::select("Continue", &["Back".into()], 0)?;
        return Ok(());
    }
    print!("\nPress Enter to return to the menu…");
    io::stdout().flush()?;
    let mut line = String::new();
    io::stdin().read_line(&mut line)?;
    Ok(())
}
pub fn home(stop: Arc<AtomicBool>, running: Arc<AtomicBool>) -> Result<()> {
    anyhow::ensure!(
        io::stdin().is_terminal() && io::stdout().is_terminal(),
        "Open chuggin in a terminal to use the menu. For unattended runs use chuggin run --forever."
    );
    let _screen = crate::ui::Screen::enter()?;
    loop {
        let project = setup::find_project()?;
        let settings = setup::settings()?;
        let items = vec![
            if project.is_some() {
                "Resume project"
            } else {
                "Set up this project"
            }
            .into(),
            "Project goal".into(),
            "Progress".into(),
            "Choose model".into(),
            "Settings".into(),
            "Quit".into(),
        ];
        let root = std::env::current_dir()?.display().to_string();
        let effective = project.as_ref().and_then(|p| runner::load(p).ok());
        let model = effective
            .as_ref()
            .map(|c| c.model.as_str())
            .unwrap_or(&settings.model);
        let model = if model.trim().is_empty() {
            "Choose a model during setup"
        } else {
            model
        };
        let status = if let Some(c) = &effective {
            std::fs::read(c.state_dir.join("state.json"))
                .ok()
                .and_then(|b| serde_json::from_slice::<serde_json::Value>(&b).ok())
                .map(|v| {
                    format!(
                        "Ready after cycle {}\nYour accepted work is saved.",
                        v["cycle"]
                    )
                })
                .unwrap_or("Goal accepted. Ready for the first cycle.".into())
        } else {
            "A fresh project. Start with a goal; Chuggin will help shape it.".into()
        };
        let Some(choice) = crate::ui::home_select(&items, &root, model, &status)? else {
            return Ok(());
        };
        let action: Result<()> = (|| {
            match choice {
                0 => {
                    if let Some(path) = project.as_ref() {
                        stop.store(false, Ordering::SeqCst);
                        running.store(true, Ordering::SeqCst);
                        let result = crate::ui::dashboard(path, stop.clone(), running.clone());
                        running.store(false, Ordering::SeqCst);
                        result?;
                    } else {
                        setup::wizard()?;
                    }
                }
                1 => {
                    if let Some(path) = project.as_ref() {
                        crate::ui::show("Project goal", &runner::load(path)?.goal)?;
                    } else {
                        crate::ui::show(
                            "Project goal",
                            "Set up this project to draft and accept its goal.",
                        )?;
                    }
                }
                2 => {
                    if let Some(path) = project.as_ref() {
                        let c = runner::load(path)?;
                        let progress = std::fs::read_to_string(c.state_dir.join("state.json"))
                            .unwrap_or("This project has not started yet.".into());
                        let text=serde_json::from_str::<serde_json::Value>(&progress).ok().map(|v|{
                            let mut s=format!("Cycle {}\nAccepted branch: {}\nWorkspace: {}\n\nRecent attempts\n",v["cycle"],v["accepted_branch"].as_str().unwrap_or(""),v["accepted_workspace"].as_str().unwrap_or(""));
                            if let Some(items)=v["recent"].as_array(){for o in items.iter().rev(){s.push_str(&format!("\n#{} · {}\n{}\n",o["cycle"],o["disposition"].as_str().unwrap_or(""),o["task"].as_str().unwrap_or("")));}}s
                        }).unwrap_or(progress);
                        crate::ui::show("Saved progress", &text)?;
                    } else {
                        crate::ui::show("Saved progress", "This project has not started yet.")?;
                    }
                }
                3 => setup::choose_model()?,
                4 => setup::settings_menu()?,
                _ => {}
            }
            Ok(())
        })();
        if choice == 5 {
            return Ok(());
        }
        if let Err(e) = action {
            crate::ui::notice(format!("\n{e:#}"));
            pause().context("Could not return to menu")?;
        }
    }
}