use crate::cells::{push_literal, push_money};
use crate::error::FormsError;
use crate::map::Form1040Map;
use crate::pdf;
use crate::verify::{topmost_yes_no_pair, verify_flat, FlatPlacement};
use btctax_core::Usd;
const F1040_COL_AMOUNT: usize = 0;
const F1040_CLUSTERS_UNIFIED: &[(f32, f32)] = &[(504.0, 576.0)];
const F1040_CLUSTERS_2017: &[(f32, f32)] = &[(482.0, 555.0)];
fn f1040_clusters(year: i32) -> &'static [(f32, f32)] {
match year {
2017 => F1040_CLUSTERS_2017,
_ => F1040_CLUSTERS_UNIFIED,
}
}
#[derive(Debug, Clone, Copy)]
pub struct Form1040Inputs {
pub da_yes: bool,
pub schedule_d_active: bool,
pub schedule_d_line16: Usd,
}
#[derive(Debug, Clone)]
pub struct Form1040Fill {
pub pdf: Vec<u8>,
pub filled_7a: bool,
pub active_zero: bool,
pub loss: bool,
}
pub fn fill_form_1040_capgains(
inputs: &Form1040Inputs,
map: &Form1040Map,
) -> Result<Option<Form1040Fill>, FormsError> {
if map.da_present {
if !inputs.da_yes {
return Ok(None);
}
} else if !(inputs.schedule_d_active && inputs.schedule_d_line16 >= Usd::ZERO) {
return Ok(None);
}
let mut writes: Vec<(String, pdf::FieldValue)> = Vec::new();
let mut placements: Vec<FlatPlacement> = Vec::new();
if map.da_present {
let da_yes = map.da_yes.as_ref().ok_or_else(|| {
FormsError::Structure("1040 map has da_present=true but no da_yes field".into())
})?;
writes.push((
da_yes.field.clone(),
pdf::FieldValue::Check {
on: da_yes.on.clone(),
},
));
placements.push(FlatPlacement::check(da_yes.field.clone(), 0));
}
let mut filled_7a = false;
let mut active_zero = false;
let mut loss = false;
if inputs.schedule_d_active {
if inputs.schedule_d_line16 < Usd::ZERO {
loss = true; } else if inputs.schedule_d_line16.is_zero() {
active_zero = true; push_literal(
&mut writes,
&mut placements,
&map.line7a,
"-0-",
F1040_COL_AMOUNT,
);
filled_7a = true;
} else {
push_money(
&mut writes,
&mut placements,
&map.line7a,
inputs.schedule_d_line16,
F1040_COL_AMOUNT,
None,
);
filled_7a = true;
}
}
let mut doc = pdf::load(pdf::f1040_pdf(map.year)?)?;
let index = pdf::index(&pdf::collect_fields(&doc)?);
pdf::drop_xfa_and_set_needappearances(&mut doc)?;
pdf::apply_writes(&mut doc, &index, &writes)?;
pdf::strip_nondeterminism(&mut doc);
let bytes = pdf::save(&mut doc)?;
let check = pdf::load(&bytes)?;
let fields = pdf::collect_fields(&check)?;
verify_flat(&check, &fields, &placements, f1040_clusters(map.year))?;
if map.da_present {
let da_yes = map.da_yes.as_ref().expect("checked above");
let da_no = map.da_no.as_ref().ok_or_else(|| {
FormsError::Structure("1040 map has da_present=true but no da_no field".into())
})?;
let (yes_fqn, no_fqn) = topmost_yes_no_pair(&check, &fields, 0)?;
if yes_fqn != da_yes.field {
return Err(FormsError::Geometry(format!(
"1040 DA 'Yes' map field {:?} is not the LEFT member of the top-most adjacent {{/1,/2}} pair ({yes_fqn:?})",
da_yes.field
)));
}
if no_fqn != da_no.field {
return Err(FormsError::Geometry(format!(
"1040 DA 'No' map field {:?} is not the right member of the top-most adjacent {{/1,/2}} pair ({no_fqn:?})",
da_no.field
)));
}
}
Ok(Some(Form1040Fill {
pdf: bytes,
filled_7a,
active_zero,
loss,
}))
}