1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#[cfg(feature = "clap")]
use clap::Parser;

use getset::Getters;
use tracing::debug;
use typed_builder::TypedBuilder;

use crate::{
    get_storage_from_config, parse_time_from_user_input, ActivityStateManagement, ActivityStore,
    IntermissionAction, PaceConfig, PaceDateTime, PaceResult, SyncStorage, UserMessage,
};

/// `hold` subcommand options
#[derive(Debug)]
#[cfg_attr(feature = "clap", derive(Parser))]
pub struct HoldCommandOptions {
    /// The time the activity has been holded (defaults to the current time if not provided). Format: HH:MM
    #[cfg_attr(feature = "clap", clap(long, name = "Pause Time", alias = "at"))]
    // FIXME: We should directly parse that into PaceTime or PaceDateTime
    pause_at: Option<String>,

    /// The reason for the intermission, if this is not set, the description of the activity to be held will be used
    #[cfg_attr(feature = "clap", clap(short, long, name = "Reason"))]
    reason: Option<String>,

    /// If there are existing intermissions, they will be finished and a new one is being created
    ///
    /// This is useful, if you want to also track the purpose of an interruption to an activity.
    #[cfg_attr(feature = "clap", clap(long))]
    new_if_exists: bool,
}

impl HoldCommandOptions {
    /// Handles the `hold` subcommand
    ///
    /// # Arguments
    ///
    /// * `config` - The configuration for the pace application
    ///
    /// # Errors
    ///
    /// Returns an error if the activity could not be held
    ///
    /// # Returns
    ///
    /// A `UserMessage` with the information about the held activity that can be displayed to the user
    #[tracing::instrument(skip(self))]
    pub fn handle_hold(&self, config: &PaceConfig) -> PaceResult<UserMessage> {
        let action = if self.new_if_exists {
            IntermissionAction::New
        } else {
            IntermissionAction::Extend
        };

        let date_time = parse_time_from_user_input(&self.pause_at)?;

        debug!("Parsed date time: {:?}", date_time);

        let hold_opts = HoldOptions::builder()
            .action(action)
            .reason(self.reason.clone())
            .begin_time(date_time)
            .build();

        debug!("Hold options: {:?}", hold_opts);

        let activity_store = ActivityStore::with_storage(get_storage_from_config(config)?)?;

        let user_message =
            if let Some(activity) = activity_store.hold_most_recent_active_activity(hold_opts)? {
                debug!("Held {}", activity.activity());

                activity_store.sync()?;

                format!("Held {}", activity.activity())
            } else {
                "No unfinished activities to hold.".to_string()
            };

        Ok(UserMessage::new(user_message))
    }
}

/// Options for holding an activity
#[derive(Debug, Clone, PartialEq, TypedBuilder, Eq, Hash, Default, Getters)]
#[getset(get = "pub")]
#[non_exhaustive]
pub struct HoldOptions {
    /// The action to take on the intermission
    #[builder(default)]
    action: IntermissionAction,

    /// The start time of the intermission
    #[builder(default, setter(into))]
    begin_time: PaceDateTime,

    /// The reason for holding the activity
    #[builder(default, setter(into))]
    reason: Option<String>,
}