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