pub mod generated;

use crate::spawn::ensure_daemon_safe;
use anyhow::Result;
use night_fury_daemon_core::cli::print_response;
use night_fury_daemon_core::protocol::Response;
use std::process::ExitCode;
use tail_fin_cli_core::{exec_on_daemon, ExecArgs};

/// Map a command's ok-ness to a process exit code so `tfd` exits non-zero on a
/// failed command (for shell conditionals). The decision is returned to the
/// binary's `main`, which surfaces it as the process `ExitCode` — the library
/// never calls `process::exit` (workspace convention: library code returns
/// values, the binary owns the exit).
pub fn exit_code(ok: bool) -> ExitCode {
    if ok {
        ExitCode::SUCCESS
    } else {
        ExitCode::FAILURE
    }
}

/// Transparent mode for `tfd`: ensure a daemon exists, then acquire → exec →
/// release via the shared no-spawn core, print the result, and report the
/// resulting exit code (FAILURE if the command response was not ok).
pub async fn acquire_then_exec(socket: &str, args: ExecArgs<'_>) -> Result<ExitCode> {
    ensure_daemon_safe(socket).await?;
    let exec_resp = exec_on_daemon(socket, args)
        .await
        .map_err(|e| anyhow::anyhow!("{e}"))?;
    if print_and_ok(&exec_resp) {
        Ok(ExitCode::SUCCESS)
    } else {
        Ok(ExitCode::from(tail_fin_cli_core::exit_code_for_kind(
            exec_resp.error_kind.as_deref(),
        )))
    }
}

/// Print the response as JSON to stdout and report whether it succeeded.
///
/// Returns `resp.ok` so the binary can choose the process exit code. The full
/// JSON is already on stdout, so a non-ok response needs no separate stderr
/// message — the caller maps `false` to a failing `ExitCode`, not an `Err`
/// (which `anyhow` would re-print).
pub fn print_and_ok(resp: &Response) -> bool {
    print_response(resp);
    resp.ok
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn print_and_ok_reports_response_status() {
        assert!(print_and_ok(&Response::ok("1", json!({"x": 1}))));
        assert!(!print_and_ok(&Response::err("1", "boom")));
    }

    #[test]
    fn print_and_ok_accepts_cli_core_response_name() {
        let resp = tail_fin_cli_core::daemon_client::Response {
            id: "1".into(),
            ok: true,
            data: Some(json!({"x": 1})),
            error: None,
            error_kind: None,
        };

        assert!(print_and_ok(&resp));
    }

    #[test]
    fn exit_code_maps_ok_to_success_and_fail_to_failure() {
        // ExitCode isn't PartialEq; compare via its Debug repr.
        assert_eq!(
            format!("{:?}", exit_code(true)),
            format!("{:?}", ExitCode::SUCCESS)
        );
        assert_eq!(
            format!("{:?}", exit_code(false)),
            format!("{:?}", ExitCode::FAILURE)
        );
    }

    #[test]
    fn business_error_kinds_map_to_contract_exit_codes() {
        // acquire_then_exec derives its code from exit_code_for_kind; assert
        // the mapping here so tfd can't silently drift from tail-fin again.
        assert_eq!(tail_fin_cli_core::exit_code_for_kind(Some("timeout")), 4);
        assert_eq!(
            tail_fin_cli_core::exit_code_for_kind(Some("auth_required")),
            3
        );
        assert_eq!(tail_fin_cli_core::exit_code_for_kind(Some("api")), 1);
    }
}
