mod fixtures;
mod frame;
mod input;
mod year;
pub use fixtures::*;
pub use frame::*;
pub use input::*;
pub use year::*;
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use bevy_app::App;
use bevy_ecs::system::RunSystemOnce;
use plurimus::core::{CorePlugin, TerminalSize};
use plurimus::term::{InputCapabilities, KeyCode};
use retiretui_engine::params::TaxTables;
use retiretui_engine::plan::Plan;
use tracing::subscriber::DefaultGuard;
use tracing_subscriber::layer::SubscriberExt as _;
use crate::commands::tui::confirm::Confirm;
use crate::commands::tui::documents::Browsing;
use crate::commands::tui::edit::DraftEditor;
use crate::commands::tui::journal::{self, Inbox, Journal};
use crate::commands::tui::nav::{ActivePage, Page};
use crate::commands::tui::session::{Session, Today};
use crate::commands::tui::settings::Settings;
use crate::commands::tui::tools::Searches;
use crate::commands::tui::watch;
pub const SIZE: TerminalSize = TerminalSize::new(128, 32);
pub const ROOMY: TerminalSize = TerminalSize::new(200, 55);
pub const TODAY: Today = Today(2026);
pub fn commit_edit(app: &mut App, edit: impl Fn(&mut Plan) + Send + Sync + 'static) {
app.world_mut()
.run_system_once(move |mut editor: DraftEditor| {
edit(&mut editor.draft.plan);
editor.commit();
})
.unwrap();
}
pub fn is_asking(app: &App) -> bool {
app.world().resource::<Confirm>().is_open()
}
pub fn is_browsing(app: &App) -> bool {
app.world().resource::<Browsing>().is_open()
}
pub fn answer_back(app: &mut App, back: usize) {
for _ in 0..back {
press_shift(app, KeyCode::Tab);
}
press_key(app, KeyCode::Enter);
}
pub struct Headless {
app: App,
_listening: DefaultGuard,
}
impl Deref for Headless {
type Target = App;
fn deref(&self) -> &App {
&self.app
}
}
impl DerefMut for Headless {
fn deref_mut(&mut self) -> &mut App {
&mut self.app
}
}
pub fn searched_app(path: PathBuf, plan_text: &str, size: TerminalSize) -> Headless {
std::fs::write(&path, format!("{plan_text}{FEW_TRIALS}")).unwrap();
let mut app = headless_app_at(path, size);
app.insert_resource(Searches(true));
crate::commands::tui::tools::settle_all(&mut app);
app
}
pub fn headless_app(size: TerminalSize) -> Headless {
headless_app_at(scratch_plan(), size)
}
pub fn said(app: &App) -> Vec<String> {
app.world()
.resource::<Journal>()
.entries()
.map(|entry| entry.text.clone())
.collect()
}
pub fn headless_app_at(plan_path: PathBuf, size: TerminalSize) -> Headless {
headless_app_set(plan_path, size, Settings::still())
}
pub fn headless_app_set(path: PathBuf, size: TerminalSize, settings: Settings) -> Headless {
headless_app_on(path, size, settings, TODAY)
}
pub fn headless_app_in(path: PathBuf, size: TerminalSize, today: Today) -> Headless {
headless_app_on(path, size, Settings::still(), today)
}
fn headless_app_on(
path: PathBuf,
size: TerminalSize,
settings: Settings,
today: Today,
) -> Headless {
let session = Session::at(path, TaxTables::embedded());
let (projected, files) = watch::load_session(&session, today).unwrap();
let mut app = App::new();
app.add_plugins(CorePlugin);
app.insert_resource(InputCapabilities::none());
app.insert_resource(size);
app.insert_resource(session);
app.insert_resource(settings);
app.insert_resource(today);
app.insert_resource(projected);
app.insert_resource(watch::Watch::new(files));
crate::commands::tui::add_tui(&mut app);
app.insert_resource(Searches(false));
let inbox = app.world().resource::<Inbox>().clone();
let subscriber = tracing_subscriber::registry().with(journal::layer(inbox));
let listening = tracing::subscriber::set_default(subscriber);
app.update();
Headless {
app,
_listening: listening,
}
}
pub const SETTLING_TICKS: usize = 3;
pub fn active_page(app: &App) -> Page {
app.world().resource::<ActivePage>().0
}
pub fn show(app: &mut App, page: Page) {
app.insert_resource(ActivePage(page));
for _ in 0..SETTLING_TICKS {
app.update();
}
}