use std::borrow::Cow;
use std::fmt::Display;

use cargo_metadata::{Dependency, DependencyKind as DK, Target, TargetKind as TK};

pub fn make_diff(old: &str, new: &str, header: Option<(&str, &str)>) -> String {
    let diff = similar::TextDiff::from_lines(old, new);
    let mut udiff = diff.unified_diff();

    if let Some((a, b)) = header {
        udiff.header(a, b);
    }

    udiff.to_string()
}

pub fn format_list<D: Display>(items: &[D]) -> String {
    format!(
        "[{}]",
        items
            .iter()
            .map(|i| format!("\"{i}\""))
            .collect::<Vec<String>>()
            .join(", ")
    )
}

pub fn format_option_string(value: Option<&String>) -> &str {
    value.map_or("(none)", String::as_str)
}

pub fn path_from_dep(dep: &Dependency) -> String {
    let mut path_items: Vec<Cow<'static, str>> = Vec::new();

    if let Some(target) = &dep.target {
        path_items.push(format!("target.\"{}\"", target.to_string().replace('"', "\'")).into());
    }

    match dep.kind {
        DK::Normal => path_items.push("dependencies".into()),
        DK::Development => path_items.push("dev-dependencies".into()),
        DK::Build => path_items.push("build-dependencies".into()),
        _ => {},
    }

    path_items.push(dep.rename.clone().unwrap_or(dep.name.clone()).clone().into());
    path_items.join(".")
}

pub fn value_from_dep(dep: &Dependency) -> String {
    let mut items = Vec::new();
    if dep.rename.is_some() {
        items.push(format!("package = \"{}\"", dep.name));
    }
    items.push(format!("version = \"{}\"", dep.req));
    if let Some(path) = &dep.path {
        items.push(format!("path = \"{path}\""));
    }
    if dep.optional {
        items.push(String::from("optional = true"));
    }
    if !dep.uses_default_features {
        items.push(String::from("default-features = false"));
    }
    if !dep.features.is_empty() {
        items.push(format!(
            "features = [{}]",
            dep.features
                .iter()
                .map(|f| format!("\"{f}\""))
                .collect::<Vec<String>>()
                .join(", ")
        ));
    }

    format!("{{ {} }}", items.join(", "))
}

const LIBRARY_KINDS: &[TK] = &[TK::Lib, TK::RLib, TK::CDyLib, TK::DyLib, TK::StaticLib, TK::ProcMacro];

pub fn path_from_target(target: &Target) -> String {
    let kind = if LIBRARY_KINDS.iter().any(|t| target.kind.contains(t)) {
        "lib"
    } else if target.kind.contains(&TK::Bench) {
        "benches"
    } else if target.kind.contains(&TK::Bin) {
        "bin"
    } else if target.kind.contains(&TK::CustomBuild) {
        "build"
    } else if target.kind.contains(&TK::Example) {
        "example"
    } else if target.kind.contains(&TK::Test) {
        "test"
    } else {
        "<unknown>"
    };

    format!("{}[\"{}\"]", kind, target.name)
}

pub fn value_from_target(target: &Target) -> String {
    let mut items = Vec::new();

    items.push(format!("path = \"{}\"", target.src_path));
    items.push(format!("edition = \"{}\"", target.edition));

    if LIBRARY_KINDS.iter().any(|t| target.kind.contains(t)) {
        items.push(format!(
            "crate-type = [{}]",
            target
                .crate_types
                .iter()
                .map(|t| format!("\"{t}\""))
                .collect::<Vec<String>>()
                .join(", ")
        ));
    }
    if !target.required_features.is_empty() {
        items.push(format!(
            "required-features = [{}]",
            target
                .required_features
                .iter()
                .map(|f| format!("\"{f}\""))
                .collect::<Vec<String>>()
                .join(", ")
        ));
    }
    if !target.doctest {
        items.push(String::from("doctest = false"));
    }
    if !target.test {
        items.push(String::from("test = false"));
    }
    if !target.doc {
        items.push(String::from("doc = false"));
    }

    format!("{{ {} }}", items.join(", "))
}

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

    #[test]
    fn test_format_list() {
        assert_eq!(format_list::<&str>(&[]), "[]");
        assert_eq!(format_list(&["hello"]), "[\"hello\"]");
        assert_eq!(format_list(&["hello", "world"]), "[\"hello\", \"world\"]");
    }
}
