use serde::Serialize;
use crate::plan::{
ColaSpec, Conversion, Dollars, Plan, PlanDate, PlanError, SCHEMA_VERSION, Trigger,
};
use super::{LadderStep, OptimizeOptions};
pub fn ladder_overlay(
base: &str,
over: &Plan,
options: &OptimizeOptions,
steps: &[LadderStep],
) -> Result<String, PlanError> {
#[derive(Serialize)]
#[serde(untagged)]
enum LadderFragment<'a> {
Stated(Box<Conversion>),
Removed { id: &'a str, remove: bool },
}
#[derive(Serialize)]
struct OverlayDocument<'a> {
schema: u32,
base: &'a str,
conversions: Vec<LadderFragment<'a>>,
}
let stated = ladder_conversions(options, steps);
let removed: Vec<LadderFragment> = over
.conversions
.iter()
.filter(|conversion| is_ladder(conversion))
.map(|conversion| conversion.id.as_str())
.filter(|id| !stated.iter().any(|new| new.id == *id))
.map(|id| LadderFragment::Removed { id, remove: true })
.collect();
let conversions = stated
.into_iter()
.map(|conversion| LadderFragment::Stated(Box::new(conversion)))
.chain(removed)
.collect();
Ok(toml::to_string_pretty(&OverlayDocument {
schema: SCHEMA_VERSION,
base,
conversions,
})?)
}
pub const LADDER_ID_PREFIX: &str = "opt-";
#[must_use]
pub fn is_ladder(conversion: &Conversion) -> bool {
conversion.id.starts_with(LADDER_ID_PREFIX)
}
pub fn apply_ladder(plan: &mut Plan, options: &OptimizeOptions, steps: &[LadderStep]) {
plan.conversions.retain(|conversion| !is_ladder(conversion));
plan.conversions.extend(ladder_conversions(options, steps));
}
#[must_use]
fn ladder_conversions(options: &OptimizeOptions, steps: &[LadderStep]) -> Vec<Conversion> {
steps
.iter()
.map(|step| ladder_conversion(&step.source, &options.destination, step.year, step.amount))
.collect()
}
pub(super) fn ladder_conversion(
source: &str,
destination: &str,
year: i16,
amount: Dollars,
) -> Conversion {
Conversion {
id: format!("{LADDER_ID_PREFIX}{source}-{year}"),
name: None,
from: source.to_owned(),
to: destination.to_owned(),
amount,
start: None,
end: None,
on: Some(Trigger {
date: Some(PlanDate(
jiff::civil::Date::new(year, 1, 1).expect("January 1st of a validated year"),
)),
age: None,
owner: None,
event: None,
income: None,
offset: None,
}),
cola: ColaSpec::Follows(false),
}
}