use std::io::Write as _;
use anyhow::Result;
use serde_json::json;
use super::{consecutive_exit2_blocks, Chunk, HookInput, LedgerLine, Mode, Vehicle};
pub(crate) const DEFAULT_BLOCK_CAP: i64 = 8;
pub(crate) const BLOCK_CAP_ENV: &str = "CLAUDE_CODE_STOP_HOOK_BLOCK_CAP";
pub(crate) const HELD_BLOCK_CAP: &str = "block-cap";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct VehicleChoice {
pub(crate) vehicle: Vehicle,
pub(crate) block_count: Option<u32>,
pub(crate) held_block_cap: bool,
}
pub(crate) fn block_cap(raw: Option<&str>) -> i64 {
raw.and_then(|s| s.trim().parse::<i64>().ok())
.unwrap_or(DEFAULT_BLOCK_CAP)
}
pub(crate) fn exit2_fits(
cap: i64,
consecutive: usize,
prior_exit2: bool,
stop_hook_active: bool,
) -> bool {
if cap <= 0 || stop_hook_active || prior_exit2 {
return false;
}
let used = i64::try_from(consecutive).unwrap_or(i64::MAX);
used.saturating_add(1) < cap
}
pub(crate) fn choose_vehicle(
hook: &HookInput,
chunk: &Chunk,
ledger: &[LedgerLine],
cap: i64,
) -> VehicleChoice {
let additional = VehicleChoice {
vehicle: Vehicle::AdditionalContext,
block_count: None,
held_block_cap: false,
};
if !hook.is_stop_family() || chunk.mode != Mode::Queue {
return additional;
}
let consecutive = consecutive_exit2_blocks(ledger);
if exit2_fits(cap, consecutive, chunk.prior_exit2, hook.stop_hook_active) {
return VehicleChoice {
vehicle: Vehicle::Exit2,
block_count: Some(u32::try_from(consecutive + 1).unwrap_or(u32::MAX)),
held_block_cap: false,
};
}
VehicleChoice {
held_block_cap: true,
..additional
}
}
pub(crate) fn hook_output(event: &str, chunk_text: &str) -> Result<String> {
Ok(serde_json::to_string(&json!({
"hookSpecificOutput": {
"hookEventName": event,
"additionalContext": chunk_text,
}
}))?)
}
pub(crate) fn emit(event: &str, chunk_text: &str, vehicle: Vehicle) -> Result<()> {
match vehicle {
Vehicle::AdditionalContext => {
println!("{}", hook_output(event, chunk_text)?);
Ok(())
}
Vehicle::Exit2 => {
eprintln!("{chunk_text}");
let _ = std::io::stdout().flush();
let _ = std::io::stderr().flush();
std::process::exit(2);
}
}
}