use crate::render::{build_verify, EventRow, VerifyReport};
use crate::{CliError, Session};
use btctax_core::LedgerState;
use btctax_store::Passphrase;
use std::path::Path;
pub fn events_list(vault_path: &Path, pp: &Passphrase) -> Result<Vec<EventRow>, CliError> {
use btctax_core::conventions::tax_date;
use btctax_core::persistence::{load_all, load_all_ordered};
use btctax_core::{EventId, EventPayload, LedgerEvent, Sat, TransferTarget};
use std::collections::{BTreeSet, HashMap};
let session = Session::open(vault_path, pp)?;
let conn = session.conn();
let prices = session.prices();
let decoded = load_all(conn)?;
let ordered = load_all_ordered(conn)?;
let by_id: HashMap<String, &LedgerEvent> =
decoded.iter().map(|e| (e.id.canonical(), e)).collect();
let voided: BTreeSet<EventId> = decoded
.iter()
.filter_map(|e| match &e.payload {
EventPayload::VoidDecisionEvent(v) => Some(v.target_event_id.clone()),
_ => None,
})
.collect();
let mut decided: HashMap<EventId, EventId> = HashMap::new();
for e in &decoded {
if voided.contains(&e.id) {
continue;
}
match &e.payload {
EventPayload::ClassifyInbound(d) => {
decided.insert(d.transfer_in_event.clone(), e.id.clone());
}
EventPayload::ReclassifyOutflow(d) => {
decided.insert(d.transfer_out_event.clone(), e.id.clone());
}
EventPayload::ManualFmv(d) => {
decided.insert(d.event.clone(), e.id.clone());
}
EventPayload::ReclassifyIncome(d) => {
decided.insert(d.income_event.clone(), e.id.clone());
}
EventPayload::ClassifyRaw(d) => {
decided.insert(d.target.clone(), e.id.clone());
}
EventPayload::SupersedeImport(d) => {
decided.insert(d.conflict_event.clone(), e.id.clone());
}
EventPayload::RejectImport(d) => {
decided.insert(d.conflict_event.clone(), e.id.clone());
}
EventPayload::TransferLink(d) => {
decided.insert(d.out_event.clone(), e.id.clone());
if let TransferTarget::InEvent(in_id) = &d.in_event_or_wallet {
decided.insert(in_id.clone(), e.id.clone());
}
}
EventPayload::SelfTransferPassthrough(d) => {
decided.insert(d.in_event.clone(), e.id.clone());
decided.insert(d.out_event.clone(), e.id.clone());
}
_ => {}
}
}
let mut rows = Vec::new();
for raw in &ordered {
let Some(ev) = by_id.get(&raw.event_id) else {
continue;
};
let (kind, sat): (&'static str, Option<Sat>) = match &ev.payload {
EventPayload::TransferIn(ti) => ("transfer-in", Some(ti.sat)),
EventPayload::TransferOut(to) => ("transfer-out", Some(to.sat)),
EventPayload::Unclassified(_) => ("unclassified", None),
EventPayload::ImportConflict(_) => ("import-conflict", None),
EventPayload::Income(inc) => ("income", Some(inc.sat)),
_ => continue,
};
let date = tax_date(ev.utc_timestamp, ev.original_tz);
let usd = match &ev.payload {
EventPayload::Income(inc) => inc
.usd_fmv
.or_else(|| sat.and_then(|s| btctax_core::price::fmv_of(prices, date, s))),
_ => sat.and_then(|s| btctax_core::price::fmv_of(prices, date, s)),
};
rows.push(EventRow {
reff: ev.id.canonical(),
kind,
date,
sat,
usd,
decision_ref: decided.get(&ev.id).map(|d| d.canonical()),
});
}
Ok(rows)
}
pub fn report(
vault_path: &Path,
pp: &Passphrase,
_year: Option<i32>,
) -> Result<LedgerState, CliError> {
let session = Session::open(vault_path, pp)?;
let (state, _cfg) = session.project()?;
Ok(state)
}
pub fn verify(vault_path: &Path, pp: &Passphrase) -> Result<VerifyReport, CliError> {
let session = Session::open(vault_path, pp)?;
let (events, state, _cfg) = session.load_events_and_project()?;
let cli = session.config()?;
Ok(build_verify(&state, &events, session.prices(), &cli))
}