Skip to main content

mdvault_core/domain/
context.rs

1//! Context types for note creation.
2//!
3//! These types carry state through the note creation lifecycle.
4
5use std::collections::HashMap;
6use std::path::PathBuf;
7use std::sync::Arc;
8
9use crate::config::types::ResolvedConfig;
10use crate::templates::repository::LoadedTemplate;
11use crate::types::{TypeDefinition, TypeRegistry};
12
13/// Core metadata fields managed by Rust.
14/// These fields are authoritative and survive template/hook modifications.
15#[derive(Debug, Clone, Default)]
16pub struct CoreMetadata {
17    pub note_type: Option<String>,
18    pub title: Option<String>,
19    pub project_id: Option<String>,
20    pub task_id: Option<String>,
21    pub meeting_id: Option<String>,
22    pub task_counter: Option<u32>,
23    pub project: Option<String>, // Parent project for tasks
24    pub date: Option<String>,    // For daily/meeting notes
25    pub week: Option<String>,    // For weekly notes
26}
27
28impl CoreMetadata {
29    /// Convert to HashMap for merging into frontmatter.
30    pub fn to_yaml_map(&self) -> HashMap<String, serde_yaml::Value> {
31        let mut map = HashMap::new();
32        if let Some(ref t) = self.note_type {
33            map.insert("type".into(), serde_yaml::Value::String(t.clone()));
34        }
35        if let Some(ref t) = self.title {
36            map.insert("title".into(), serde_yaml::Value::String(t.clone()));
37        }
38        if let Some(ref id) = self.project_id {
39            map.insert("project-id".into(), serde_yaml::Value::String(id.clone()));
40        }
41        if let Some(ref id) = self.task_id {
42            map.insert("task-id".into(), serde_yaml::Value::String(id.clone()));
43        }
44        if let Some(ref id) = self.meeting_id {
45            map.insert("meeting-id".into(), serde_yaml::Value::String(id.clone()));
46        }
47        if let Some(counter) = self.task_counter {
48            map.insert("task_counter".into(), serde_yaml::Value::Number(counter.into()));
49        }
50        if let Some(ref p) = self.project {
51            map.insert("project".into(), serde_yaml::Value::String(p.clone()));
52        }
53        if let Some(ref d) = self.date {
54            map.insert("date".into(), serde_yaml::Value::String(d.clone()));
55        }
56        if let Some(ref w) = self.week {
57            map.insert("week".into(), serde_yaml::Value::String(w.clone()));
58        }
59        map
60    }
61}
62
63/// Context available during note creation.
64pub struct CreationContext<'a> {
65    // Core inputs
66    pub title: String,
67    pub type_name: String,
68
69    // Configuration
70    pub config: &'a ResolvedConfig,
71    pub typedef: Option<Arc<TypeDefinition>>,
72    pub registry: &'a TypeRegistry,
73
74    // State (accumulated during creation)
75    pub vars: HashMap<String, String>,
76    pub core_metadata: CoreMetadata,
77
78    // Output state
79    pub output_path: Option<PathBuf>,
80
81    // Template (if using template-based creation)
82    pub template: Option<LoadedTemplate>,
83
84    // Mode flags
85    pub batch_mode: bool,
86}
87
88impl<'a> CreationContext<'a> {
89    /// Create a new creation context.
90    pub fn new(
91        type_name: &str,
92        title: &str,
93        config: &'a ResolvedConfig,
94        registry: &'a TypeRegistry,
95    ) -> Self {
96        let typedef = registry.get(type_name);
97
98        let core_metadata = CoreMetadata {
99            note_type: Some(type_name.to_string()),
100            title: Some(title.to_string()),
101            ..Default::default()
102        };
103
104        let vars = HashMap::from([
105            ("title".to_string(), title.to_string()),
106            ("type".to_string(), type_name.to_string()),
107        ]);
108
109        Self {
110            title: title.to_string(),
111            type_name: type_name.to_string(),
112            config,
113            typedef,
114            registry,
115            vars,
116            core_metadata,
117            output_path: None,
118            template: None,
119            batch_mode: false,
120        }
121    }
122
123    /// Add CLI-provided variables.
124    pub fn with_vars(mut self, cli_vars: HashMap<String, String>) -> Self {
125        self.vars.extend(cli_vars);
126        self
127    }
128
129    /// Set a template to use for content generation.
130    pub fn with_template(mut self, template: LoadedTemplate) -> Self {
131        self.template = Some(template);
132        self
133    }
134
135    /// Set batch mode flag.
136    pub fn with_batch_mode(mut self, batch: bool) -> Self {
137        self.batch_mode = batch;
138        self
139    }
140
141    /// Get a variable value.
142    pub fn get_var(&self, key: &str) -> Option<&str> {
143        self.vars.get(key).map(|s| s.as_str())
144    }
145
146    /// Set a variable value.
147    pub fn set_var(&mut self, key: impl Into<String>, value: impl Into<String>) {
148        self.vars.insert(key.into(), value.into());
149    }
150
151    /// Create a PromptContext from this CreationContext.
152    pub fn to_prompt_context(&self) -> PromptContext<'_> {
153        PromptContext {
154            config: self.config,
155            type_name: &self.type_name,
156            title: &self.title,
157            provided_vars: &self.vars,
158            batch_mode: self.batch_mode,
159        }
160    }
161}
162
163/// Context for determining prompts.
164pub struct PromptContext<'a> {
165    pub config: &'a ResolvedConfig,
166    pub type_name: &'a str,
167    pub title: &'a str,
168    pub provided_vars: &'a HashMap<String, String>,
169    pub batch_mode: bool,
170}
171
172/// A single field prompt specification.
173#[derive(Debug, Clone)]
174pub struct FieldPrompt {
175    pub field_name: String,
176    pub prompt_text: String,
177    pub prompt_type: PromptType,
178    pub required: bool,
179    pub default_value: Option<String>,
180}
181
182/// Type of prompt to display.
183#[derive(Debug, Clone)]
184pub enum PromptType {
185    /// Single-line text input.
186    Text,
187    /// Multi-line text input.
188    Multiline,
189    /// Selection from a list of options.
190    Select(Vec<String>),
191    /// Special: pick from indexed projects.
192    ProjectSelector,
193}