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