use crate::cli::{err, hint, DEFAULT_PORT};
use anyhow::Result;
use clap::Parser;
use std::time::{Duration, Instant};

#[derive(Parser)]
pub struct Args {
    /// JS snippet to run inside the adom-step webview. Wrap `await` freely;
    /// the UI invokes via AsyncFunction so top-level await works.
    pub code: String,

    /// How long to wait for the UI to return a result.
    #[arg(long, default_value_t = 5)]
    pub timeout_secs: u64,

    #[arg(long)]
    pub quiet: bool,

    #[arg(long, default_value_t = DEFAULT_PORT)]
    pub port: u16,
}

pub fn run(args: Args) -> Result<()> {
    eval_snippet(&args.code, args.timeout_secs, args.port, args.quiet).map(|v| {
        println!("{}", serde_json::to_string_pretty(&v).unwrap_or_default());
    })
}

/// Reusable helper for other CLI subcommands that need to drive the UI via JS.
pub fn eval_snippet(code: &str, timeout_secs: u64, port: u16, quiet: bool) -> Result<serde_json::Value> {
    let base = format!("http://127.0.0.1:{}", port);
    let queue: serde_json::Value = match ureq::post(&format!("{base}/eval"))
        .timeout(Duration::from_secs(2))
        .send_json(serde_json::json!({"code": code}))
    {
        Ok(r) => r.into_json()?,
        Err(e) => {
            err(format!("could not reach adom-step: {e}"));
            hint("start it first with `adom-step view <file.step>`");
            return Err(anyhow::anyhow!("unreachable"));
        }
    };
    let id = queue
        .get("id")
        .and_then(|v| v.as_str())
        .ok_or_else(|| anyhow::anyhow!("server returned no id: {queue}"))?
        .to_string();
    if !quiet {
        eprintln!("queued {id}");
    }
    let deadline = Instant::now() + Duration::from_secs(timeout_secs);
    loop {
        if Instant::now() >= deadline {
            err(format!("timeout waiting for result of {id}"));
            hint("is the webview open and loaded?");
            return Err(anyhow::anyhow!("timeout"));
        }
        let r: serde_json::Value = ureq::get(&format!("{base}/eval/{id}"))
            .timeout(Duration::from_secs(2))
            .call()?
            .into_json()?;
        if r.get("done").and_then(|v| v.as_bool()).unwrap_or(false) {
            return Ok(r.get("result").cloned().unwrap_or(serde_json::Value::Null));
        }
        std::thread::sleep(Duration::from_millis(150));
    }
}