use std::collections::HashSet;
use chrono::{FixedOffset, NaiveTime};
use chrono_tz::Tz;
#[cfg(feature = "clap")]
use clap::Parser;
use getset::Getters;
use pace_time::{date_time::PaceDateTime, time_zone::PaceTimeZoneKind, Validate};
use tracing::debug;
use typed_builder::TypedBuilder;
use crate::{
commands::UpdateOptions,
config::PaceConfig,
error::{ActivityLogErrorKind, PaceResult, UserMessage},
service::activity_store::ActivityStore,
storage::{get_storage_from_config, ActivityQuerying, ActivityWriteOps, SyncStorage},
};
#[derive(Debug, Clone, PartialEq, TypedBuilder, Eq, Hash, Default, Getters)]
#[getset(get = "pub")]
#[non_exhaustive]
#[cfg_attr(feature = "clap", derive(Parser))]
#[cfg_attr(
feature = "clap", clap(group = clap::ArgGroup::new("adjust").multiple(true).required(true)))]
#[cfg_attr(
feature = "clap", clap(group = clap::ArgGroup::new("tz").multiple(false).required(false)))]
pub struct AdjustCommandOptions {
#[cfg_attr(
feature = "clap",
clap(
short,
long,
group = "adjust",
value_name = "Category",
visible_alias = "cat"
)
)]
category: Option<String>,
#[cfg_attr(
feature = "clap",
clap(
short,
long,
group = "adjust",
value_name = "Description",
visible_alias = "desc"
)
)]
description: Option<String>,
#[cfg_attr(
feature = "clap",
clap(
short,
long,
group = "adjust",
value_name = "Starting Time",
visible_alias = "begin",
visible_alias = "at"
)
)]
start: Option<NaiveTime>,
#[cfg_attr(
feature = "clap",
clap(
short,
long,
group = "adjust",
name = "Tags",
value_name = "Tags",
visible_alias = "tag",
value_delimiter = ','
)
)]
tags: Option<Vec<String>>,
#[cfg_attr(
feature = "clap",
clap(
short,
long,
group = "adjust",
requires = "Tags",
value_name = "Override Tags",
)
)]
override_tags: bool,
#[cfg_attr(
feature = "clap",
clap(
short = 'z',
long,
group = "tz",
value_name = "Time Zone",
visible_alias = "tz"
)
)]
time_zone: Option<Tz>,
#[cfg_attr(
feature = "clap",
clap(
short = 'Z',
long,
group = "tz",
value_name = "Time Zone Offset",
visible_alias = "tzo"
)
)]
time_zone_offset: Option<FixedOffset>,
}
impl AdjustCommandOptions {
#[tracing::instrument(skip(self))]
pub fn handle_adjust(&self, config: &PaceConfig) -> PaceResult<UserMessage> {
let Self {
category,
description,
start,
tags,
override_tags,
time_zone,
time_zone_offset,
..
} = self;
let date_time = PaceDateTime::try_from((
start.as_ref(),
PaceTimeZoneKind::try_from((time_zone.as_ref(), 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(config)?)?;
let activity_item = activity_store
.most_recent_active_activity()?
.ok_or_else(|| ActivityLogErrorKind::NoActiveActivityToAdjust)?;
debug!("Most recent active activity item: {:?}", activity_item);
let guid = *activity_item.guid();
let mut activity = activity_item.activity().clone();
if let Some(category) = category {
debug!("Setting category to: {:?}", category);
_ = activity.set_category(category.clone().into());
}
if let Some(description) = description {
debug!("Setting description to: {:?}", description);
_ = activity.set_description(description.clone());
}
if start.is_some() {
debug!("Setting start time to: {:?}", date_time);
_ = activity.set_begin(date_time);
}
if let Some(tags) = tags {
let tags = tags.iter().cloned().collect::<HashSet<String>>();
if *override_tags {
debug!("Overriding tags with: {:?}", tags);
_ = activity.set_tags(Some(tags));
} else {
let merged_tags = activity.tags_mut().as_mut().map_or_else(
|| tags.clone(),
|existing_tags| existing_tags.union(&tags).cloned().collect(),
);
debug!("Setting merged tags: {:?}", merged_tags);
_ = activity.set_tags(Some(merged_tags));
}
}
_ = activity_store.update_activity(guid, activity.clone(), UpdateOptions::default())?;
if activity_item.activity() != &activity {
activity_store.sync()?;
return Ok(UserMessage::new(format!(
"{} has been adjusted.",
activity_item.activity()
)));
}
Ok(UserMessage::new("No changes were made."))
}
}