use std::{cmp::Ordering, collections::HashMap, fmt::Display};
use anyhow::Result;
use colored::Colorize;
use country_emoji::flag;
use serde_json::{Map, Value, json};
use crate::{
client::post_graphql,
config::Configs,
gql::queries,
util::prompt::{
prompt_select_with_cancel, prompt_u64_with_placeholder_and_validation_and_cancel,
},
};
pub struct PromptRegion(pub queries::regions::RegionsRegions, pub String);
impl Display for PromptRegion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.1)
}
}
pub async fn fetch_regions(
client: &reqwest::Client,
configs: &Configs,
) -> Result<queries::regions::ResponseData> {
let regions = post_graphql::<queries::Regions, _>(
client,
configs.get_backboard(),
queries::regions::Variables,
)
.await?;
Ok(regions)
}
pub async fn prompt_for_regions(
configs: &Configs,
client: &reqwest::Client,
existing: &Value,
) -> Result<HashMap<String, u64>> {
let regions = fetch_regions(client, configs).await?;
prompt_for_regions_with_data(regions, existing)
}
pub fn prompt_for_regions_with_data(
mut regions: queries::regions::ResponseData,
existing: &Value,
) -> Result<HashMap<String, u64>> {
let mut updated: HashMap<String, u64> = HashMap::new();
loop {
let get_replicas_amount = |name: String| {
let before = if let Some(num) = existing.get(name.clone()) {
num.get("numReplicas").unwrap().as_u64().unwrap()
} else {
0
};
let after = if let Some(new_value) = updated.get(&name) {
*new_value
} else {
before
};
(before, after)
};
regions.regions.sort_by(|a, b| {
get_replicas_amount(b.name.clone())
.1
.cmp(&get_replicas_amount(a.name.clone()).1)
});
let region_options = regions
.regions
.iter()
.filter(|r| r.railway_metal.unwrap_or_default())
.map(|f| {
PromptRegion(
f.clone(),
format!(
"{} {}{}{}",
flag(&f.country).unwrap_or_default(),
f.location,
if f.railway_metal.unwrap_or_default() {
" (METAL)".bold().purple().to_string()
} else {
String::new()
},
{
let (before, after) = get_replicas_amount(f.name.clone());
let amount = format!(
" ({} replica{})",
after,
if after == 1 { "" } else { "s" }
);
match after.cmp(&before) {
Ordering::Equal if after == 0 => String::new().normal(),
Ordering::Equal => amount.yellow(),
Ordering::Greater => amount.green(),
Ordering::Less => amount.red(),
}
.to_string()
}
),
)
})
.collect::<Vec<PromptRegion>>();
let selected =
prompt_select_with_cancel("Select a region <esc to finish>", region_options)?;
if let Some(region) = selected {
let amount_before = if let Some(updated) = updated.get(®ion.0.name) {
*updated
} else if let Some(previous) = existing.as_object().unwrap().get(®ion.0.name) {
previous.get("numReplicas").unwrap().as_u64().unwrap()
} else {
0
};
let prompted = prompt_u64_with_placeholder_and_validation_and_cancel(
format!(
"Enter the amount of replicas for {} <esc to go back>",
region.0.name.clone()
)
.as_str(),
amount_before.to_string().as_str(),
)?;
if let Some(prompted) = prompted {
let parse: u64 = prompted.parse()?;
updated.insert(region.0.name.clone(), parse);
}
} else {
break;
}
}
Ok(updated)
}
pub fn convert_hashmap_to_map(map: HashMap<String, u64>) -> Map<String, Value> {
map.iter().fold(Map::new(), |mut m, (key, val)| {
m.insert(
key.clone(),
if *val == 0 {
Value::Null } else {
json!({ "numReplicas": val })
},
);
m
})
}
pub fn merge_config(existing: Value, new_config: Map<String, Value>) -> Value {
let mut map = match existing {
Value::Object(object) => object,
_ => Map::new(),
};
map.extend(new_config);
Value::Object(map)
}