exp 0.1.2

CLI experiment tracker for agent runs, prompt testing, and simulations
use anyhow::Result;
use rusqlite::Connection;

use crate::db;
use crate::display;

pub fn set(
    conn: &Connection,
    experiment: &str,
    controls: &[(String, String)],
    independents: &[(String, String)],
) -> Result<()> {
    let exp_id = db::resolve_experiment_id(conn, experiment)?;

    for (name, value) in controls {
        upsert_variable(conn, &exp_id, name, "control", value)?;
    }
    for (name, values) in independents {
        upsert_variable(conn, &exp_id, name, "independent", values)?;
    }

    Ok(())
}

fn upsert_variable(conn: &Connection, exp_id: &str, name: &str, role: &str, values: &str) -> Result<()> {
    let existing: Option<String> = conn
        .query_row(
            "SELECT id FROM variables WHERE exp_id = ?1 AND name = ?2",
            rusqlite::params![exp_id, name],
            |row| row.get(0),
        )
        .ok();

    if let Some(id) = existing {
        conn.execute(
            "UPDATE variables SET role = ?1, val_list = ?2 WHERE id = ?3",
            rusqlite::params![role, values, id],
        )?;
    } else {
        let id = db::new_id();
        conn.execute(
            "INSERT INTO variables (id, exp_id, name, role, val_list) VALUES (?1, ?2, ?3, ?4, ?5)",
            rusqlite::params![id, exp_id, name, role, values],
        )?;
    }

    Ok(())
}

pub fn list(conn: &Connection, experiment: &str) -> Result<()> {
    let exp_id = db::resolve_experiment_id(conn, experiment)?;

    let mut stmt = conn.prepare(
        "SELECT name, role, val_list FROM variables WHERE exp_id = ?1 ORDER BY role, name",
    )?;
    let rows: Vec<Vec<String>> = stmt
        .query_map([&exp_id], |row| {
            Ok(vec![
                row.get::<_, String>(0)?,
                row.get::<_, String>(1)?,
                row.get::<_, Option<String>>(2)?.unwrap_or_default(),
            ])
        })?
        .collect::<Result<_, _>>()?;

    if rows.is_empty() {
        println!("No variables defined.");
        return Ok(());
    }

    let table = display::build_table(&["name", "role", "values"], &rows);
    println!("{table}");
    Ok(())
}

pub fn rm(conn: &Connection, experiment: &str, name: &str) -> Result<()> {
    let exp_id = db::resolve_experiment_id(conn, experiment)?;

    let affected = conn.execute(
        "DELETE FROM variables WHERE exp_id = ?1 AND name = ?2",
        rusqlite::params![exp_id, name],
    )?;

    if affected == 0 {
        anyhow::bail!("variable not found: {name}");
    }

    Ok(())
}