use anyhow::Context;
use serde::Serialize;

pub fn string(str: &str) -> String {
    serde_yml::to_string(str).expect("Should be able to encode string")
}

pub fn to_string<T: ?Sized + Serialize>(value: &T) -> anyhow::Result<String> {
    serde_yml::to_string(value).context("Could not serialize to yaml")
}

pub fn from_str<D: for<'de> serde::Deserialize<'de>>(s: &str) -> anyhow::Result<D> {
    serde_yml::from_str(s).map_err(|e| anyhow::format_err!("{e}:\n{}", extract_context(&e, s)))
}

fn extract_context(serde_error: &serde_yml::Error, s: &str) -> String {
    let Some(location) = serde_error.location() else {
        return String::from("Error provided no location information, could not extract context");
    };
    let lines: Vec<_> = s.lines().collect();
    if lines.len() == 1 {
        let (col_begin, highlight) = if location.column() > 30 {
            (location.column() - 30, 30)
        } else {
            (1, location.column())
        };
        let col_end = if lines[0].len() + 31 < location.column() {
            lines[0].len() + 1
        } else {
            location.column() + 30
        };
        let mut line: String = lines[0]
            .chars()
            .skip(col_begin - 1)
            .take(col_end - col_begin)
            .collect();
        line.push('\n');
        line.extend(std::iter::repeat_n(' ', highlight - 1));
        line.push('^');
        line
    } else {
        let error_line = location.line();
        let mut result = String::new();
        if error_line > 1 {
            result.push_str(&format!("{}: {}\n", error_line - 1, lines[error_line - 2]));
        }
        result.push_str(&format!("{}: {}\n", error_line, lines[error_line - 1]));
        result.push_str(&format!(
            "{}  {}\n",
            " ".repeat(error_line.to_string().len()),
            std::iter::repeat_n(' ', location.column() - 1)
                .chain(['^'].into_iter())
                .collect::<String>(),
        ));
        if lines.len() > error_line {
            result.push_str(&format!("{}: {}\n", error_line + 1, lines[error_line]));
        }
        result
    }
}
