use std::marker::PhantomData;
use bevy_app::{App, Update};
use bevy_ecs::change_detection::{DetectChanges, DetectChangesMut};
use bevy_ecs::hierarchy::{ChildOf, Children};
use bevy_ecs::prelude::{
Changed, Commands, Component, Entity, Has, IntoScheduleConfigs, Query, Res, ResMut, With,
};
use plurimus::core::ratatui_core::layout::Constraint;
use plurimus::core::ratatui_core::text::Line;
use plurimus::ui::{ScrollArea, UiStyle};
use plurimus::widgets::{ActiveDescendant, TableColumns, WidgetSystems, table_row};
use retiretui_engine::project::Summary;
use super::{EnterRuns, Found, ResultPane, Tool, handle_enter};
use crate::commands::tui::edit::{Draft, table_bundle};
use crate::commands::tui::hints::Hints;
use crate::commands::tui::layout::{self, filling, placed};
use crate::commands::tui::present::compact_dollars;
use crate::commands::tui::session::Basis;
use crate::commands::tui::tabulate;
use crate::commands::tui::theme::{Repainted, Theme};
pub fn plugin<R: Found>(app: &mut App) {
app.add_systems(
Update,
(refresh_options::<R>, follow_cursor::<R>)
.chain()
.after(super::poll_search::<R>)
.before(Repainted)
.before(WidgetSystems::Layout),
);
}
pub const CURRENT_PLAN: &str = "Current";
pub const FIGURES: [&str; 4] = ["unfunded", "final net", "taxes", "medicare"];
pub fn figures(summary: &Summary) -> [String; 4] {
[
summary.lifetime_unfunded,
summary.final_net_worth,
summary.lifetime_taxes,
summary.lifetime_medicare,
]
.map(compact_dollars)
}
pub struct Laid {
pub header: Vec<String>,
pub current: Vec<String>,
pub options: Vec<Vec<String>>,
}
#[derive(Component)]
pub struct CurrentRow;
#[derive(Component)]
pub struct OptionsTable<R>(PhantomData<R>);
#[derive(Component)]
pub struct OptionRow(usize);
pub fn spawn_table<R: Found>(
commands: &mut Commands,
framed: Entity,
enter: &'static str,
hints: Hints,
) -> Entity {
commands.entity(framed).insert(ResultPane::<R>::default());
commands
.spawn((
table_bundle(),
OptionsTable::<R>(PhantomData),
EnterRuns(enter),
hints,
layout::Rests,
filling(),
placed(),
ChildOf(framed),
))
.observe(handle_enter)
.id()
}
fn refresh_options<R: Found>(
state: (Res<Tool<R>>, Res<Basis>, Res<Theme>, Res<Draft>),
mut tables: Query<(Entity, &mut ScrollArea), With<OptionsTable<R>>>,
mut commands: Commands,
) {
let (tool, basis, theme, draft) = state;
let is_moved =
tool.is_changed() || basis.is_changed() || theme.is_changed() || draft.is_changed();
if !is_moved || (tool.found().is_none() && tool.is_running()) {
return;
}
for (table, mut scroll) in &mut tables {
commands.entity(table).despawn_related::<Children>();
let Some(found) = tool.found() else {
say_instead(&mut commands, (table, &mut scroll), tool.said(), &theme);
continue;
};
let laid = found.laid(&draft.plan, basis.nominal);
let rows = laid.options.len().saturating_add(2);
scroll.content_size.height = u16::try_from(rows).unwrap_or(u16::MAX);
let chosen = tool.highlighted().unwrap_or(0);
fill(&mut commands, table, laid, chosen, &theme);
}
}
pub fn say_instead(
commands: &mut Commands,
(table, scroll): (Entity, &mut ScrollArea),
said: String,
theme: &Theme,
) {
commands.entity(table).insert((
TableColumns(vec![Constraint::Fill(1)]),
ActiveDescendant(None),
));
commands.spawn((
table_row([Line::from(said)]),
CurrentRow,
UiStyle(theme.dimmed()),
ChildOf(table),
));
scroll.content_size.height = 1;
}
fn fill(commands: &mut Commands, table: Entity, laid: Laid, chosen: usize, theme: &Theme) {
let Laid {
header,
current,
mut options,
} = laid;
options.insert(0, current);
let spawned = tabulate::fill(commands, table, (&header, &options), 0);
let Some((¤t, options)) = spawned.split_first() else {
return;
};
commands
.entity(current)
.insert((CurrentRow, UiStyle(theme.dimmed())));
for (index, &option) in options.iter().enumerate() {
commands.entity(option).insert(OptionRow(index));
}
commands
.entity(table)
.insert(ActiveDescendant(options.get(chosen).copied()));
}
pub fn follow_cursor<R: Found>(
mut tables: Query<
(&mut ActiveDescendant, &Children),
(With<OptionsTable<R>>, Changed<ActiveDescendant>),
>,
rows: Query<(Has<CurrentRow>, Option<&OptionRow>)>,
mut tool: ResMut<Tool<R>>,
) {
for (mut on, children) in &mut tables {
let Some(row) = on.0 else {
continue;
};
match rows.get(row) {
Ok((true, _)) => {
let best = children
.iter()
.find(|&&child| rows.get(child).is_ok_and(|(_, option)| option.is_some()))
.copied();
on.0 = best;
}
Ok((false, Some(&OptionRow(index)))) => {
tool.bypass_change_detection().highlighted = index + 1;
}
_ => {}
}
}
}