use crate::error::Result;
use std::collections::HashMap;
pub fn action_space() -> HashMap<&'static str, &'static str> {
let mut m = HashMap::new();
m.insert("click", r#"{"x": "int (0-100)", "y": "int (0-100)"}"#);
m.insert("type", r#"{"text": "string"}"#);
m.insert("key", r#"{"keys": "list of key names"}"#);
m.insert("shell", r#"{"command": "string"}"#);
m.insert("wait", r#"{"duration": "float seconds"}"#);
m.insert("hotkey", r#"{"keys": "list of keys"}"#);
m.insert("scroll", r#"{"direction": "up|down", "amount": "int"}"#);
m.insert("quit", r#"{"description": "goal complete"}"#);
m
}
pub fn perform_action(action: &serde_json::Value) -> Result<HashMap<String, String>> {
let action_type = action.get("type").and_then(|v| v.as_str()).unwrap_or("");
let mut result = HashMap::new();
match action_type {
"click" => {
let x = action
.get("x")
.and_then(|v| v.as_f64())
.unwrap_or(50.0)
.max(0.0)
.min(100.0);
let y = action
.get("y")
.and_then(|v| v.as_f64())
.unwrap_or(50.0)
.max(0.0)
.min(100.0);
let (w, h) = std::process::Command::new("xdotool")
.arg("getdisplaygeometry")
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| {
let s = String::from_utf8_lossy(&o.stdout);
let p: Vec<&str> = s.trim().split_whitespace().collect();
(
p.first()
.and_then(|p| p.parse::<f64>().ok())
.unwrap_or(1920.0),
p.get(1)
.and_then(|p| p.parse::<f64>().ok())
.unwrap_or(1080.0),
)
})
.unwrap_or((1920.0, 1080.0));
let _ = std::process::Command::new("xdotool")
.args([
"mousemove",
&((x * w / 100.0) as i64).to_string(),
&((y * h / 100.0) as i64).to_string(),
"click",
"1",
])
.output();
result.insert("status".into(), "success".into());
result.insert("output".into(), format!("Clicked at ({}, {}).", x, y));
}
"type" => {
let text = action.get("text").and_then(|v| v.as_str()).unwrap_or("");
let _ = std::process::Command::new("xdotool")
.args(["type", "--clearmodifiers", "--delay", "12", "--", text])
.output();
result.insert("status".into(), "success".into());
result.insert("output".into(), format!("Typed '{}'.", text));
}
"key" => {
let keys: Vec<String> = action
.get("keys")
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.or_else(|| {
action
.get("keys")
.and_then(|v| v.as_str())
.map(|s| vec![s.into()])
})
.unwrap_or_default();
for k in &keys {
let _ = std::process::Command::new("xdotool")
.args(["key", k])
.output();
}
result.insert("status".into(), "success".into());
result.insert("output".into(), "Pressed key(s).".into());
}
"hotkey" => {
let keys = action
.get("keys")
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.filter_map(|v| v.as_str())
.collect::<Vec<_>>()
.join("+")
})
.unwrap_or_default();
if !keys.is_empty() {
let _ = std::process::Command::new("xdotool")
.args(["key", &keys])
.output();
}
result.insert("status".into(), "success".into());
result.insert("output".into(), "Pressed hotkey.".into());
}
"shell" | "bash" => {
let cmd = action.get("command").and_then(|v| v.as_str()).unwrap_or("");
let _ = std::process::Command::new("sh").args(["-c", cmd]).spawn();
result.insert("status".into(), "success".into());
result.insert("output".into(), format!("Launched '{}'.", cmd));
}
"wait" => {
let dur = action
.get("duration")
.and_then(|v| v.as_f64())
.unwrap_or(1.0);
std::thread::sleep(std::time::Duration::from_secs_f64(dur));
result.insert("status".into(), "success".into());
result.insert("output".into(), format!("Waited {}s.", dur));
}
"scroll" => {
let dir = action
.get("direction")
.and_then(|v| v.as_str())
.unwrap_or("down");
let amt = action.get("amount").and_then(|v| v.as_i64()).unwrap_or(3);
let btn = if dir == "up" { "4" } else { "5" };
for _ in 0..amt.unsigned_abs() {
let _ = std::process::Command::new("xdotool")
.args(["click", btn])
.output();
}
result.insert("status".into(), "success".into());
result.insert("output".into(), format!("Scrolled {} by {}.", dir, amt));
}
other => {
result.insert("status".into(), "error".into());
result.insert("message".into(), format!("Unknown action: {}", other));
}
}
Ok(result)
}