1use crate::backend::{load_config, say, tr};
13use jiff::Zoned;
14use jiff::civil::Date;
15use sioul_core::agenda::Occurrence;
16use sioul_core::capacity::{self, DayBalance, Record};
17use sioul_core::dayview::DayView;
18use sioul_core::plan::Settings;
19use sioul_core::tasks::Task;
20use sioul_core::timelog::Session;
21use std::collections::BTreeMap;
22use std::sync::{Arc, Mutex};
23
24fn events(from: i64, to: i64) -> Vec<Occurrence> {
27 type Kept = BTreeMap<(i64, i64), (i64, Vec<Occurrence>)>;
28 static KEPT: Mutex<Kept> = Mutex::new(BTreeMap::new());
29 let now = jiff::Timestamp::now().as_second();
30 let mut kept = KEPT.lock().unwrap_or_else(|e| e.into_inner());
31 kept.retain(|_, (at, _)| (0..300).contains(&(now - *at)));
32 if let Some((_, events)) = kept.get(&(from, to)) {
33 return events.clone();
34 }
35 let events = sioul_core::agenda::occurrences(from, to);
36 kept.insert((from, to), (now, events.clone()));
37 events
38}
39
40static LAST: Mutex<Option<Arc<Record>>> = Mutex::new(None);
42
43pub(crate) fn planned(mut settings: Settings, tasks: &[Task], sessions: &[Session]) -> Settings {
47 let own = |from: i64, to: i64| sioul_core::blocks::without_blocks(events(from, to), tasks);
49 let record = capacity::gather(tasks, sessions, &settings, &load_config().planning, &own, &Zoned::now());
50 settings.capacity = record.planning.clone();
51 *LAST.lock().unwrap_or_else(|e| e.into_inner()) = Some(Arc::new(record));
52 settings
53}
54
55pub(crate) fn last() -> Option<Arc<Record>> {
57 LAST.lock().unwrap_or_else(|e| e.into_inner()).clone()
58}
59
60pub(crate) fn ratio_line(task: &Task) -> String {
63 last().map(|record| record.ratio_line(task, tr())).unwrap_or_default()
64}
65
66pub(crate) fn dress(day: &mut DayView, tasks: &[Task]) {
73 let now = Zoned::now();
74 let record = last();
75 let suggestion = record.as_ref().and_then(|r| capacity::suggestion(tasks, &r.index, now.date()));
76 for block in &mut day.blocks {
77 match block.kind {
78 "gain" => {
79 block.title = tr().text("capacity-gain-slot", None);
80 block.note = suggestion.as_ref().map(|title| say("capacity-gain-suggestion", &[("title", title.clone())])).unwrap_or_default();
81 }
82 "slack" => block.title = tr().text("capacity-slack", None),
83 _ => {}
84 }
85 }
86 day.said = record.map(|r| r.reasons(now.date(), tr())).unwrap_or_default();
87 let slots = sioul_core::attention::Slots { day: Some(now.date()), slots: day.blocks.iter().filter(|b| b.kind == "gain").map(|b| (b.start, b.end)).collect() };
88 match slots.save(&sioul_core::attention::Slots::default_path()) {
89 Ok(true) => {
91 std::thread::spawn(|| crate::calls::refresh(true));
92 }
93 Ok(false) => {}
94 Err(e) => eprintln!("sioul: {e}"),
95 }
96}
97
98pub(crate) fn day_balance(date: Date) -> DayBalance {
103 let zone = jiff::tz::TimeZone::system();
104 let tasks = sioul_core::tasks::all(&zone);
105 let sessions = sioul_core::timelog::sessions();
106 let record = last().unwrap_or_else(|| {
107 let config = load_config();
108 let settings = Settings::of_hours(&config.week_hours(), sioul_core::areas::TaskAreas::usual());
109 let own = |from: i64, to: i64| sioul_core::blocks::without_blocks(events(from, to), &tasks);
110 Arc::new(capacity::gather(&tasks, &sessions, &settings, &config.planning, &own, &Zoned::now()))
111 });
112 let mut record = (*record).clone();
114 record.outcomes.remove(&date);
115 record.outcomes.extend(capacity::outcomes_of(&sioul_core::reviews::Reviews::load_between(&sioul_core::reviews::Reviews::default_path(), date, date)));
116 let midnight = |d: Date| d.to_zoned(zone.clone()).map_or(0, |z| z.timestamp().as_second());
117 let next = date.tomorrow().map_or(midnight(date) + 86_400, midnight);
118 let events = sioul_core::blocks::without_blocks(events(midnight(date), next), &tasks);
120 record.day_balance(&tasks, &sessions, &events, date, &zone, tr())
121}