1use clap::{Args, Subcommand};
2use serde_json::json;
3
4use crate::client::BackpacClient;
5use crate::errors::CairnError;
6
7use super::{output_json, Cli};
8
9#[derive(Args, Debug)]
10pub struct IntentArgs {
11 #[command(subcommand)]
12 pub command: IntentCommands,
13}
14
15#[derive(Subcommand, Debug)]
16pub enum IntentCommands {
17 Send {
19 #[arg(long)]
21 method: String,
22
23 #[arg(long)]
25 params: String,
26
27 #[arg(long)]
29 poi_id: Option<String>,
30
31 #[arg(long)]
33 confidence: Option<f64>,
34
35 #[arg(long, default_value = "1")]
37 id: u64,
38 },
39
40 Status {
42 intent_id: String,
44 },
45
46 Verify {
48 intent_id: String,
50
51 #[arg(long)]
53 receiver_did: Option<String>,
54
55 #[arg(long)]
57 min_confidence: Option<f64>,
58 },
59
60 Wait {
62 intent_id: String,
64
65 #[arg(long, default_value = "2")]
67 interval: u64,
68
69 #[arg(long, default_value = "120")]
71 timeout: u64,
72 },
73
74 List {
76 #[arg(long)]
78 status: Option<String>,
79
80 #[arg(long)]
82 since: Option<String>,
83
84 #[arg(long, default_value = "20")]
86 limit: u32,
87 },
88}
89
90impl IntentArgs {
91 pub async fn execute(&self, cli: &Cli) -> Result<(), CairnError> {
92 let client = BackpacClient::new(cli.jwt.as_deref(), cli.api_url.as_deref());
93
94 match &self.command {
95 IntentCommands::Send {
96 method,
97 params,
98 poi_id,
99 confidence,
100 id,
101 } => {
102 let params_val: serde_json::Value = serde_json::from_str(params)
103 .map_err(|e| CairnError::InvalidInput(format!("Invalid JSON params: {}", e)))?;
104
105 let body = json!({
106 "jsonrpc": "2.0",
107 "method": method,
108 "params": params_val,
109 "id": id,
110 });
111
112 let result = client
113 .rpc_post(&body, poi_id.as_deref(), *confidence)
114 .await?;
115
116 output_json(&result, &cli.output);
117 Ok(())
118 }
119
120 IntentCommands::Status { intent_id } => {
121 let path = format!("/v1/intents/{}", intent_id);
122 let result = client.get(&path).await?;
123 output_json(&result, &cli.output);
124 Ok(())
125 }
126
127 IntentCommands::Verify {
128 intent_id,
129 receiver_did,
130 min_confidence,
131 } => {
132 let mut path = format!("/v1/intents/{}/verify", intent_id);
133 let mut query_parts: Vec<String> = Vec::new();
134
135 if let Some(did) = receiver_did {
136 query_parts.push(format!("receiver_did={}", did));
137 }
138 if let Some(conf) = min_confidence {
139 query_parts.push(format!("min_confidence={}", conf));
140 }
141 if !query_parts.is_empty() {
142 path = format!("{}?{}", path, query_parts.join("&"));
143 }
144
145 let result = client.get(&path).await?;
146 output_json(&result, &cli.output);
147 Ok(())
148 }
149
150 IntentCommands::Wait {
151 intent_id,
152 interval,
153 timeout,
154 } => {
155 let start = std::time::Instant::now();
156 let max_duration = std::time::Duration::from_secs(*timeout);
157 let poll_interval = std::time::Duration::from_secs(*interval);
158 let terminal_states = ["FINALIZED", "ABORTED", "EXPIRED"];
159
160 loop {
161 if start.elapsed() > max_duration {
162 return Err(CairnError::Timeout);
163 }
164
165 let path = format!("/v1/intents/{}", intent_id);
166 let result = client.get(&path).await?;
167
168 let status = result
169 .get("status")
170 .and_then(|v| v.as_str())
171 .unwrap_or("UNKNOWN");
172
173 if terminal_states.contains(&status) {
174 output_json(&result, &cli.output);
175 return Ok(());
176 }
177
178 tokio::time::sleep(poll_interval).await;
179 }
180 }
181
182 IntentCommands::List {
183 status,
184 since,
185 limit,
186 } => {
187 let mut query_parts: Vec<String> = Vec::new();
188
189 if let Some(s) = status {
190 query_parts.push(format!("status={}", s));
191 }
192 if let Some(s) = since {
193 query_parts.push(format!("since={}", s));
194 }
195 query_parts.push(format!("limit={}", limit));
196
197 let path = format!("/v1/intents?{}", query_parts.join("&"));
198 let result = client.get(&path).await?;
199 output_json(&result, &cli.output);
200 Ok(())
201 }
202 }
203 }
204}