1use crate::domain::documents::types::DocumentType;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub struct FlightLevelConfig {
8 pub strategies_enabled: bool,
10 pub initiatives_enabled: bool,
12}
13
14impl FlightLevelConfig {
15 pub fn new(
17 strategies_enabled: bool,
18 initiatives_enabled: bool,
19 ) -> Result<Self, ConfigurationError> {
20 if !initiatives_enabled && strategies_enabled {
22 return Err(ConfigurationError::InvalidConfiguration(
23 "Cannot enable strategies without initiatives - this would create a gap in the hierarchy".to_string()
24 ));
25 }
26
27 Ok(Self {
28 strategies_enabled,
29 initiatives_enabled,
30 })
31 }
32
33 pub fn full() -> Self {
35 Self {
36 strategies_enabled: true,
37 initiatives_enabled: true,
38 }
39 }
40
41 pub fn streamlined() -> Self {
43 Self {
44 strategies_enabled: false,
45 initiatives_enabled: true,
46 }
47 }
48
49 pub fn direct() -> Self {
51 Self {
52 strategies_enabled: false,
53 initiatives_enabled: false,
54 }
55 }
56
57 pub fn is_document_type_allowed(&self, doc_type: DocumentType) -> bool {
59 match doc_type {
60 DocumentType::Vision | DocumentType::Adr => true, DocumentType::Task => true, DocumentType::Strategy => self.strategies_enabled,
63 DocumentType::Initiative => self.initiatives_enabled,
64 }
65 }
66
67 pub fn get_parent_type(&self, doc_type: DocumentType) -> Option<DocumentType> {
69 match doc_type {
70 DocumentType::Vision | DocumentType::Adr => None, DocumentType::Strategy => Some(DocumentType::Vision),
72 DocumentType::Initiative => {
73 if self.strategies_enabled {
74 Some(DocumentType::Strategy)
75 } else {
76 Some(DocumentType::Vision)
77 }
78 }
79 DocumentType::Task => {
80 if self.initiatives_enabled {
81 Some(DocumentType::Initiative)
82 } else {
83 Some(DocumentType::Vision)
84 }
85 }
86 }
87 }
88
89 pub fn preset_name(&self) -> &'static str {
91 match (self.strategies_enabled, self.initiatives_enabled) {
92 (true, true) => "full",
93 (false, true) => "streamlined",
94 (false, false) => "direct",
95 (true, false) => "invalid", }
97 }
98
99 pub fn enabled_document_types(&self) -> Vec<DocumentType> {
101 let mut types = vec![DocumentType::Vision];
102
103 if self.strategies_enabled {
104 types.push(DocumentType::Strategy);
105 }
106
107 if self.initiatives_enabled {
108 types.push(DocumentType::Initiative);
109 }
110
111 types.push(DocumentType::Task);
112 types.push(DocumentType::Adr); types
115 }
116
117 pub fn hierarchy_display(&self) -> String {
119 let mut hierarchy = vec!["Vision"];
120
121 if self.strategies_enabled {
122 hierarchy.push("Strategy");
123 }
124
125 if self.initiatives_enabled {
126 hierarchy.push("Initiative");
127 }
128
129 hierarchy.push("Task");
130
131 hierarchy.join(" → ")
132 }
133}
134
135impl Default for FlightLevelConfig {
136 fn default() -> Self {
137 Self::full()
138 }
139}
140
141impl fmt::Display for FlightLevelConfig {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 write!(f, "{}", self.preset_name())
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Eq)]
149pub enum ConfigurationError {
150 InvalidConfiguration(String),
151 SerializationError(String),
152 InvalidValue(String),
153 MissingConfiguration(String),
154}
155
156impl fmt::Display for ConfigurationError {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 match self {
159 ConfigurationError::InvalidConfiguration(msg) => {
160 write!(f, "Invalid configuration: {}", msg)
161 }
162 ConfigurationError::SerializationError(msg) => {
163 write!(f, "Serialization error: {}", msg)
164 }
165 ConfigurationError::InvalidValue(msg) => write!(f, "Invalid value: {}", msg),
166 ConfigurationError::MissingConfiguration(msg) => {
167 write!(f, "Missing configuration: {}", msg)
168 }
169 }
170 }
171}
172
173impl std::error::Error for ConfigurationError {}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_preset_configurations() {
181 let full = FlightLevelConfig::full();
182 assert!(full.strategies_enabled);
183 assert!(full.initiatives_enabled);
184 assert_eq!(full.preset_name(), "full");
185
186 let streamlined = FlightLevelConfig::streamlined();
187 assert!(!streamlined.strategies_enabled);
188 assert!(streamlined.initiatives_enabled);
189 assert_eq!(streamlined.preset_name(), "streamlined");
190
191 let direct = FlightLevelConfig::direct();
192 assert!(!direct.strategies_enabled);
193 assert!(!direct.initiatives_enabled);
194 assert_eq!(direct.preset_name(), "direct");
195 }
196
197 #[test]
198 fn test_configuration_validation() {
199 assert!(FlightLevelConfig::new(true, true).is_ok());
201 assert!(FlightLevelConfig::new(false, true).is_ok());
202 assert!(FlightLevelConfig::new(false, false).is_ok());
203
204 assert!(FlightLevelConfig::new(true, false).is_err());
206 }
207
208 #[test]
209 fn test_document_type_allowed() {
210 let full = FlightLevelConfig::full();
211 assert!(full.is_document_type_allowed(DocumentType::Vision));
212 assert!(full.is_document_type_allowed(DocumentType::Strategy));
213 assert!(full.is_document_type_allowed(DocumentType::Initiative));
214 assert!(full.is_document_type_allowed(DocumentType::Task));
215 assert!(full.is_document_type_allowed(DocumentType::Adr));
216
217 let streamlined = FlightLevelConfig::streamlined();
218 assert!(streamlined.is_document_type_allowed(DocumentType::Vision));
219 assert!(!streamlined.is_document_type_allowed(DocumentType::Strategy));
220 assert!(streamlined.is_document_type_allowed(DocumentType::Initiative));
221 assert!(streamlined.is_document_type_allowed(DocumentType::Task));
222 assert!(streamlined.is_document_type_allowed(DocumentType::Adr));
223
224 let direct = FlightLevelConfig::direct();
225 assert!(direct.is_document_type_allowed(DocumentType::Vision));
226 assert!(!direct.is_document_type_allowed(DocumentType::Strategy));
227 assert!(!direct.is_document_type_allowed(DocumentType::Initiative));
228 assert!(direct.is_document_type_allowed(DocumentType::Task));
229 assert!(direct.is_document_type_allowed(DocumentType::Adr));
230 }
231
232 #[test]
233 fn test_parent_type_resolution() {
234 let full = FlightLevelConfig::full();
235 assert_eq!(full.get_parent_type(DocumentType::Vision), None);
236 assert_eq!(
237 full.get_parent_type(DocumentType::Strategy),
238 Some(DocumentType::Vision)
239 );
240 assert_eq!(
241 full.get_parent_type(DocumentType::Initiative),
242 Some(DocumentType::Strategy)
243 );
244 assert_eq!(
245 full.get_parent_type(DocumentType::Task),
246 Some(DocumentType::Initiative)
247 );
248 assert_eq!(full.get_parent_type(DocumentType::Adr), None);
249
250 let streamlined = FlightLevelConfig::streamlined();
251 assert_eq!(
252 streamlined.get_parent_type(DocumentType::Initiative),
253 Some(DocumentType::Vision)
254 );
255 assert_eq!(
256 streamlined.get_parent_type(DocumentType::Task),
257 Some(DocumentType::Initiative)
258 );
259
260 let direct = FlightLevelConfig::direct();
261 assert_eq!(
262 direct.get_parent_type(DocumentType::Task),
263 Some(DocumentType::Vision)
264 );
265 }
266
267 #[test]
268 fn test_enabled_document_types() {
269 let full = FlightLevelConfig::full();
270 let full_types = full.enabled_document_types();
271 assert_eq!(
272 full_types,
273 vec![
274 DocumentType::Vision,
275 DocumentType::Strategy,
276 DocumentType::Initiative,
277 DocumentType::Task,
278 DocumentType::Adr
279 ]
280 );
281
282 let streamlined = FlightLevelConfig::streamlined();
283 let streamlined_types = streamlined.enabled_document_types();
284 assert_eq!(
285 streamlined_types,
286 vec![
287 DocumentType::Vision,
288 DocumentType::Initiative,
289 DocumentType::Task,
290 DocumentType::Adr
291 ]
292 );
293
294 let direct = FlightLevelConfig::direct();
295 let direct_types = direct.enabled_document_types();
296 assert_eq!(
297 direct_types,
298 vec![DocumentType::Vision, DocumentType::Task, DocumentType::Adr]
299 );
300 }
301
302 #[test]
303 fn test_hierarchy_display() {
304 assert_eq!(
305 FlightLevelConfig::full().hierarchy_display(),
306 "Vision → Strategy → Initiative → Task"
307 );
308 assert_eq!(
309 FlightLevelConfig::streamlined().hierarchy_display(),
310 "Vision → Initiative → Task"
311 );
312 assert_eq!(
313 FlightLevelConfig::direct().hierarchy_display(),
314 "Vision → Task"
315 );
316 }
317
318 #[test]
319 fn test_serialization() {
320 let config = FlightLevelConfig::streamlined();
321 let json = serde_json::to_string(&config).unwrap();
322 let deserialized: FlightLevelConfig = serde_json::from_str(&json).unwrap();
323 assert_eq!(config, deserialized);
324 }
325}