use abscissa_core::{status_err, tracing::debug, Application, Command, Runnable, Shutdown};
use clap::Parser;
use eyre::Result;
use pace_cli::{confirmation_or_break, prompt_resume_activity};
use pace_core::prelude::{
get_storage_from_config, ActivityQuerying, ActivityReadOps, ActivityStateManagement,
ActivityStore, ResumeCommandOptions, ResumeOptions, SyncStorage, UserMessage,
};
use pace_time::{date_time::PaceDateTime, time_zone::PaceTimeZoneKind, Validate};
use crate::prelude::PACE_APP;
#[derive(Command, Debug, Parser)]
pub struct ResumeCmd {
#[clap(flatten)]
resume_opts: ResumeCommandOptions,
}
impl Runnable for ResumeCmd {
fn run(&self) {
match self.inner_run() {
Ok(user_message) => user_message.display(),
Err(err) => {
status_err!("{}", err);
PACE_APP.shutdown(Shutdown::Crash);
}
};
}
}
impl ResumeCmd {
pub fn inner_run(&self) -> Result<UserMessage> {
let config = &PACE_APP.config();
let date_time = PaceDateTime::try_from((
self.resume_opts.at().as_ref(),
PaceTimeZoneKind::try_from((
self.resume_opts.time_zone().as_ref(),
self.resume_opts.time_zone_offset().as_ref(),
))?,
PaceTimeZoneKind::from(config.general().default_time_zone().as_ref()),
))?
.validate()?;
debug!("Parsed time: {date_time:?}");
let activity_store =
ActivityStore::with_storage(get_storage_from_config(&PACE_APP.config())?)?;
let (msg, resumed) = if let Some(resumed_activity) = activity_store
.resume_most_recent_activity(ResumeOptions::builder().resume_time(date_time).build())?
{
(format!("Resumed {}", resumed_activity.activity()), true)
} else {
("".to_string(), false)
};
let user_message = if *self.resume_opts.list() || !resumed {
let Some(activity_ids) = activity_store.list_most_recent_activities(usize::from(
PACE_APP
.config()
.general()
.most_recent_count()
.unwrap_or_else(|| 9u8),
))?
else {
return Ok(UserMessage::new("No recent activities to continue."));
};
let activity_items = activity_ids
.iter()
.flat_map(|activity_id| activity_store.read_activity(*activity_id))
.filter_map(|activity| activity.activity().is_resumable().then_some(activity))
.collect::<Vec<_>>();
if activity_items.is_empty() {
return Ok(UserMessage::new("No activities to continue."));
}
let string_repr = activity_items
.iter()
.map(|activity| activity.activity().to_string())
.collect::<Vec<_>>();
let selection = prompt_resume_activity(&string_repr)?;
let Some(activity_item) = activity_items.get(selection) else {
return Ok(UserMessage::new("No activity selected to resume."));
};
let result =
activity_store.resume_activity(*activity_item.guid(), ResumeOptions::default());
let user_message = match result {
Ok(_) => format!("Resumed {}", activity_item.activity()),
Err(recoverable_err) if recoverable_err.possible_new_activity_from_resume() => {
debug!("Activity to resume: {:?}", activity_item.activity());
confirmation_or_break(
"We can't resume this already ended activity. Do you want to begin one with the same contents?",
)?;
debug!("Creating new activity from the same contents");
let new_activity = activity_item.activity().new_from_self();
debug!("New Activity: {:?}", new_activity);
let new_stored_activity = activity_store.begin_activity(new_activity)?;
debug!("Started Activity: {:?}", new_stored_activity);
format!("Resumed {}", new_stored_activity.activity())
}
Err(err) => return Err(err.into()),
};
user_message
} else {
msg
};
activity_store.sync()?;
Ok(UserMessage::new(user_message))
}
}