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