use crate::import::{Roster, RosterUnit};
use super::helpers::{
char_slot_assignment, displayed_unit_points, title_case_id, total_army_points,
};
use super::{ExportFormat, RosterSerializer};
const FENCE: &str = "+++++++++++++++++++++++++++++++++++++++++++++++";
fn wargear_list_text(unit: &RosterUnit, include_warlord_tag: bool) -> String {
let mut parts: Vec<String> = Vec::with_capacity(unit.wargear.len() + 1);
for w in &unit.wargear {
if w.count > 1 {
parts.push(format!("{}x {}", w.count, w.ref_.raw_name));
} else {
parts.push(w.ref_.raw_name.clone());
}
}
if include_warlord_tag && unit.is_warlord {
parts.push("Warlord".to_string());
}
parts.join(", ")
}
fn header(roster: &Roster, units: &[RosterUnit], char_slots: &[Option<u32>]) -> String {
let faction = title_case_id(roster.faction_id.as_deref()).unwrap_or_else(|| "Unknown".into());
let detachment = title_case_id(roster.detachment_id.as_deref());
let limit = roster
.points
.declared_limit
.unwrap_or_else(|| total_army_points(roster));
let total = roster
.points
.total_reported
.unwrap_or_else(|| total_army_points(roster));
let warlord_idx = units.iter().position(|u| u.is_warlord);
let warlord = match warlord_idx {
Some(i) => format!(
"Char{}: {}",
char_slots[i].map(|n| n.to_string()).unwrap_or_default(),
units[i].ref_.raw_name
),
None => "—".to_string(),
};
let enhancement_idx = units.iter().position(|u| u.enhancement.is_some());
let enhancement = match enhancement_idx {
Some(i) => {
let u = &units[i];
let enh = u.enhancement.as_ref().expect("enhancement present");
format!(
"{} (on Char{}: {})",
enh.raw_name,
char_slots[i].map(|n| n.to_string()).unwrap_or_default(),
u.ref_.raw_name
)
}
None => "—".to_string(),
};
let det_display = detachment.unwrap_or_else(|| "—".to_string());
let lines = vec![
FENCE.to_string(),
format!("+ LIST NAME: {}", roster.name),
format!("+ FACTION KEYWORD: {faction}"),
format!("+ DETACHMENT: {det_display}"),
format!("+ TOTAL ARMY POINTS: {total}pts"),
format!("+ POINTS LIMIT: {limit}pts"),
"+".to_string(),
format!("+ WARLORD: {warlord}"),
format!("+ ENHANCEMENT: {enhancement}"),
format!("+ NUMBER OF UNITS: {}", units.len()),
FENCE.to_string(),
];
lines.join("\n")
}
fn is_allied_unit(_u: &RosterUnit, _faction_id: Option<&str>) -> bool {
false
}
pub struct NewRecruitWtcCompactSerializer;
impl RosterSerializer for NewRecruitWtcCompactSerializer {
fn id(&self) -> ExportFormat {
ExportFormat::NewrecruitWtcCompact
}
fn serialize(&self, roster: &Roster) -> String {
let units = &roster.units;
let slots = char_slot_assignment(units);
let mut lines: Vec<String> = vec![header(roster, units, &slots), String::new()];
for (i, u) in units.iter().enumerate() {
let prefix = match slots[i] {
Some(n) => format!("Char{n}: "),
None => String::new(),
};
let pts = displayed_unit_points(u);
let pts_text = match pts {
Some(p) => format!("{p} pts"),
None => String::new(),
};
lines.push(format!(
"{prefix}{}x {} ({pts_text}): {}",
u.model_count,
u.ref_.raw_name,
wargear_list_text(u, true)
));
if let Some(enh) = &u.enhancement {
let enh_text = match u.enhancement_points {
Some(p) => format!("Enhancement: {} (+{p} pts)", enh.raw_name),
None => format!("Enhancement: {}", enh.raw_name),
};
lines.push(enh_text);
}
}
let mut out = lines.join("\n");
out.push('\n');
out
}
}
fn multi_model_with_line(u: &RosterUnit) -> String {
let divisible = u
.wargear
.iter()
.all(|w| u.model_count > 0 && w.count % u.model_count == 0);
if divisible {
let mut per_model: Vec<String> = u
.wargear
.iter()
.map(|w| {
let c = w.count / u.model_count;
if c > 1 {
format!("{c}x {}", w.ref_.raw_name)
} else {
w.ref_.raw_name.clone()
}
})
.filter(|s| !s.is_empty())
.collect();
if u.is_warlord {
per_model.push("Warlord".to_string());
}
return format!("{} with {}", u.model_count, per_model.join(", "));
}
format!("1 with {}", wargear_list_text(u, true))
}
pub struct NewRecruitWtcFullSerializer;
impl RosterSerializer for NewRecruitWtcFullSerializer {
fn id(&self) -> ExportFormat {
ExportFormat::NewrecruitWtcFull
}
fn serialize(&self, roster: &Roster) -> String {
let units = &roster.units;
let slots = char_slot_assignment(units);
let mut battleline_idxs: Vec<usize> = Vec::new();
let mut allied_idxs: Vec<usize> = Vec::new();
for (i, u) in units.iter().enumerate() {
if is_allied_unit(u, roster.faction_id.as_deref()) {
allied_idxs.push(i);
} else {
battleline_idxs.push(i);
}
}
let mut lines: Vec<String> = vec![
header(roster, units, &slots),
String::new(),
"BATTLELINE".to_string(),
String::new(),
];
let emit_unit = |i: usize, lines: &mut Vec<String>| {
let u = &units[i];
let prefix = match slots[i] {
Some(n) => format!("Char{n}: "),
None => String::new(),
};
let pts = displayed_unit_points(u);
let pts_text = match pts {
Some(p) => format!("{p} pts"),
None => String::new(),
};
lines.push(format!(
"{prefix}{}x {} ({pts_text})",
u.model_count, u.ref_.raw_name
));
if u.model_count > 1 {
lines.push(multi_model_with_line(u));
} else {
lines.push(format!("1 with {}", wargear_list_text(u, true)));
}
if let Some(enh) = &u.enhancement {
let enh_text = match u.enhancement_points {
Some(p) => format!("Enhancement: {} (+{p} pts)", enh.raw_name),
None => format!("Enhancement: {}", enh.raw_name),
};
lines.push(enh_text);
}
lines.push(String::new());
};
for i in &battleline_idxs {
emit_unit(*i, &mut lines);
}
if !allied_idxs.is_empty() {
lines.push("ALLIED UNITS".to_string());
lines.push(String::new());
for i in &allied_idxs {
emit_unit(*i, &mut lines);
}
}
lines.join("\n")
}
}