use super::exec::{execute_claim, execute_create, execute_ensure_workspace, resolve_name};
use super::goal::compose_prepared;
use super::{BallSpec, Deps, Payload, PlanInputs, Prepared, StartError, StartInputs, Step, Target};
use crate::actions::verbs::log_step_failure;
use crate::binding::{work_worktree_path, workspace_path};
use crate::cli_outbound::Binary;
use crate::names::Rng;
use crate::projects::join::JoinState;
use crate::world::toolgate::ToolchainState;
use crate::world::{layout_under, seed};
use std::path::{Path, PathBuf};
const GATE_STEP: &str = "gate";
pub fn resolve_worktree(
payload: &Payload,
balls_state_root: &Path,
name: &str,
claimed: Option<PathBuf>,
) -> Option<PathBuf> {
let Payload::Ball {
project,
ball: BallSpec::Existing { id, .. },
} = payload
else {
return None;
};
Some(claimed.unwrap_or_else(|| existing_worktree(balls_state_root, project, id, name)))
}
fn existing_worktree(balls_state_root: &Path, project: &Path, id: &str, name: &str) -> PathBuf {
let canonical = work_worktree_path(balls_state_root, project, id, None);
let suffixed = work_worktree_path(balls_state_root, project, id, Some(name));
if !canonical.exists() && suffixed.exists() {
suffixed
} else {
canonical
}
}
pub fn prepare(
deps: &Deps,
inputs: &StartInputs,
rng: &mut dyn Rng,
ts: &str,
) -> Result<Prepared, StartError> {
require_tools(
&deps.gate,
&inputs.payload,
&deps.state_root,
ts,
&inputs.home,
)?;
let name = resolve_name(inputs, rng, &deps.state_root, ts)?;
let workspace = resolve_workspace(&inputs.target, &inputs.yog_data_root, &name);
run(deps, &resolved(inputs, name, workspace), ts)
}
fn resolve_workspace(target: &Target, yog_data_root: &Path, name: &str) -> PathBuf {
match target {
Target::Existing { workspace } => workspace.clone(),
Target::Mint => workspace_path(yog_data_root, name),
}
}
fn require_tools(
gate: &ToolchainState,
payload: &Payload,
state_root: &Path,
ts: &str,
cwd: &Path,
) -> Result<(), StartError> {
let mut needed = vec![Binary::Lernie];
if matches!(payload, Payload::Ball { .. }) {
needed.push(Binary::Bl);
}
for binary in needed {
if let Err(refusal) = gate.require(binary) {
log_step_failure(state_root, ts, cwd, GATE_STEP, &refusal.to_string())?;
return Err(StartError::GateRefused(refusal));
}
}
Ok(())
}
fn run(deps: &Deps, inputs: &PlanInputs, ts: &str) -> Result<Prepared, StartError> {
let mut claimed: Option<PathBuf> = None;
for step in super::plan(inputs) {
match step {
Step::EnsureSeeded => {
let layout = layout_under(&inputs.yog_data_root);
seed::ensure_seeded(&deps.lernie, &deps.state_root, ts, &layout)?;
}
Step::EnsureWorkspace { workspace } => {
execute_ensure_workspace(&deps.lernie, &deps.state_root, ts, &workspace)?;
}
Step::Create {
project,
title,
body,
} => {
let id = execute_create(
&deps.bl,
&deps.state_root,
ts,
&project,
&title,
&body,
&inputs.name,
)?;
return run(deps, &with_minted(inputs, &project, id, title, body), ts);
}
Step::Claim { project, id, name } => {
claimed = Some(
execute_claim(
&deps.bl,
&deps.state_root,
ts,
&project,
&id,
&name,
&inputs.balls_state_root,
)?
.worktree,
);
}
Step::Prompt { .. } => {}
}
}
let worktree = resolve_worktree(
&inputs.payload,
&inputs.balls_state_root,
&inputs.name,
claimed,
);
Ok(compose_prepared(inputs, worktree.as_deref()))
}
fn resolved(inputs: &StartInputs, name: String, workspace: PathBuf) -> PlanInputs {
PlanInputs {
name,
workspace,
payload: inputs.payload.clone(),
home: inputs.home.clone(),
yog_data_root: inputs.yog_data_root.clone(),
balls_state_root: inputs.balls_state_root.clone(),
}
}
fn with_minted(
inputs: &PlanInputs,
project: &Path,
id: String,
title: String,
body: String,
) -> PlanInputs {
PlanInputs {
payload: Payload::Ball {
project: project.to_path_buf(),
ball: BallSpec::Existing {
id,
title,
body,
join: JoinState::ReadyStartable,
},
},
..inputs.clone()
}
}