use serde::Serialize;
use std::io::IsTerminal;

use crate::error::CliError;

#[derive(Clone, Copy)]
pub enum Format {
    Json,
    Human,
}

impl Format {
    pub fn detect(json_flag: bool) -> Self {
        if json_flag || !std::io::stdout().is_terminal() {
            Format::Json
        } else {
            Format::Human
        }
    }

    #[allow(dead_code)]
    pub fn is_json(self) -> bool {
        matches!(self, Format::Json)
    }
}

pub fn print_success<T: Serialize>(format: Format, data: &T) {
    if let Format::Json = format {
        let envelope = serde_json::json!({
            "version": "1",
            "status": "success",
            "data": data,
        });
        let output = serde_json::to_string_pretty(&envelope)
            .unwrap_or_else(|_| r#"{"version":"1","status":"error"}"#.to_string());
        println!("{}", output);
    }
}

pub fn print_success_or<T: Serialize, F: FnOnce(&T)>(format: Format, data: &T, human: F) {
    match format {
        Format::Json => print_success(format, data),
        Format::Human => human(data),
    }
}

pub fn print_error(format: Format, err: &CliError) {
    let envelope = serde_json::json!({
        "version": "1",
        "status": "error",
        "error": {
            "code": err.error_code(),
            "message": err.to_string(),
            "suggestion": err.suggestion(),
        },
    });
    match format {
        Format::Json => {
            // JSON errors go to stdout so agents parsing the machine contract
            // always receive the error envelope. stderr is for human diagnostics only.
            let output = serde_json::to_string_pretty(&envelope)
                .unwrap_or_else(|_| r#"{"version":"1","status":"error"}"#.to_string());
            println!("{}", output);
        }
        Format::Human => {
            eprintln!("error: {}", err);
            eprintln!("  {}", err.suggestion());
        }
    }
}

pub fn print_clap_error(format: Format, err: clap::Error) {
    match format {
        Format::Json => {
            let cli_err = CliError::InvalidInput(err.to_string());
            print_error(format, &cli_err);
        }
        Format::Human => {
            let _ = err.print();
        }
    }
}
