use std::{env, io::Cursor};

use image::codecs::png::PngDecoder;
use nu_plugin::EvaluatedCall;
use nu_protocol::{LabeledError, Span, Value};

/// Converts an image (PNG) to ANSI escape codes for terminal display.
///
/// # Errors
/// Returns an error if:
/// - The input is not valid binary data
/// - The PNG decoder fails to parse the image
/// - Width or height parameters are invalid
///
/// # Errors
/// Returns an error if image decoding fails.
pub fn image_to_ansi(call: &EvaluatedCall, input: &Value) -> Result<Value, LabeledError> {
    match build_params(call, input) {
        Ok(params) => {
            let img = PngDecoder::new(Cursor::new(params.file.as_slice()))
                .and_then(image::DynamicImage::from_decoder);
            match img {
                Ok(img) => {
                    let result = super::writer::lib::to_ansi(&img, &params);
                    Ok(Value::string(result, call.head))
                }
                Err(er) => Err(response_error(er.to_string(), call.head)),
            }
        }
        Err(err) => Err(err),
    }
}

pub(super) struct IntoAnsiParams {
    file: Vec<u8>,
    pub width: Option<u32>,
    pub height: Option<u32>,
    pub truecolor: bool,
}

pub fn truecolor_available() -> bool {
    if let Ok(value) = env::var("COLORTERM") {
        value.contains("truecolor") || value.contains("24bit")
    } else {
        false
    }
}

fn build_params(call: &EvaluatedCall, input: &Value) -> Result<IntoAnsiParams, LabeledError> {
    let mut params = IntoAnsiParams {
        file: [].to_vec(),
        // verbose: false,
        height: None,
        width: None,
        truecolor: truecolor_available(),
    };
    match input.as_binary() {
        Ok(file) => file.clone_into(&mut params.file),
        Err(err) => return Err(make_params_err(err.to_string(), call.head)),
    }
    params.width = load_u32(call, "width").ok();
    params.height = load_u32(call, "height").ok();

    Ok(params)
}

fn load_u32(call: &EvaluatedCall, flag_name: &str) -> Result<u32, LabeledError> {
    match call.get_flag_value(flag_name) {
        Some(val) => match val {
            Value::Int { .. } => {
                let int_val = val
                    .as_int()
                    .map_err(|e| make_params_err(e.to_string(), call.head))?;
                int_val
                    .try_into()
                    .map_err(|err: std::num::TryFromIntError| make_params_err(err.to_string(), call.head))
            }
            _ => Err(make_params_err(
                format!("value of `{flag_name}` is not an integer"),
                call.head,
            )),
        },
        None => Err(make_params_err(
            format!("cannot find `{flag_name}` parameter"),
            call.head,
        )),
    }
}

fn make_params_err(text: String, span: Span) -> LabeledError {
    LabeledError::new(text).with_label("faced an error when tried to parse the params", span)
}

fn response_error(text: String, span: Span) -> LabeledError {
    LabeledError::new(text).with_label("cannot create image", span)
}
