lemma/
semantic.rs

1use crate::error::LemmaError;
2use crate::parsing::ast::Span;
3use crate::parsing::source::Source;
4use chrono::{Datelike, Timelike};
5use rust_decimal::Decimal;
6use serde::Serialize;
7use std::fmt;
8use std::hash::{Hash, Hasher};
9use std::str::FromStr;
10use std::sync::{Arc, OnceLock};
11
12/// A Lemma document containing facts and rules
13#[derive(Debug, Clone, PartialEq)]
14pub struct LemmaDoc {
15    pub name: String,
16    pub attribute: Option<String>,
17    pub start_line: usize,
18    pub commentary: Option<String>,
19    pub types: Vec<TypeDef>,
20    pub facts: Vec<LemmaFact>,
21    pub rules: Vec<LemmaRule>,
22}
23
24#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
25pub struct LemmaFact {
26    pub reference: FactReference,
27    pub value: FactValue,
28    pub source_location: Option<Source>,
29}
30
31/// An unless clause that provides an alternative result
32///
33/// Unless clauses are evaluated in order, and the last matching condition wins.
34/// This matches natural language: "X unless A then Y, unless B then Z" - if both
35/// A and B are true, Z is returned (the last match).
36#[derive(Debug, Clone, PartialEq, serde::Serialize)]
37pub struct UnlessClause {
38    pub condition: Expression,
39    pub result: Expression,
40    pub source_location: Option<Source>,
41}
42
43/// A rule with a single expression and optional unless clauses
44#[derive(Debug, Clone, PartialEq, serde::Serialize)]
45pub struct LemmaRule {
46    pub name: String,
47    pub expression: Expression,
48    pub unless_clauses: Vec<UnlessClause>,
49    pub source_location: Option<Source>,
50}
51
52/// An expression that can be evaluated, with source location
53///
54/// Expressions use semantic equality and hashing - two expressions with the same
55/// structure (kind) are equal/hash-equal regardless of source location.
56#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
57pub struct Expression {
58    pub kind: ExpressionKind,
59    pub source_location: Option<Source>,
60}
61
62impl Expression {
63    /// Create a new expression with kind and source location
64    #[must_use]
65    pub fn new(kind: ExpressionKind, source_location: Option<Source>) -> Self {
66        Self {
67            kind,
68            source_location,
69        }
70    }
71
72    /// Get the source text for this expression from the given sources map
73    ///
74    /// Returns `None` if the expression has no source location or the source is not found.
75    pub fn get_source_text(
76        &self,
77        sources: &std::collections::HashMap<String, String>,
78    ) -> Option<String> {
79        self.source_location.as_ref().and_then(|loc| {
80            sources
81                .get(&loc.attribute)
82                .and_then(|source| loc.extract_text(source))
83        })
84    }
85
86    /// Collect all FactPath references from this expression tree.
87    pub fn collect_fact_paths(&self, facts: &mut std::collections::HashSet<FactPath>) {
88        match &self.kind {
89            ExpressionKind::FactPath(fp) => {
90                facts.insert(fp.clone());
91            }
92            ExpressionKind::LogicalAnd(left, right)
93            | ExpressionKind::LogicalOr(left, right)
94            | ExpressionKind::Arithmetic(left, _, right)
95            | ExpressionKind::Comparison(left, _, right) => {
96                left.collect_fact_paths(facts);
97                right.collect_fact_paths(facts);
98            }
99            ExpressionKind::UnitConversion(inner, _)
100            | ExpressionKind::LogicalNegation(inner, _)
101            | ExpressionKind::MathematicalComputation(_, inner) => {
102                inner.collect_fact_paths(facts);
103            }
104            ExpressionKind::Literal(_)
105            | ExpressionKind::Reference(_)
106            | ExpressionKind::UnresolvedUnitLiteral(_, _)
107            | ExpressionKind::FactReference(_)
108            | ExpressionKind::RuleReference(_)
109            | ExpressionKind::Veto(_)
110            | ExpressionKind::RulePath(_) => {}
111        }
112    }
113
114    /// Compute semantic hash - hashes the expression structure, ignoring source location
115    fn semantic_hash<H: Hasher>(&self, state: &mut H) {
116        self.kind.semantic_hash(state);
117    }
118}
119
120/// Semantic equality - compares expressions by structure only, ignoring source location
121impl PartialEq for Expression {
122    fn eq(&self, other: &Self) -> bool {
123        self.kind == other.kind
124    }
125}
126
127impl Eq for Expression {}
128
129/// Semantic hashing - hashes expression structure only, ignoring source location
130impl Hash for Expression {
131    fn hash<H: Hasher>(&self, state: &mut H) {
132        self.semantic_hash(state);
133    }
134}
135
136/// The kind/type of expression
137#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
138pub enum ExpressionKind {
139    Literal(LiteralValue),
140    /// Unresolved reference from parser (resolved during planning)
141    Reference(Reference),
142    /// Unresolved unit literal from parser (resolved during planning)
143    /// Contains (number, unit_name) - the unit name will be resolved to its type during semantic analysis
144    UnresolvedUnitLiteral(Decimal, String),
145    /// Resolved fact reference (converted from Reference during planning)
146    FactReference(FactReference),
147    RuleReference(RuleReference),
148    LogicalAnd(Arc<Expression>, Arc<Expression>),
149    LogicalOr(Arc<Expression>, Arc<Expression>),
150    Arithmetic(Arc<Expression>, ArithmeticComputation, Arc<Expression>),
151    Comparison(Arc<Expression>, ComparisonComputation, Arc<Expression>),
152    UnitConversion(Arc<Expression>, ConversionTarget),
153    LogicalNegation(Arc<Expression>, NegationType),
154    MathematicalComputation(MathematicalComputation, Arc<Expression>),
155    Veto(VetoExpression),
156    /// Resolved fact path (used after planning, converted from FactReference)
157    FactPath(FactPath),
158    /// Resolved rule path (used after planning, converted from RuleReference)
159    RulePath(RulePath),
160}
161
162impl ExpressionKind {
163    /// Compute semantic hash for expression kinds
164    fn semantic_hash<H: Hasher>(&self, state: &mut H) {
165        // Hash discriminant first
166        std::mem::discriminant(self).hash(state);
167
168        match self {
169            ExpressionKind::Literal(lit) => lit.semantic_hash(state),
170            ExpressionKind::Reference(r) => r.hash(state),
171            ExpressionKind::UnresolvedUnitLiteral(_, _) => {
172                unreachable!("UnresolvedUnitLiteral found during hashing - this indicates a bug: unresolved units should be resolved during planning");
173            }
174            ExpressionKind::FactReference(fr) => fr.hash(state),
175            ExpressionKind::RuleReference(rr) => rr.hash(state),
176            ExpressionKind::LogicalAnd(left, right) | ExpressionKind::LogicalOr(left, right) => {
177                left.semantic_hash(state);
178                right.semantic_hash(state);
179            }
180            ExpressionKind::Arithmetic(left, op, right) => {
181                left.semantic_hash(state);
182                op.hash(state);
183                right.semantic_hash(state);
184            }
185            ExpressionKind::Comparison(left, op, right) => {
186                left.semantic_hash(state);
187                op.hash(state);
188                right.semantic_hash(state);
189            }
190            ExpressionKind::UnitConversion(expr, target) => {
191                expr.semantic_hash(state);
192                target.hash(state);
193            }
194            ExpressionKind::LogicalNegation(expr, neg_type) => {
195                expr.semantic_hash(state);
196                neg_type.hash(state);
197            }
198            ExpressionKind::MathematicalComputation(op, expr) => {
199                op.hash(state);
200                expr.semantic_hash(state);
201            }
202            ExpressionKind::Veto(veto) => veto.semantic_hash(state),
203            ExpressionKind::FactPath(fp) => fp.hash(state),
204            ExpressionKind::RulePath(rp) => rp.hash(state),
205        }
206    }
207}
208
209/// Unresolved reference from parser
210///
211/// During parsing, identifiers are captured as References.
212/// During planning, they are resolved to FactReference.
213/// Examples:
214/// - Local reference "age": segments=[], name="age"
215/// - Cross-document "employee.salary": segments=["employee"], name="salary"
216#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
217pub struct Reference {
218    pub segments: Vec<String>,
219    pub name: String,
220}
221
222/// Reference to a fact (resolved from Reference during planning)
223///
224/// Fact references use dot notation to traverse documents.
225/// Examples:
226/// - Local fact "age": segments=[], fact="age"
227/// - Cross-document "employee.salary": segments=["employee"], fact="salary"
228#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
229pub struct FactReference {
230    pub segments: Vec<String>,
231    pub fact: String,
232}
233
234/// Reference to a rule
235///
236/// Rule references use a question mark suffix to distinguish them from fact references.
237/// Examples:
238/// - Local rule "has_license?": segments=[], rule="has_license"
239/// - Cross-document "employee.is_eligible?": segments=["employee"], rule="is_eligible"
240#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
241pub struct RuleReference {
242    pub segments: Vec<String>,
243    pub rule: String,
244}
245
246/// A single segment in a path traversal
247///
248/// Used in both FactPath and RulePath to represent document traversal.
249/// Each segment contains a fact name that points to a document.
250#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
251pub struct PathSegment {
252    /// Fact name at this segment
253    pub fact: String,
254
255    /// Document name this fact points to
256    pub doc: String,
257}
258
259/// A resolved path to a fact, with document traversal segments
260///
261/// Used after planning to represent fully resolved fact references.
262/// Public because used in ExecutionPlan and evaluation.
263#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
264pub struct FactPath {
265    /// Path segments: each segment is a fact name that points to a document
266    pub segments: Vec<PathSegment>,
267
268    /// Final fact name
269    pub fact: String,
270}
271
272impl FactPath {
273    /// Returns true if this is a local fact (no document traversal)
274    #[must_use]
275    pub fn is_local(&self) -> bool {
276        self.segments.is_empty()
277    }
278
279    /// Create a new FactPath from segments and fact name
280    #[must_use]
281    pub fn new(segments: Vec<PathSegment>, fact: String) -> Self {
282        Self { segments, fact }
283    }
284
285    /// Create a local fact path (no document traversal)
286    #[must_use]
287    pub fn local(fact: String) -> Self {
288        Self {
289            segments: Vec::new(),
290            fact,
291        }
292    }
293
294    /// Create a FactPath from a full path of strings
295    ///
296    /// The last element becomes the fact name, all others become segments.
297    /// Segment doc fields are left empty since we only have fact names.
298    /// This is for backward compatibility with tests.
299    #[must_use]
300    pub fn from_path(mut path: Vec<String>) -> Self {
301        if path.is_empty() {
302            return Self {
303                segments: Vec::new(),
304                fact: String::new(),
305            };
306        }
307        let fact = path.pop().unwrap_or_default();
308        let segments = path
309            .into_iter()
310            .map(|fact_name| PathSegment {
311                fact: fact_name,
312                doc: String::new(),
313            })
314            .collect();
315        Self { segments, fact }
316    }
317
318    /// Get all path segments as fact names including the final fact name
319    #[must_use]
320    pub fn full_path(&self) -> Vec<String> {
321        let mut path: Vec<String> = self.segments.iter().map(|s| s.fact.clone()).collect();
322        path.push(self.fact.clone());
323        path
324    }
325}
326
327/// A resolved path to a rule, with document traversal segments
328///
329/// Used after planning to represent fully resolved rule references.
330/// Public because used in ExecutionPlan and evaluation.
331#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
332pub struct RulePath {
333    /// Path segments: each segment is a fact name that points to a document
334    pub segments: Vec<PathSegment>,
335
336    /// Final rule name
337    pub rule: String,
338}
339
340impl RulePath {
341    /// Returns true if this is a local rule (no document traversal)
342    #[must_use]
343    pub fn is_local(&self) -> bool {
344        self.segments.is_empty()
345    }
346
347    /// Create a local rule path (no document traversal)
348    #[must_use]
349    pub fn local(rule: String) -> Self {
350        Self {
351            segments: Vec::new(),
352            rule,
353        }
354    }
355}
356
357impl RuleReference {
358    /// Create from a full path (last element becomes rule)
359    pub fn from_path(mut full_path: Vec<String>) -> Self {
360        let rule = full_path.pop().unwrap_or_default();
361        Self {
362            segments: full_path,
363            rule,
364        }
365    }
366
367    /// Returns true if this is a local rule reference (no path segments)
368    #[must_use]
369    pub fn is_local(&self) -> bool {
370        self.segments.is_empty()
371    }
372
373    /// Get all path segments including the rule name
374    #[must_use]
375    pub fn full_path(&self) -> Vec<String> {
376        let mut path = self.segments.clone();
377        path.push(self.rule.clone());
378        path
379    }
380}
381
382/// Arithmetic computations
383#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
384pub enum ArithmeticComputation {
385    Add,
386    Subtract,
387    Multiply,
388    Divide,
389    Modulo,
390    Power,
391}
392
393impl ArithmeticComputation {
394    /// Returns a human-readable name for the computation
395    #[must_use]
396    pub fn name(&self) -> &'static str {
397        match self {
398            ArithmeticComputation::Add => "addition",
399            ArithmeticComputation::Subtract => "subtraction",
400            ArithmeticComputation::Multiply => "multiplication",
401            ArithmeticComputation::Divide => "division",
402            ArithmeticComputation::Modulo => "modulo",
403            ArithmeticComputation::Power => "exponentiation",
404        }
405    }
406
407    /// Returns the operator symbol
408    #[must_use]
409    pub fn symbol(&self) -> &'static str {
410        match self {
411            ArithmeticComputation::Add => "+",
412            ArithmeticComputation::Subtract => "-",
413            ArithmeticComputation::Multiply => "*",
414            ArithmeticComputation::Divide => "/",
415            ArithmeticComputation::Modulo => "%",
416            ArithmeticComputation::Power => "^",
417        }
418    }
419}
420
421/// Comparison computations
422#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
423pub enum ComparisonComputation {
424    GreaterThan,
425    LessThan,
426    GreaterThanOrEqual,
427    LessThanOrEqual,
428    Equal,
429    NotEqual,
430    Is,
431    IsNot,
432}
433
434impl ComparisonComputation {
435    /// Returns a human-readable name for the computation
436    #[must_use]
437    pub fn name(&self) -> &'static str {
438        match self {
439            ComparisonComputation::GreaterThan => "greater than",
440            ComparisonComputation::LessThan => "less than",
441            ComparisonComputation::GreaterThanOrEqual => "greater than or equal",
442            ComparisonComputation::LessThanOrEqual => "less than or equal",
443            ComparisonComputation::Equal => "equal",
444            ComparisonComputation::NotEqual => "not equal",
445            ComparisonComputation::Is => "is",
446            ComparisonComputation::IsNot => "is not",
447        }
448    }
449
450    /// Returns the operator symbol
451    #[must_use]
452    pub fn symbol(&self) -> &'static str {
453        match self {
454            ComparisonComputation::GreaterThan => ">",
455            ComparisonComputation::LessThan => "<",
456            ComparisonComputation::GreaterThanOrEqual => ">=",
457            ComparisonComputation::LessThanOrEqual => "<=",
458            ComparisonComputation::Equal => "==",
459            ComparisonComputation::NotEqual => "!=",
460            ComparisonComputation::Is => "is",
461            ComparisonComputation::IsNot => "is not",
462        }
463    }
464
465    /// Check if this is an equality comparison (== or is)
466    #[must_use]
467    pub fn is_equal(&self) -> bool {
468        matches!(
469            self,
470            ComparisonComputation::Equal | ComparisonComputation::Is
471        )
472    }
473
474    /// Check if this is an inequality comparison (!= or is not)
475    #[must_use]
476    pub fn is_not_equal(&self) -> bool {
477        matches!(
478            self,
479            ComparisonComputation::NotEqual | ComparisonComputation::IsNot
480        )
481    }
482}
483
484/// The target unit for unit conversion expressions
485#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
486pub enum ConversionTarget {
487    Duration(DurationUnit),
488    Percentage,
489}
490
491/// Types of logical negation
492#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
493pub enum NegationType {
494    Not,
495}
496
497/// Logical computations
498#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
499pub enum LogicalComputation {
500    And,
501    Or,
502    Not,
503}
504
505/// A veto expression that prohibits any valid verdict from the rule
506///
507/// Unlike `reject` (which is just an alias for boolean `false`), a veto
508/// prevents the rule from producing any valid result. This is used for
509/// validation and constraint enforcement.
510///
511/// Example: `veto "Must be over 18"` - blocks the rule entirely with a message
512#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
513pub struct VetoExpression {
514    pub message: Option<String>,
515}
516
517impl VetoExpression {
518    fn semantic_hash<H: Hasher>(&self, state: &mut H) {
519        self.message.hash(state);
520    }
521}
522
523/// Mathematical computations
524#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
525pub enum MathematicalComputation {
526    Sqrt,
527    Sin,
528    Cos,
529    Tan,
530    Asin,
531    Acos,
532    Atan,
533    Log,
534    Exp,
535    Abs,
536    Floor,
537    Ceil,
538    Round,
539}
540
541#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
542pub enum FactValue {
543    Literal(LiteralValue),
544    DocumentReference(String),
545    TypeDeclaration {
546        base: String,
547        overrides: Option<Vec<(String, Vec<String>)>>,
548        from: Option<String>,
549    },
550}
551
552/// A type for type declarations
553/// Boolean value with original input preserved
554#[derive(
555    Debug,
556    Clone,
557    PartialEq,
558    Serialize,
559    serde::Deserialize,
560    strum_macros::EnumString,
561    strum_macros::Display,
562)]
563#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
564pub enum BooleanValue {
565    True,
566    False,
567    Yes,
568    No,
569    Accept,
570    Reject,
571}
572
573impl From<BooleanValue> for bool {
574    fn from(value: BooleanValue) -> bool {
575        match value {
576            BooleanValue::True | BooleanValue::Yes | BooleanValue::Accept => true,
577            BooleanValue::False | BooleanValue::No | BooleanValue::Reject => false,
578        }
579    }
580}
581
582impl From<&BooleanValue> for bool {
583    fn from(value: &BooleanValue) -> bool {
584        match value {
585            BooleanValue::True | BooleanValue::Yes | BooleanValue::Accept => true,
586            BooleanValue::False | BooleanValue::No | BooleanValue::Reject => false,
587        }
588    }
589}
590
591impl From<bool> for BooleanValue {
592    fn from(value: bool) -> BooleanValue {
593        if value {
594            BooleanValue::True
595        } else {
596            BooleanValue::False
597        }
598    }
599}
600
601impl std::ops::Not for BooleanValue {
602    type Output = BooleanValue;
603
604    fn not(self) -> Self::Output {
605        if self.into() {
606            BooleanValue::False
607        } else {
608            BooleanValue::True
609        }
610    }
611}
612
613impl std::ops::Not for &BooleanValue {
614    type Output = BooleanValue;
615
616    fn not(self) -> Self::Output {
617        if self.into() {
618            BooleanValue::False
619        } else {
620            BooleanValue::True
621        }
622    }
623}
624
625/// The actual value data (without type information)
626#[derive(Debug, Clone, PartialEq, Serialize, serde::Deserialize)]
627pub enum Value {
628    Number(Decimal),
629    Scale(Decimal, Option<String>), // value, optional unit name (e.g., "eur", "usd", "kilogram")
630    Text(String),
631    Date(DateTimeValue),
632    Time(TimeValue),
633    Boolean(BooleanValue),
634    Duration(Decimal, DurationUnit),
635    Ratio(Decimal, Option<String>), // value, optional unit name (e.g., "percent", "permille")
636}
637
638/// A literal value with its type
639///
640/// Every literal value knows its type - no distinction between standard and custom types.
641#[derive(Debug, Clone, PartialEq, Serialize, serde::Deserialize)]
642pub struct LiteralValue {
643    pub value: Value,
644    pub lemma_type: LemmaType,
645}
646
647impl LiteralValue {
648    /// Create a Number literal value from any type that can convert to Decimal
649    /// Uses STANDARD_NUMBER as the type
650    pub fn number<T: Into<Decimal>>(value: T) -> Self {
651        LiteralValue {
652            value: Value::Number(value.into()),
653            lemma_type: standard_number().clone(),
654        }
655    }
656
657    /// Create a Number literal value with a custom type
658    pub fn number_with_type<T: Into<Decimal>>(value: T, lemma_type: LemmaType) -> Self {
659        LiteralValue {
660            value: Value::Number(value.into()),
661            lemma_type,
662        }
663    }
664
665    /// Create a Scale literal value
666    /// Uses the provided type (must be a Scale type)
667    pub fn scale<T: Into<Decimal>>(value: T, unit: Option<String>) -> Self {
668        LiteralValue {
669            value: Value::Scale(value.into(), unit),
670            lemma_type: crate::semantic::standard_scale().clone(),
671        }
672    }
673
674    /// Create a Scale literal value with a custom type
675    pub fn scale_with_type<T: Into<Decimal>>(
676        value: T,
677        unit: Option<String>,
678        lemma_type: LemmaType,
679    ) -> Self {
680        LiteralValue {
681            value: Value::Scale(value.into(), unit),
682            lemma_type,
683        }
684    }
685
686    /// Create a Text literal value
687    /// Uses STANDARD_TEXT as the type
688    pub fn text(value: String) -> Self {
689        LiteralValue {
690            value: Value::Text(value),
691            lemma_type: standard_text().clone(),
692        }
693    }
694
695    /// Create a Text literal value with a custom type
696    pub fn text_with_type(value: String, lemma_type: LemmaType) -> Self {
697        LiteralValue {
698            value: Value::Text(value),
699            lemma_type,
700        }
701    }
702
703    /// Create a Boolean literal value
704    /// Uses STANDARD_BOOLEAN as the type
705    pub fn boolean(value: BooleanValue) -> Self {
706        let canonical: BooleanValue = bool::from(&value).into();
707        LiteralValue {
708            value: Value::Boolean(canonical),
709            lemma_type: standard_boolean().clone(),
710        }
711    }
712
713    /// Create a Boolean literal value with a custom type
714    pub fn boolean_with_type(value: BooleanValue, lemma_type: LemmaType) -> Self {
715        let canonical: BooleanValue = bool::from(&value).into();
716        LiteralValue {
717            value: Value::Boolean(canonical),
718            lemma_type,
719        }
720    }
721
722    /// Create a Date literal value
723    /// Uses STANDARD_DATE as the type
724    pub fn date(value: DateTimeValue) -> Self {
725        LiteralValue {
726            value: Value::Date(value),
727            lemma_type: standard_date().clone(),
728        }
729    }
730
731    /// Create a Date literal value with a custom type
732    pub fn date_with_type(value: DateTimeValue, lemma_type: LemmaType) -> Self {
733        LiteralValue {
734            value: Value::Date(value),
735            lemma_type,
736        }
737    }
738
739    /// Create a Time literal value
740    /// Uses STANDARD_TIME as the type
741    pub fn time(value: TimeValue) -> Self {
742        LiteralValue {
743            value: Value::Time(value),
744            lemma_type: standard_time().clone(),
745        }
746    }
747
748    /// Create a Time literal value with a custom type
749    pub fn time_with_type(value: TimeValue, lemma_type: LemmaType) -> Self {
750        LiteralValue {
751            value: Value::Time(value),
752            lemma_type,
753        }
754    }
755
756    /// Create a Duration literal value
757    /// Uses STANDARD_DURATION as the type
758    pub fn duration(value: Decimal, unit: DurationUnit) -> Self {
759        LiteralValue {
760            value: Value::Duration(value, unit),
761            lemma_type: standard_duration().clone(),
762        }
763    }
764
765    /// Create a Duration literal value with a custom type
766    pub fn duration_with_type(value: Decimal, unit: DurationUnit, lemma_type: LemmaType) -> Self {
767        LiteralValue {
768            value: Value::Duration(value, unit),
769            lemma_type,
770        }
771    }
772
773    /// Create a Ratio literal value
774    /// Uses STANDARD_RATIO as the type
775    pub fn ratio<T: Into<Decimal>>(value: T, unit: Option<String>) -> Self {
776        LiteralValue {
777            value: Value::Ratio(value.into(), unit),
778            lemma_type: standard_ratio().clone(),
779        }
780    }
781
782    /// Create a Ratio literal value with a custom type
783    pub fn ratio_with_type<T: Into<Decimal>>(
784        value: T,
785        unit: Option<String>,
786        lemma_type: LemmaType,
787    ) -> Self {
788        LiteralValue {
789            value: Value::Ratio(value.into(), unit),
790            lemma_type,
791        }
792    }
793
794    /// Get the type of this literal value
795    pub fn get_type(&self) -> &LemmaType {
796        &self.lemma_type
797    }
798
799    /// Get the display value as a string (uses the Display implementation)
800    #[must_use]
801    pub fn display_value(&self) -> String {
802        self.to_string()
803    }
804
805    /// Get the byte size of this literal value for resource limiting
806    pub fn byte_size(&self) -> usize {
807        match &self.value {
808            Value::Text(s) => s.len(),
809            Value::Number(d) => std::mem::size_of_val(d),
810            Value::Scale(d, _) => std::mem::size_of_val(d),
811            Value::Boolean(_) => std::mem::size_of::<bool>(),
812            Value::Date(_) => std::mem::size_of::<DateTimeValue>(),
813            Value::Time(_) => std::mem::size_of::<TimeValue>(),
814            Value::Duration(value, _) => std::mem::size_of_val(value),
815            Value::Ratio(value, _) => std::mem::size_of_val(value),
816        }
817    }
818
819    /// Compute semantic hash for literal values
820    /// Uses string representation for Decimal to avoid Hash trait requirement
821    fn semantic_hash<H: Hasher>(&self, state: &mut H) {
822        std::mem::discriminant(&self.value).hash(state);
823        match &self.value {
824            Value::Number(d) | Value::Scale(d, _) | Value::Ratio(d, _) => {
825                d.to_string().hash(state);
826            }
827            Value::Text(s) => s.hash(state),
828            Value::Boolean(b) => std::mem::discriminant(b).hash(state),
829            Value::Date(dt) => {
830                dt.year.hash(state);
831                dt.month.hash(state);
832                dt.day.hash(state);
833                dt.hour.hash(state);
834                dt.minute.hash(state);
835                dt.second.hash(state);
836                if let Some(tz) = &dt.timezone {
837                    tz.offset_hours.hash(state);
838                    tz.offset_minutes.hash(state);
839                }
840            }
841            Value::Time(t) => {
842                t.hour.hash(state);
843                t.minute.hash(state);
844                t.second.hash(state);
845                if let Some(tz) = &t.timezone {
846                    tz.offset_hours.hash(state);
847                    tz.offset_minutes.hash(state);
848                }
849            }
850            Value::Duration(value, unit) => {
851                value.to_string().hash(state);
852                std::mem::discriminant(unit).hash(state);
853            }
854        }
855    }
856}
857
858/// A time value
859#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, serde::Deserialize)]
860pub struct TimeValue {
861    pub hour: u8,
862    pub minute: u8,
863    pub second: u8,
864    pub timezone: Option<TimezoneValue>,
865}
866
867/// A timezone value
868#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
869pub struct TimezoneValue {
870    pub offset_hours: i8,
871    pub offset_minutes: u8,
872}
873
874/// A datetime value that preserves timezone information
875#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
876pub struct DateTimeValue {
877    pub year: i32,
878    pub month: u32,
879    pub day: u32,
880    pub hour: u32,
881    pub minute: u32,
882    pub second: u32,
883    pub timezone: Option<TimezoneValue>,
884}
885
886/// Unit types for different physical quantities
887macro_rules! impl_unit_serialize {
888    ($($unit_type:ty),+) => {
889        $(
890            impl Serialize for $unit_type {
891                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
892                where
893                    S: serde::Serializer,
894                {
895                    serializer.serialize_str(&self.to_string())
896                }
897            }
898        )+
899    };
900}
901
902impl_unit_serialize!(DurationUnit);
903
904#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Deserialize, strum_macros::EnumString)]
905#[strum(serialize_all = "lowercase")]
906pub enum DurationUnit {
907    Year,
908    Month,
909    Week,
910    Day,
911    Hour,
912    Minute,
913    Second,
914    Millisecond,
915    Microsecond,
916}
917
918impl fmt::Display for DurationUnit {
919    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
920        let s = match self {
921            DurationUnit::Year => "years",
922            DurationUnit::Month => "months",
923            DurationUnit::Week => "weeks",
924            DurationUnit::Day => "days",
925            DurationUnit::Hour => "hours",
926            DurationUnit::Minute => "minutes",
927            DurationUnit::Second => "seconds",
928            DurationUnit::Millisecond => "milliseconds",
929            DurationUnit::Microsecond => "microseconds",
930        };
931        write!(f, "{}", s)
932    }
933}
934
935impl Reference {
936    #[must_use]
937    pub fn new(segments: Vec<String>, name: String) -> Self {
938        Self { segments, name }
939    }
940
941    #[must_use]
942    pub fn local(name: String) -> Self {
943        Self {
944            segments: Vec::new(),
945            name,
946        }
947    }
948
949    #[must_use]
950    pub fn from_path(path: Vec<String>) -> Self {
951        if path.is_empty() {
952            Self {
953                segments: Vec::new(),
954                name: String::new(),
955            }
956        } else {
957            // Safe: path is non-empty.
958            let name = path[path.len() - 1].clone();
959            let segments = path[..path.len() - 1].to_vec();
960            Self { segments, name }
961        }
962    }
963
964    #[must_use]
965    pub fn is_local(&self) -> bool {
966        self.segments.is_empty()
967    }
968
969    #[must_use]
970    pub fn full_path(&self) -> Vec<String> {
971        let mut path = self.segments.clone();
972        path.push(self.name.clone());
973        path
974    }
975
976    /// Convert to FactReference (used during planning resolution)
977    #[must_use]
978    pub fn to_fact_reference(&self) -> FactReference {
979        FactReference {
980            segments: self.segments.clone(),
981            fact: self.name.clone(),
982        }
983    }
984}
985
986impl FactReference {
987    /// Create a new FactReference from segments and fact name
988    #[must_use]
989    pub fn new(segments: Vec<String>, fact: String) -> Self {
990        Self { segments, fact }
991    }
992
993    /// Create a FactReference from a single fact name (local reference)
994    #[must_use]
995    pub fn local(fact: String) -> Self {
996        Self {
997            segments: Vec::new(),
998            fact,
999        }
1000    }
1001
1002    /// Create a FactReference from a Vec<String> path (for backward compatibility during migration)
1003    #[must_use]
1004    pub fn from_path(path: Vec<String>) -> Self {
1005        if path.is_empty() {
1006            Self {
1007                segments: Vec::new(),
1008                fact: String::new(),
1009            }
1010        } else {
1011            // Safe: path is non-empty.
1012            let fact = path[path.len() - 1].clone();
1013            let segments = path[..path.len() - 1].to_vec();
1014            Self { segments, fact }
1015        }
1016    }
1017
1018    /// Returns true if this is a local reference (no path segments)
1019    #[must_use]
1020    pub fn is_local(&self) -> bool {
1021        self.segments.is_empty()
1022    }
1023
1024    /// Get all path segments including the fact name
1025    #[must_use]
1026    pub fn full_path(&self) -> Vec<String> {
1027        let mut path = self.segments.clone();
1028        path.push(self.fact.clone());
1029        path
1030    }
1031}
1032
1033impl LemmaFact {
1034    #[must_use]
1035    pub fn new(reference: FactReference, value: FactValue) -> Self {
1036        Self {
1037            reference,
1038            value,
1039            source_location: None,
1040        }
1041    }
1042
1043    #[must_use]
1044    pub fn with_source_location(mut self, source_location: Source) -> Self {
1045        self.source_location = Some(source_location);
1046        self
1047    }
1048
1049    /// Returns true if this fact is local (not a cross-document reference)
1050    #[must_use]
1051    pub fn is_local(&self) -> bool {
1052        self.reference.is_local()
1053    }
1054}
1055
1056impl LemmaDoc {
1057    #[must_use]
1058    pub fn new(name: String) -> Self {
1059        Self {
1060            name,
1061            attribute: None,
1062            start_line: 1,
1063            commentary: None,
1064            types: Vec::new(),
1065            facts: Vec::new(),
1066            rules: Vec::new(),
1067        }
1068    }
1069
1070    #[must_use]
1071    pub fn with_attribute(mut self, attribute: String) -> Self {
1072        self.attribute = Some(attribute);
1073        self
1074    }
1075
1076    #[must_use]
1077    pub fn with_start_line(mut self, start_line: usize) -> Self {
1078        self.start_line = start_line;
1079        self
1080    }
1081
1082    #[must_use]
1083    pub fn set_commentary(mut self, commentary: String) -> Self {
1084        self.commentary = Some(commentary);
1085        self
1086    }
1087
1088    #[must_use]
1089    pub fn add_fact(mut self, fact: LemmaFact) -> Self {
1090        self.facts.push(fact);
1091        self
1092    }
1093
1094    #[must_use]
1095    pub fn add_rule(mut self, rule: LemmaRule) -> Self {
1096        self.rules.push(rule);
1097        self
1098    }
1099
1100    #[must_use]
1101    pub fn add_type(mut self, type_def: TypeDef) -> Self {
1102        self.types.push(type_def);
1103        self
1104    }
1105
1106    /// Get the expected type for a fact by path
1107    /// Returns None if the fact is not found in this document or if the fact is a document reference
1108    pub fn get_fact_type(&self, fact_ref: &[String]) -> Option<LemmaType> {
1109        let fact_path: Vec<String> = fact_ref.to_vec();
1110        let fact_name = fact_path.last()?.clone();
1111        let segments: Vec<String> = fact_path[..fact_path.len().saturating_sub(1)].to_vec();
1112        let target_ref = FactReference {
1113            segments,
1114            fact: fact_name,
1115        };
1116        self.facts
1117            .iter()
1118            .find(|fact| fact.reference == target_ref)
1119            .and_then(|fact| match &fact.value {
1120                FactValue::Literal(lit) => Some(lit.get_type().clone()),
1121                FactValue::TypeDeclaration { .. } => {
1122                    // Type resolution happens during planning phase
1123                    None
1124                }
1125                FactValue::DocumentReference(_) => None,
1126            })
1127    }
1128}
1129
1130impl fmt::Display for LemmaDoc {
1131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1132        write!(f, "doc {}", self.name)?;
1133        writeln!(f)?;
1134
1135        if let Some(ref commentary) = self.commentary {
1136            writeln!(f, "\"\"\"{}", commentary)?;
1137            writeln!(f, "\"\"\"")?;
1138        }
1139
1140        for fact in &self.facts {
1141            write!(f, "{}", fact)?;
1142        }
1143
1144        for rule in &self.rules {
1145            write!(f, "{}", rule)?;
1146        }
1147
1148        Ok(())
1149    }
1150}
1151
1152impl fmt::Display for FactReference {
1153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1154        for segment in &self.segments {
1155            write!(f, "{}.", segment)?;
1156        }
1157        write!(f, "{}", self.fact)
1158    }
1159}
1160
1161impl fmt::Display for LemmaFact {
1162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1163        writeln!(f, "fact {} = {}", self.reference, self.value)
1164    }
1165}
1166
1167impl fmt::Display for LemmaRule {
1168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1169        write!(f, "rule {} = {}", self.name, self.expression)?;
1170
1171        for unless_clause in &self.unless_clauses {
1172            write!(
1173                f,
1174                " unless {} then {}",
1175                unless_clause.condition, unless_clause.result
1176            )?;
1177        }
1178
1179        writeln!(f)?;
1180        Ok(())
1181    }
1182}
1183
1184impl fmt::Display for Expression {
1185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1186        match &self.kind {
1187            ExpressionKind::Literal(lit) => write!(f, "{}", lit),
1188            ExpressionKind::Reference(r) => {
1189                if r.segments.is_empty() {
1190                    write!(f, "{}", r.name)
1191                } else {
1192                    write!(f, "{}.{}", r.segments.join("."), r.name)
1193                }
1194            }
1195            ExpressionKind::FactReference(fact_ref) => write!(f, "{}", fact_ref),
1196            ExpressionKind::FactPath(fact_path) => write!(f, "{}", fact_path),
1197            ExpressionKind::RuleReference(rule_ref) => write!(f, "{}", rule_ref),
1198            ExpressionKind::RulePath(rule_path) => write!(f, "{}", rule_path),
1199            ExpressionKind::Arithmetic(left, op, right) => {
1200                write!(f, "{} {} {}", left, op, right)
1201            }
1202            ExpressionKind::Comparison(left, op, right) => {
1203                write!(f, "{} {} {}", left, op, right)
1204            }
1205            ExpressionKind::UnitConversion(value, target) => {
1206                write!(f, "{} in {}", value, target)
1207            }
1208            ExpressionKind::LogicalNegation(expr, _) => {
1209                write!(f, "not {}", expr)
1210            }
1211            ExpressionKind::LogicalAnd(left, right) => {
1212                write!(f, "{} and {}", left, right)
1213            }
1214            ExpressionKind::LogicalOr(left, right) => {
1215                write!(f, "{} or {}", left, right)
1216            }
1217            ExpressionKind::MathematicalComputation(op, operand) => {
1218                let op_name = match op {
1219                    MathematicalComputation::Sqrt => "sqrt",
1220                    MathematicalComputation::Sin => "sin",
1221                    MathematicalComputation::Cos => "cos",
1222                    MathematicalComputation::Tan => "tan",
1223                    MathematicalComputation::Asin => "asin",
1224                    MathematicalComputation::Acos => "acos",
1225                    MathematicalComputation::Atan => "atan",
1226                    MathematicalComputation::Log => "log",
1227                    MathematicalComputation::Exp => "exp",
1228                    MathematicalComputation::Abs => "abs",
1229                    MathematicalComputation::Floor => "floor",
1230                    MathematicalComputation::Ceil => "ceil",
1231                    MathematicalComputation::Round => "round",
1232                };
1233                write!(f, "{} {}", op_name, operand)
1234            }
1235            ExpressionKind::Veto(veto) => match &veto.message {
1236                Some(msg) => write!(f, "veto \"{}\"", msg),
1237                None => write!(f, "veto"),
1238            },
1239            ExpressionKind::UnresolvedUnitLiteral(number, unit_name) => {
1240                write!(f, "{} {}", number, unit_name)
1241            }
1242        }
1243    }
1244}
1245
1246impl fmt::Display for LiteralValue {
1247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1248        match &self.value {
1249            Value::Number(n) => {
1250                // Get decimals from type specification if available
1251                let decimals_opt = match &self.lemma_type.specifications {
1252                    TypeSpecification::Number { decimals, .. } => *decimals,
1253                    _ => None,
1254                };
1255
1256                if let Some(decimals) = decimals_opt {
1257                    // Format with fixed decimal places, always showing all decimals
1258                    let rounded = n.round_dp(decimals as u32);
1259                    let mut s = rounded.to_string();
1260                    // Ensure we have the right number of decimal places
1261                    if let Some(dot_pos) = s.find('.') {
1262                        let current_decimals = s.len() - dot_pos - 1;
1263                        if current_decimals < decimals as usize {
1264                            // Pad with zeros
1265                            s.push_str(&"0".repeat(decimals as usize - current_decimals));
1266                        } else if current_decimals > decimals as usize {
1267                            // This shouldn't happen due to round_dp, but handle it
1268                            let truncate_pos = dot_pos + 1 + decimals as usize;
1269                            s = s[..truncate_pos].to_string();
1270                        }
1271                    } else {
1272                        // No decimal point, add it with zeros
1273                        s.push('.');
1274                        s.push_str(&"0".repeat(decimals as usize));
1275                    }
1276                    write!(f, "{}", s)
1277                } else {
1278                    // No decimals specified: normalize (remove trailing zeros)
1279                    let normalized = n.normalize();
1280                    if normalized.fract().is_zero() {
1281                        let int_part = normalized.trunc().to_string();
1282                        let formatted = int_part
1283                            .chars()
1284                            .rev()
1285                            .enumerate()
1286                            .flat_map(|(i, c)| {
1287                                if i > 0 && i % 3 == 0 && c != '-' {
1288                                    vec![',', c]
1289                                } else {
1290                                    vec![c]
1291                                }
1292                            })
1293                            .collect::<String>()
1294                            .chars()
1295                            .rev()
1296                            .collect::<String>();
1297                        write!(f, "{}", formatted)
1298                    } else {
1299                        write!(f, "{}", normalized)
1300                    }
1301                }
1302            }
1303            Value::Text(s) => {
1304                let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
1305                write!(f, "\"{}\"", escaped)
1306            }
1307            Value::Date(dt) => write!(f, "{}", dt),
1308            Value::Boolean(b) => write!(f, "{}", b),
1309            Value::Time(time) => {
1310                write!(f, "time({}, {}, {})", time.hour, time.minute, time.second)
1311            }
1312            Value::Scale(n, unit_opt) => {
1313                // Format the number part (same as Number)
1314                let decimals_opt = match &self.lemma_type.specifications {
1315                    TypeSpecification::Scale { decimals, .. } => *decimals,
1316                    _ => None,
1317                };
1318
1319                let number_str = if let Some(decimals) = decimals_opt {
1320                    // Format with fixed decimal places
1321                    let rounded = n.round_dp(decimals as u32);
1322                    let mut s = rounded.to_string();
1323                    if let Some(dot_pos) = s.find('.') {
1324                        let current_decimals = s.len() - dot_pos - 1;
1325                        if current_decimals < decimals as usize {
1326                            s.push_str(&"0".repeat(decimals as usize - current_decimals));
1327                        }
1328                    } else {
1329                        s.push('.');
1330                        s.push_str(&"0".repeat(decimals as usize));
1331                    }
1332                    s
1333                } else {
1334                    // No decimals specified: normalize (remove trailing zeros)
1335                    let normalized = n.normalize();
1336                    if normalized.fract().is_zero() {
1337                        normalized.trunc().to_string()
1338                    } else {
1339                        normalized.to_string()
1340                    }
1341                };
1342
1343                // Append unit if present
1344                if let Some(unit) = unit_opt {
1345                    write!(f, "{} {}", number_str, unit)
1346                } else {
1347                    write!(f, "{}", number_str)
1348                }
1349            }
1350            Value::Duration(value, unit) => write!(f, "{} {}", value, unit),
1351            Value::Ratio(r, unit_opt) => {
1352                // Use tracked unit if present
1353                if let Some(unit) = unit_opt {
1354                    if unit == "percent" {
1355                        // Display as percent: convert ratio (0.50) to percent (50%)
1356                        let percentage_value = *r * rust_decimal::Decimal::from(100);
1357                        let rounded = percentage_value.round_dp(2);
1358                        if rounded.fract().is_zero() {
1359                            write!(f, "{}%", rounded.trunc())
1360                        } else {
1361                            write!(f, "{}%", rounded)
1362                        }
1363                    } else {
1364                        // Display with unit
1365                        let normalized = r.normalize();
1366                        if normalized.fract().is_zero() {
1367                            write!(f, "{} {}", normalized.trunc(), unit)
1368                        } else {
1369                            write!(f, "{} {}", normalized, unit)
1370                        }
1371                    }
1372                } else {
1373                    // Display as regular ratio (no unit)
1374                    let normalized = r.normalize();
1375                    if normalized.fract().is_zero() {
1376                        write!(f, "{}", normalized.trunc())
1377                    } else {
1378                        write!(f, "{}", normalized)
1379                    }
1380                }
1381            }
1382        }
1383    }
1384}
1385
1386impl fmt::Display for ConversionTarget {
1387    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1388        match self {
1389            ConversionTarget::Duration(unit) => write!(f, "{}", unit),
1390            ConversionTarget::Percentage => write!(f, "percent"),
1391        }
1392    }
1393}
1394
1395impl fmt::Display for FactValue {
1396    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1397        match self {
1398            FactValue::Literal(lit) => write!(f, "{}", lit),
1399            FactValue::TypeDeclaration {
1400                base,
1401                overrides,
1402                from,
1403            } => {
1404                let base_str = if let Some(from_doc) = from {
1405                    format!("{} from {}", base, from_doc)
1406                } else {
1407                    base.clone()
1408                };
1409
1410                if let Some(ref overrides_vec) = overrides {
1411                    let override_str = overrides_vec
1412                        .iter()
1413                        .map(|(cmd, args)| {
1414                            let args_str = args.join(" ");
1415                            if args_str.is_empty() {
1416                                cmd.clone()
1417                            } else {
1418                                format!("{} {}", cmd, args_str)
1419                            }
1420                        })
1421                        .collect::<Vec<_>>()
1422                        .join(" -> ");
1423                    write!(f, "[{} -> {}]", base_str, override_str)
1424                } else {
1425                    write!(f, "[{}]", base_str)
1426                }
1427            }
1428            FactValue::DocumentReference(doc_name) => write!(f, "doc {}", doc_name),
1429        }
1430    }
1431}
1432
1433impl fmt::Display for ArithmeticComputation {
1434    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1435        match self {
1436            ArithmeticComputation::Add => write!(f, "+"),
1437            ArithmeticComputation::Subtract => write!(f, "-"),
1438            ArithmeticComputation::Multiply => write!(f, "*"),
1439            ArithmeticComputation::Divide => write!(f, "/"),
1440            ArithmeticComputation::Modulo => write!(f, "%"),
1441            ArithmeticComputation::Power => write!(f, "^"),
1442        }
1443    }
1444}
1445
1446impl fmt::Display for ComparisonComputation {
1447    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1448        match self {
1449            ComparisonComputation::GreaterThan => write!(f, ">"),
1450            ComparisonComputation::LessThan => write!(f, "<"),
1451            ComparisonComputation::GreaterThanOrEqual => write!(f, ">="),
1452            ComparisonComputation::LessThanOrEqual => write!(f, "<="),
1453            ComparisonComputation::Equal => write!(f, "=="),
1454            ComparisonComputation::NotEqual => write!(f, "!="),
1455            ComparisonComputation::Is => write!(f, "is"),
1456            ComparisonComputation::IsNot => write!(f, "is not"),
1457        }
1458    }
1459}
1460
1461impl fmt::Display for MathematicalComputation {
1462    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1463        match self {
1464            MathematicalComputation::Sqrt => write!(f, "sqrt"),
1465            MathematicalComputation::Sin => write!(f, "sin"),
1466            MathematicalComputation::Cos => write!(f, "cos"),
1467            MathematicalComputation::Tan => write!(f, "tan"),
1468            MathematicalComputation::Asin => write!(f, "asin"),
1469            MathematicalComputation::Acos => write!(f, "acos"),
1470            MathematicalComputation::Atan => write!(f, "atan"),
1471            MathematicalComputation::Log => write!(f, "log"),
1472            MathematicalComputation::Exp => write!(f, "exp"),
1473            MathematicalComputation::Abs => write!(f, "abs"),
1474            MathematicalComputation::Floor => write!(f, "floor"),
1475            MathematicalComputation::Ceil => write!(f, "ceil"),
1476            MathematicalComputation::Round => write!(f, "round"),
1477        }
1478    }
1479}
1480
1481impl fmt::Display for TimeValue {
1482    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1483        write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)
1484    }
1485}
1486
1487impl fmt::Display for TimezoneValue {
1488    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1489        if self.offset_hours == 0 && self.offset_minutes == 0 {
1490            write!(f, "Z")
1491        } else {
1492            let sign = if self.offset_hours >= 0 { "+" } else { "-" };
1493            let hours = self.offset_hours.abs();
1494            write!(f, "{}{:02}:{:02}", sign, hours, self.offset_minutes)
1495        }
1496    }
1497}
1498
1499impl fmt::Display for DateTimeValue {
1500    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1501        write!(
1502            f,
1503            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
1504            self.year, self.month, self.day, self.hour, self.minute, self.second
1505        )?;
1506        if let Some(tz) = &self.timezone {
1507            write!(f, "{}", tz)?;
1508        }
1509        Ok(())
1510    }
1511}
1512
1513impl fmt::Display for RuleReference {
1514    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1515        if self.segments.is_empty() {
1516            write!(f, "{}?", self.rule)
1517        } else {
1518            write!(f, "{}.{}?", self.segments.join("."), self.rule)
1519        }
1520    }
1521}
1522
1523impl fmt::Display for FactPath {
1524    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1525        for segment in &self.segments {
1526            write!(f, "{}.", segment.fact)?;
1527        }
1528        write!(f, "{}", self.fact)
1529    }
1530}
1531
1532impl fmt::Display for RulePath {
1533    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1534        for segment in &self.segments {
1535            write!(f, "{}.", segment.fact)?;
1536        }
1537        write!(f, "{}?", self.rule)
1538    }
1539}
1540
1541/// A unit for Number and Ratio types
1542#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
1543pub struct Unit {
1544    pub name: String,
1545    pub value: Decimal,
1546}
1547
1548/// Type specifications that define the foundational types available in Lemma,
1549/// including their default values and constraints.
1550#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
1551pub enum TypeSpecification {
1552    Boolean {
1553        help: Option<String>,
1554        default: Option<bool>,
1555    },
1556    Scale {
1557        minimum: Option<Decimal>,
1558        maximum: Option<Decimal>,
1559        decimals: Option<u8>,
1560        precision: Option<Decimal>,
1561        units: Vec<Unit>,
1562        help: Option<String>,
1563        default: Option<(Decimal, String)>,
1564    },
1565    Number {
1566        minimum: Option<Decimal>,
1567        maximum: Option<Decimal>,
1568        decimals: Option<u8>,
1569        precision: Option<Decimal>,
1570        help: Option<String>,
1571        default: Option<Decimal>,
1572    },
1573    Ratio {
1574        minimum: Option<Decimal>,
1575        maximum: Option<Decimal>,
1576        units: Vec<Unit>,
1577        help: Option<String>,
1578        default: Option<Decimal>,
1579    },
1580    Text {
1581        minimum: Option<usize>,
1582        maximum: Option<usize>,
1583        length: Option<usize>,
1584        options: Vec<String>,
1585        help: Option<String>,
1586        default: Option<String>,
1587    },
1588    Date {
1589        minimum: Option<DateTimeValue>,
1590        maximum: Option<DateTimeValue>,
1591        help: Option<String>,
1592        default: Option<DateTimeValue>,
1593    },
1594    Time {
1595        minimum: Option<TimeValue>,
1596        maximum: Option<TimeValue>,
1597        help: Option<String>,
1598        default: Option<TimeValue>,
1599    },
1600    Duration {
1601        help: Option<String>,
1602        default: Option<(Decimal, DurationUnit)>,
1603    },
1604    Veto {
1605        message: Option<String>,
1606    },
1607}
1608
1609impl TypeSpecification {
1610    /// Create a Boolean type with no defaults
1611    pub fn boolean() -> Self {
1612        TypeSpecification::Boolean {
1613            help: None,
1614            default: None,
1615        }
1616    }
1617
1618    /// Create a Scale type with default specifications (can have units)
1619    pub fn scale() -> Self {
1620        TypeSpecification::Scale {
1621            minimum: None,
1622            maximum: None,
1623            decimals: None,
1624            precision: None,
1625            units: vec![],
1626            help: None,
1627            default: None,
1628        }
1629    }
1630
1631    /// Create a Number type with default specifications (dimensionless, no units)
1632    pub fn number() -> Self {
1633        TypeSpecification::Number {
1634            minimum: None,
1635            maximum: None,
1636            decimals: None,
1637            precision: None,
1638            help: None,
1639            default: None,
1640        }
1641    }
1642
1643    /// Create a Ratio type with default units
1644    /// Default units: percent = 100, permille = 1000
1645    pub fn ratio() -> Self {
1646        TypeSpecification::Ratio {
1647            minimum: None,
1648            maximum: None,
1649            units: vec![
1650                Unit {
1651                    name: "percent".to_string(),
1652                    value: Decimal::from(100),
1653                },
1654                Unit {
1655                    name: "permille".to_string(),
1656                    value: Decimal::from(1000),
1657                },
1658            ],
1659            help: None,
1660            default: None,
1661        }
1662    }
1663
1664    /// Create a Text type with default specifications
1665    pub fn text() -> Self {
1666        TypeSpecification::Text {
1667            minimum: None,
1668            maximum: None,
1669            length: None,
1670            options: vec![],
1671            help: None,
1672            default: None,
1673        }
1674    }
1675
1676    /// Create a Date type with default specifications
1677    pub fn date() -> Self {
1678        TypeSpecification::Date {
1679            minimum: None,
1680            maximum: None,
1681            help: None,
1682            default: None,
1683        }
1684    }
1685
1686    /// Create a Time type with default specifications
1687    pub fn time() -> Self {
1688        TypeSpecification::Time {
1689            minimum: None,
1690            maximum: None,
1691            help: None,
1692            default: None,
1693        }
1694    }
1695
1696    /// Create a Duration type with default specifications
1697    pub fn duration() -> Self {
1698        TypeSpecification::Duration {
1699            help: None,
1700            default: None,
1701        }
1702    }
1703
1704    /// Create a Veto type (internal use only - not user-declarable)
1705    pub fn veto() -> Self {
1706        TypeSpecification::Veto { message: None }
1707    }
1708
1709    /// Apply a single override command to this specification
1710    pub fn apply_override(mut self, command: &str, args: &[String]) -> Result<Self, String> {
1711        match &mut self {
1712            TypeSpecification::Boolean { help, default } => match command {
1713                "help" => *help = args.first().cloned(),
1714                "default" => {
1715                    let d = args
1716                        .first()
1717                        .ok_or_else(|| "default requires an argument".to_string())?
1718                        .parse::<BooleanValue>()
1719                        .map_err(|_| format!("invalid default value: {:?}", args.first()))?;
1720                    *default = Some(d.into());
1721                }
1722                _ => {
1723                    return Err(format!(
1724                        "Invalid command '{}' for boolean type. Valid commands: help, default",
1725                        command
1726                    ));
1727                }
1728            },
1729            TypeSpecification::Scale {
1730                decimals,
1731                minimum,
1732                maximum,
1733                precision,
1734                units,
1735                help,
1736                default,
1737            } => match command {
1738                "decimals" => {
1739                    let d = args
1740                        .first()
1741                        .ok_or_else(|| "decimals requires an argument".to_string())?
1742                        .parse::<u8>()
1743                        .map_err(|_| format!("invalid decimals value: {:?}", args.first()))?;
1744                    *decimals = Some(d);
1745                }
1746                "unit" if args.len() >= 2 => {
1747                    let unit_name = args[0].clone();
1748                    // Check if unit name already exists in the current type
1749                    if units.iter().any(|u| u.name == unit_name) {
1750                        return Err(format!(
1751                            "Duplicate unit name '{}' in type definition. Unit names must be unique within a type.",
1752                            unit_name
1753                        ));
1754                    }
1755                    let value = args[1]
1756                        .parse::<Decimal>()
1757                        .map_err(|_| format!("invalid unit value: {}", args[1]))?;
1758                    units.push(Unit {
1759                        name: unit_name,
1760                        value,
1761                    });
1762                }
1763                "minimum" => {
1764                    let m = args
1765                        .first()
1766                        .ok_or_else(|| "minimum requires an argument".to_string())?
1767                        .parse::<Decimal>()
1768                        .map_err(|_| format!("invalid minimum value: {:?}", args.first()))?;
1769                    *minimum = Some(m);
1770                }
1771                "maximum" => {
1772                    let m = args
1773                        .first()
1774                        .ok_or_else(|| "maximum requires an argument".to_string())?
1775                        .parse::<Decimal>()
1776                        .map_err(|_| format!("invalid maximum value: {:?}", args.first()))?;
1777                    *maximum = Some(m);
1778                }
1779                "precision" => {
1780                    let p = args
1781                        .first()
1782                        .ok_or_else(|| "precision requires an argument".to_string())?
1783                        .parse::<Decimal>()
1784                        .map_err(|_| format!("invalid precision value: {:?}", args.first()))?;
1785                    *precision = Some(p);
1786                }
1787                "help" => *help = args.first().cloned(),
1788                "default" => {
1789                    if args.len() < 2 {
1790                        return Err(
1791                            "default requires a value and unit (e.g., 'default 1 kilogram')"
1792                                .to_string(),
1793                        );
1794                    }
1795                    let value = args[0]
1796                        .parse::<Decimal>()
1797                        .map_err(|_| format!("invalid default value: {:?}", args[0]))?;
1798                    let unit_name = args[1].clone();
1799                    // Validate that the unit exists
1800                    if !units.iter().any(|u| u.name == unit_name) {
1801                        return Err(format!(
1802                            "Invalid unit '{}' for default. Valid units: {}",
1803                            unit_name,
1804                            units
1805                                .iter()
1806                                .map(|u| u.name.clone())
1807                                .collect::<Vec<_>>()
1808                                .join(", ")
1809                        ));
1810                    }
1811                    *default = Some((value, unit_name));
1812                }
1813                _ => {
1814                    return Err(format!(
1815                        "Invalid command '{}' for scale type. Valid commands: unit, minimum, maximum, decimals, precision, help, default",
1816                        command
1817                    ));
1818                }
1819            },
1820            TypeSpecification::Number {
1821                decimals,
1822                minimum,
1823                maximum,
1824                precision,
1825                help,
1826                default,
1827            } => match command {
1828                "decimals" => {
1829                    let d = args
1830                        .first()
1831                        .ok_or_else(|| "decimals requires an argument".to_string())?
1832                        .parse::<u8>()
1833                        .map_err(|_| format!("invalid decimals value: {:?}", args.first()))?;
1834                    *decimals = Some(d);
1835                }
1836                "unit" => {
1837                    return Err(
1838                        "Invalid command 'unit' for number type. Number types are dimensionless and cannot have units. Use 'scale' type instead.".to_string()
1839                    );
1840                }
1841                "minimum" => {
1842                    let m = args
1843                        .first()
1844                        .ok_or_else(|| "minimum requires an argument".to_string())?
1845                        .parse::<Decimal>()
1846                        .map_err(|_| format!("invalid minimum value: {:?}", args.first()))?;
1847                    *minimum = Some(m);
1848                }
1849                "maximum" => {
1850                    let m = args
1851                        .first()
1852                        .ok_or_else(|| "maximum requires an argument".to_string())?
1853                        .parse::<Decimal>()
1854                        .map_err(|_| format!("invalid maximum value: {:?}", args.first()))?;
1855                    *maximum = Some(m);
1856                }
1857                "precision" => {
1858                    let p = args
1859                        .first()
1860                        .ok_or_else(|| "precision requires an argument".to_string())?
1861                        .parse::<Decimal>()
1862                        .map_err(|_| format!("invalid precision value: {:?}", args.first()))?;
1863                    *precision = Some(p);
1864                }
1865                "help" => *help = args.first().cloned(),
1866                "default" => {
1867                    let d = args
1868                        .first()
1869                        .ok_or_else(|| "default requires an argument".to_string())?
1870                        .parse::<Decimal>()
1871                        .map_err(|_| format!("invalid default value: {:?}", args.first()))?;
1872                    *default = Some(d);
1873                }
1874                _ => {
1875                    return Err(format!(
1876                        "Invalid command '{}' for number type. Valid commands: minimum, maximum, decimals, precision, help, default",
1877                        command
1878                    ));
1879                }
1880            },
1881            TypeSpecification::Ratio {
1882                minimum,
1883                maximum,
1884                units,
1885                help,
1886                default,
1887            } => match command {
1888                "unit" if args.len() >= 2 => {
1889                    let value = args[1]
1890                        .parse::<Decimal>()
1891                        .map_err(|_| format!("invalid unit value: {}", args[1]))?;
1892                    units.push(Unit {
1893                        name: args[0].clone(),
1894                        value,
1895                    });
1896                }
1897                "minimum" => {
1898                    let m = args
1899                        .first()
1900                        .ok_or_else(|| "minimum requires an argument".to_string())?
1901                        .parse::<Decimal>()
1902                        .map_err(|_| format!("invalid minimum value: {:?}", args.first()))?;
1903                    *minimum = Some(m);
1904                }
1905                "maximum" => {
1906                    let m = args
1907                        .first()
1908                        .ok_or_else(|| "maximum requires an argument".to_string())?
1909                        .parse::<Decimal>()
1910                        .map_err(|_| format!("invalid maximum value: {:?}", args.first()))?;
1911                    *maximum = Some(m);
1912                }
1913                "help" => *help = args.first().cloned(),
1914                "default" => {
1915                    let d = args
1916                        .first()
1917                        .ok_or_else(|| "default requires an argument".to_string())?
1918                        .parse::<Decimal>()
1919                        .map_err(|_| format!("invalid default value: {:?}", args.first()))?;
1920                    *default = Some(d);
1921                }
1922                _ => {
1923                    return Err(format!(
1924                        "Invalid command '{}' for ratio type. Valid commands: unit, minimum, maximum, help, default",
1925                        command
1926                    ));
1927                }
1928            },
1929            TypeSpecification::Text {
1930                minimum,
1931                maximum,
1932                length,
1933                options,
1934                help,
1935                default,
1936            } => match command {
1937                "option" if args.len() == 1 => {
1938                    options.push(strip_surrounding_quotes(&args[0]));
1939                }
1940                "options" => {
1941                    *options = args.iter().map(|s| strip_surrounding_quotes(s)).collect();
1942                }
1943                "minimum" => {
1944                    let m = args
1945                        .first()
1946                        .ok_or_else(|| "minimum requires an argument".to_string())?
1947                        .parse::<usize>()
1948                        .map_err(|_| format!("invalid minimum value: {:?}", args.first()))?;
1949                    *minimum = Some(m);
1950                }
1951                "maximum" => {
1952                    let m = args
1953                        .first()
1954                        .ok_or_else(|| "maximum requires an argument".to_string())?
1955                        .parse::<usize>()
1956                        .map_err(|_| format!("invalid maximum value: {:?}", args.first()))?;
1957                    *maximum = Some(m);
1958                }
1959                "length" => {
1960                    let l = args
1961                        .first()
1962                        .ok_or_else(|| "length requires an argument".to_string())?
1963                        .parse::<usize>()
1964                        .map_err(|_| format!("invalid length value: {:?}", args.first()))?;
1965                    *length = Some(l);
1966                }
1967                "help" => *help = args.first().cloned(),
1968                "default" => {
1969                    let arg = args
1970                        .first()
1971                        .ok_or_else(|| "default requires an argument".to_string())?;
1972                    *default = Some(strip_surrounding_quotes(arg));
1973                }
1974                _ => {
1975                    return Err(format!(
1976                        "Invalid command '{}' for text type. Valid commands: options, minimum, maximum, length, help, default",
1977                        command
1978                    ));
1979                }
1980            },
1981            TypeSpecification::Date {
1982                minimum,
1983                maximum,
1984                help,
1985                default,
1986            } => match command {
1987                "minimum" => {
1988                    let arg = args
1989                        .first()
1990                        .ok_or_else(|| "default requires an argument".to_string())?;
1991                    let Value::Date(date) = &standard_date()
1992                        .parse_value(arg)
1993                        .map_err(|_| format!("invalid default date value: {}", arg))?
1994                        .value
1995                    else {
1996                        return Err(format!("invalid default date value: {}", arg));
1997                    };
1998                    *minimum = Some(date.clone());
1999                }
2000                "maximum" => {
2001                    let arg = args
2002                        .first()
2003                        .ok_or_else(|| "default requires an argument".to_string())?;
2004                    let Value::Date(date) = standard_date()
2005                        .parse_value(arg)
2006                        .map_err(|_| format!("invalid default date value: {}", arg))?
2007                        .value
2008                    else {
2009                        return Err(format!("invalid default date value: {}", arg));
2010                    };
2011                    *maximum = Some(date);
2012                }
2013                "help" => *help = args.first().cloned(),
2014                "default" => {
2015                    let arg = args
2016                        .first()
2017                        .ok_or_else(|| "default requires an argument".to_string())?;
2018                    let Value::Date(date) = standard_date()
2019                        .parse_value(arg)
2020                        .map_err(|_| format!("invalid default date value: {}", arg))?
2021                        .value
2022                    else {
2023                        return Err(format!("invalid default date value: {}", arg));
2024                    };
2025                    *default = Some(date);
2026                }
2027                _ => {
2028                    return Err(format!(
2029                        "Invalid command '{}' for date type. Valid commands: minimum, maximum, help, default",
2030                        command
2031                    ));
2032                }
2033            },
2034            TypeSpecification::Time {
2035                minimum,
2036                maximum,
2037                help,
2038                default,
2039            } => match command {
2040                "minimum" => {
2041                    let arg = args
2042                        .first()
2043                        .ok_or_else(|| "minimum requires an argument".to_string())?;
2044                    let Value::Time(time) = &standard_time()
2045                        .parse_value(arg)
2046                        .map_err(|_| format!("invalid minimum time value: {}", arg))?
2047                        .value
2048                    else {
2049                        return Err(format!("invalid minimum time value: {}", arg));
2050                    };
2051                    *minimum = Some(time.clone());
2052                }
2053                "maximum" => {
2054                    let arg = args
2055                        .first()
2056                        .ok_or_else(|| "maximum requires an argument".to_string())?;
2057                    let Value::Time(time) = &standard_time()
2058                        .parse_value(arg)
2059                        .map_err(|_| format!("invalid maximum time value: {}", arg))?
2060                        .value
2061                    else {
2062                        return Err(format!("invalid maximum time value: {}", arg));
2063                    };
2064                    *maximum = Some(time.clone());
2065                }
2066                "help" => *help = args.first().cloned(),
2067                "default" => {
2068                    let arg = args
2069                        .first()
2070                        .ok_or_else(|| "default requires an argument".to_string())?;
2071                    let Value::Time(time) = &standard_time()
2072                        .parse_value(arg)
2073                        .map_err(|_| format!("invalid default time value: {}", arg))?
2074                        .value
2075                    else {
2076                        return Err(format!("invalid default time value: {}", arg));
2077                    };
2078                    *default = Some(time.clone());
2079                }
2080                _ => {
2081                    return Err(format!(
2082                        "Invalid command '{}' for time type. Valid commands: minimum, maximum, help, default",
2083                        command
2084                    ));
2085                }
2086            },
2087            TypeSpecification::Duration { help, default } => match command {
2088                "help" => *help = args.first().cloned(),
2089                "default" if args.len() >= 2 => {
2090                    let value = args[0]
2091                        .parse::<Decimal>()
2092                        .map_err(|_| format!("invalid duration value: {}", args[0]))?;
2093                    let unit = args[1]
2094                        .parse::<DurationUnit>()
2095                        .map_err(|_| format!("invalid duration unit: {}", args[1]))?;
2096                    *default = Some((value, unit));
2097                }
2098                _ => {
2099                    return Err(format!(
2100                        "Invalid command '{}' for duration type. Valid commands: help, default",
2101                        command
2102                    ));
2103                }
2104            },
2105            TypeSpecification::Veto { .. } => {
2106                return Err(format!(
2107                    "Invalid command '{}' for veto type. Veto is not a user-declarable type and cannot have overrides",
2108                    command
2109                ));
2110            }
2111        }
2112        Ok(self)
2113    }
2114}
2115
2116/// User-defined type as it appears in the source (AST)
2117///
2118/// Supports these variants:
2119/// - Basic type: `type money = number`
2120/// - Basic type extension: `type money = number -> decimals 2 -> unit EUR 1.00 -> unit USD 1.18`
2121/// - From another custom type: `type currency from lemma`
2122/// - Shorthand with overrides: `type currency from lemma -> maximum 1000`
2123/// - Inline type definitions: `fact age = [number -> minimum 0 -> maximum 120]`
2124#[derive(Clone, Debug, PartialEq)]
2125pub enum TypeDef {
2126    /// Regular named type definition
2127    /// Example: `type money = number -> decimals 2`
2128    Regular {
2129        name: String,
2130        parent: String,
2131        overrides: Option<Vec<(String, Vec<String>)>>,
2132    },
2133    /// Imported type from another document
2134    /// Example: `type currency from lemma` or `type currency from lemma -> maximum 1000`
2135    Import {
2136        name: String,
2137        source_type: String,
2138        from: String,
2139        overrides: Option<Vec<(String, Vec<String>)>>,
2140    },
2141    /// Inline type definition
2142    /// Example: `fact age = [number -> minimum 0 -> maximum 120]`
2143    /// Example: `fact age = [age from lemma]`
2144    /// Example: `fact age = [age from lemma -> minimum 18]`
2145    Inline {
2146        parent: String,
2147        overrides: Option<Vec<(String, Vec<String>)>>,
2148        fact_ref: FactReference,
2149        from: Option<String>,
2150    },
2151}
2152
2153/// A fully resolved type used during evaluation
2154///
2155/// This combines the type specifications with all overrides already applied.
2156#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
2157pub struct LemmaType {
2158    pub name: Option<String>,
2159    pub specifications: TypeSpecification,
2160}
2161
2162impl LemmaType {
2163    /// Create a new LemmaType with the given name and specifications
2164    pub fn new(name: String, specifications: TypeSpecification) -> Self {
2165        Self {
2166            name: Some(name),
2167            specifications,
2168        }
2169    }
2170
2171    /// Create a new LemmaType without a name (for standard types and inline fact definitions)
2172    pub fn without_name(specifications: TypeSpecification) -> Self {
2173        Self {
2174            name: None,
2175            specifications,
2176        }
2177    }
2178
2179    /// Get the name of this type, deriving from specifications if name is None
2180    pub fn name(&self) -> &str {
2181        match &self.name {
2182            Some(n) => n.as_str(),
2183            None => match &self.specifications {
2184                TypeSpecification::Boolean { .. } => "boolean",
2185                TypeSpecification::Scale { .. } => "scale",
2186                TypeSpecification::Number { .. } => "number",
2187                TypeSpecification::Text { .. } => "text",
2188                TypeSpecification::Date { .. } => "date",
2189                TypeSpecification::Time { .. } => "time",
2190                TypeSpecification::Duration { .. } => "duration",
2191                TypeSpecification::Ratio { .. } => "ratio",
2192                TypeSpecification::Veto { .. } => "veto",
2193            },
2194        }
2195    }
2196
2197    /// Check if this type is boolean
2198    pub fn is_boolean(&self) -> bool {
2199        matches!(&self.specifications, TypeSpecification::Boolean { .. })
2200    }
2201
2202    /// Check if this type is scale (has units)
2203    pub fn is_scale(&self) -> bool {
2204        matches!(&self.specifications, TypeSpecification::Scale { .. })
2205    }
2206
2207    /// Check if this type is number (dimensionless)
2208    pub fn is_number(&self) -> bool {
2209        matches!(&self.specifications, TypeSpecification::Number { .. })
2210    }
2211
2212    /// Check if this type is numeric (either scale or number)
2213    pub fn is_numeric(&self) -> bool {
2214        matches!(
2215            &self.specifications,
2216            TypeSpecification::Scale { .. } | TypeSpecification::Number { .. }
2217        )
2218    }
2219
2220    /// Check if this type is text
2221    pub fn is_text(&self) -> bool {
2222        matches!(&self.specifications, TypeSpecification::Text { .. })
2223    }
2224
2225    /// Check if this type is date
2226    pub fn is_date(&self) -> bool {
2227        matches!(&self.specifications, TypeSpecification::Date { .. })
2228    }
2229
2230    /// Check if this type is time
2231    pub fn is_time(&self) -> bool {
2232        matches!(&self.specifications, TypeSpecification::Time { .. })
2233    }
2234
2235    /// Check if this type is duration
2236    pub fn is_duration(&self) -> bool {
2237        matches!(&self.specifications, TypeSpecification::Duration { .. })
2238    }
2239
2240    /// Check if this type is ratio
2241    pub fn is_ratio(&self) -> bool {
2242        matches!(&self.specifications, TypeSpecification::Ratio { .. })
2243    }
2244
2245    /// Check if this type is veto
2246    pub fn is_veto(&self) -> bool {
2247        matches!(&self.specifications, TypeSpecification::Veto { .. })
2248    }
2249
2250    /// Check if two types have the same standard type specification (ignoring constraints/overrides)
2251    /// This compares only the TypeSpecification variant (Boolean, Number, Scale, Text, etc.)
2252    /// and ignores all constraints like minimum, maximum, decimals, units, options, etc.
2253    pub fn has_same_base_type(&self, other: &LemmaType) -> bool {
2254        use TypeSpecification::*;
2255        matches!(
2256            (&self.specifications, &other.specifications),
2257            (Boolean { .. }, Boolean { .. })
2258                | (Number { .. }, Number { .. })
2259                | (Scale { .. }, Scale { .. })
2260                | (Text { .. }, Text { .. })
2261                | (Date { .. }, Date { .. })
2262                | (Time { .. }, Time { .. })
2263                | (Duration { .. }, Duration { .. })
2264                | (Ratio { .. }, Ratio { .. })
2265                | (Veto { .. }, Veto { .. })
2266        )
2267    }
2268
2269    /// Create a Veto LemmaType (internal use only - not user-declarable)
2270    pub fn veto_type() -> Self {
2271        Self::without_name(TypeSpecification::veto())
2272    }
2273
2274    /// Create a LiteralValue from this type's default value, if one exists
2275    pub fn create_default_value(&self) -> Option<LiteralValue> {
2276        use TypeSpecification::*;
2277        match &self.specifications {
2278            Boolean { default, .. } => default
2279                .map(|b| LiteralValue::boolean_with_type(BooleanValue::from(b), self.clone())),
2280            Text { default, .. } => default
2281                .as_ref()
2282                .map(|s| LiteralValue::text_with_type(s.clone(), self.clone())),
2283            Number { default, .. } => {
2284                default.map(|d| LiteralValue::number_with_type(d, self.clone()))
2285            }
2286            Scale { default, .. } => default.as_ref().map(|(value, unit_name)| {
2287                LiteralValue::scale_with_type(*value, Some(unit_name.clone()), self.clone())
2288            }),
2289            Ratio { default, .. } => {
2290                default.map(|d| LiteralValue::ratio_with_type(d, None, self.clone()))
2291            }
2292            Date { default, .. } => default
2293                .as_ref()
2294                .map(|d| LiteralValue::date_with_type(d.clone(), self.clone())),
2295            Time { default, .. } => default
2296                .as_ref()
2297                .map(|t| LiteralValue::time_with_type(t.clone(), self.clone())),
2298            Duration { default, .. } => default
2299                .as_ref()
2300                .map(|(v, u)| LiteralValue::duration_with_type(*v, u.clone(), self.clone())),
2301            Veto { .. } => None,
2302        }
2303    }
2304
2305    /// Parse a raw string value into a LiteralValue according to this type
2306    pub fn parse_value(&self, raw: &str) -> Result<LiteralValue, LemmaError> {
2307        let value = match &self.specifications {
2308            TypeSpecification::Boolean { .. } => Self::parse_boolean_value(raw)?,
2309            TypeSpecification::Scale { .. } => Self::parse_scale_value(raw, self)?,
2310            TypeSpecification::Number { .. } => Self::parse_number_value(raw)?,
2311            TypeSpecification::Text { .. } => Self::parse_text_value(raw)?,
2312            TypeSpecification::Date { .. } => Self::parse_date_value(raw)?,
2313            TypeSpecification::Time { .. } => Self::parse_time_value(raw)?,
2314            TypeSpecification::Duration { .. } => Self::parse_duration_value(raw)?,
2315            TypeSpecification::Ratio { .. } => Self::parse_ratio_value(raw)?,
2316            TypeSpecification::Veto { .. } => {
2317                return Err(LemmaError::engine(
2318                    "Cannot parse value for veto type - veto is not a user-declarable type",
2319                    Span {
2320                        start: 0,
2321                        end: 0,
2322                        line: 1,
2323                        col: 0,
2324                    },
2325                    "<unknown>",
2326                    Arc::from(""),
2327                    "<unknown>",
2328                    1,
2329                    None::<String>,
2330                ));
2331            }
2332        };
2333        // Create LiteralValue with the appropriate helper method based on value type
2334        Ok(match &value {
2335            Value::Number(n) => LiteralValue::number_with_type(*n, self.clone()),
2336            Value::Scale(n, u) => LiteralValue::scale_with_type(*n, u.clone(), self.clone()),
2337            Value::Text(s) => LiteralValue::text_with_type(s.clone(), self.clone()),
2338            Value::Boolean(b) => LiteralValue::boolean_with_type(b.clone(), self.clone()),
2339            Value::Date(d) => LiteralValue::date_with_type(d.clone(), self.clone()),
2340            Value::Time(t) => LiteralValue::time_with_type(t.clone(), self.clone()),
2341            Value::Duration(v, u) => LiteralValue::duration_with_type(*v, u.clone(), self.clone()),
2342            Value::Ratio(r, u) => LiteralValue::ratio_with_type(*r, u.clone(), self.clone()),
2343        })
2344    }
2345
2346    fn parse_text_value(raw: &str) -> Result<Value, LemmaError> {
2347        Ok(Value::Text(raw.to_string()))
2348    }
2349
2350    fn parse_scale_value(raw: &str, lemma_type: &LemmaType) -> Result<Value, LemmaError> {
2351        let trimmed = raw.trim();
2352
2353        // Parse number and optional unit from string
2354        // Handles: "50", "50 celsius", "50celsius", "1,234.56 celsius", etc.
2355
2356        // First, try to find where the number part ends
2357        // Numbers can contain: digits, decimal point, sign, underscore, comma
2358        let mut number_end = 0;
2359        let chars: Vec<char> = trimmed.chars().collect();
2360        let mut has_decimal = false;
2361
2362        // Skip leading sign
2363        let start = if chars.first().is_some_and(|c| *c == '+' || *c == '-') {
2364            1
2365        } else {
2366            0
2367        };
2368
2369        for (i, &ch) in chars.iter().enumerate().skip(start) {
2370            match ch {
2371                '0'..='9' => number_end = i + 1,
2372                '.' if !has_decimal => {
2373                    has_decimal = true;
2374                    number_end = i + 1;
2375                }
2376                '_' | ',' => {
2377                    // Thousand separators - continue scanning
2378                    number_end = i + 1;
2379                }
2380                _ => {
2381                    // Non-numeric character - number ends here
2382                    break;
2383                }
2384            }
2385        }
2386
2387        // Extract number and unit parts
2388        let number_part = trimmed[..number_end].trim();
2389        let unit_part = trimmed[number_end..].trim();
2390
2391        // Clean number part (remove separators for parsing)
2392        let clean_number = number_part.replace(['_', ','], "");
2393        let decimal = Decimal::from_str(&clean_number).map_err(|_| {
2394            LemmaError::engine(
2395                format!("Invalid scale string: '{}' is not a valid number", raw),
2396                Span {
2397                    start: 0,
2398                    end: 0,
2399                    line: 1,
2400                    col: 0,
2401                },
2402                "<unknown>",
2403                Arc::from(raw),
2404                "<unknown>",
2405                1,
2406                None::<String>,
2407            )
2408        })?;
2409
2410        // Validate unit against type definition
2411        let allowed_units = match &lemma_type.specifications {
2412            TypeSpecification::Scale { units, .. } => units,
2413            _ => {
2414                return Err(LemmaError::engine(
2415                    format!(
2416                        "Internal error: expected a scale type but got {}",
2417                        lemma_type.name()
2418                    ),
2419                    Span {
2420                        start: 0,
2421                        end: 0,
2422                        line: 1,
2423                        col: 0,
2424                    },
2425                    "<unknown>",
2426                    Arc::from(raw),
2427                    "<unknown>",
2428                    1,
2429                    None::<String>,
2430                ));
2431            }
2432        };
2433
2434        let unit = if unit_part.is_empty() {
2435            None
2436        } else {
2437            // Validate that the unit exists in the type definition
2438            let unit_matched = allowed_units
2439                .iter()
2440                .find(|u| u.name.eq_ignore_ascii_case(unit_part));
2441
2442            if let Some(unit_def) = unit_matched {
2443                Some(unit_def.name.clone())
2444            } else {
2445                let valid: Vec<String> = allowed_units.iter().map(|u| u.name.clone()).collect();
2446                return Err(LemmaError::engine(
2447                    format!(
2448                        "Invalid unit '{}' for scale type. Valid units: {}",
2449                        unit_part,
2450                        valid.join(", ")
2451                    ),
2452                    Span {
2453                        start: 0,
2454                        end: 0,
2455                        line: 1,
2456                        col: 0,
2457                    },
2458                    "<unknown>",
2459                    Arc::from(raw),
2460                    "<unknown>",
2461                    1,
2462                    None::<String>,
2463                ));
2464            }
2465        };
2466
2467        Ok(Value::Scale(decimal, unit))
2468    }
2469
2470    fn parse_number_value(raw: &str) -> Result<Value, LemmaError> {
2471        let clean_number = raw.replace(['_', ','], "");
2472        let decimal = Decimal::from_str(&clean_number).map_err(|_| {
2473            LemmaError::engine(
2474                format!("Invalid number: '{}'. Expected a valid decimal number (e.g., 42, 3.14, 1_000_000)", raw),
2475                Span { start: 0, end: 0, line: 1, col: 0 },
2476                "<unknown>",
2477                Arc::from(raw),
2478                "<unknown>",
2479                1,
2480                None::<String>,
2481            )
2482        })?;
2483        Ok(Value::Number(decimal))
2484    }
2485
2486    fn parse_boolean_value(raw: &str) -> Result<Value, LemmaError> {
2487        let boolean_value: BooleanValue = raw.parse().map_err(|_| {
2488            LemmaError::engine(
2489                format!(
2490                    "Invalid boolean: '{}'. Expected one of: true, false, yes, no, accept, reject",
2491                    raw
2492                ),
2493                Span {
2494                    start: 0,
2495                    end: 0,
2496                    line: 1,
2497                    col: 0,
2498                },
2499                "<unknown>",
2500                Arc::from(raw),
2501                "<unknown>",
2502                1,
2503                None::<String>,
2504            )
2505        })?;
2506        Ok(Value::Boolean(boolean_value))
2507    }
2508
2509    fn parse_date_value(raw: &str) -> Result<Value, LemmaError> {
2510        let datetime_str = raw.trim();
2511
2512        if let Ok(dt) = datetime_str.parse::<chrono::DateTime<chrono::FixedOffset>>() {
2513            let offset = dt.offset().local_minus_utc();
2514            return Ok(Value::Date(DateTimeValue {
2515                year: dt.year(),
2516                month: dt.month(),
2517                day: dt.day(),
2518                hour: dt.hour(),
2519                minute: dt.minute(),
2520                second: dt.second(),
2521                timezone: Some(TimezoneValue {
2522                    offset_hours: (offset / 3600) as i8,
2523                    offset_minutes: ((offset % 3600) / 60) as u8,
2524                }),
2525            }));
2526        }
2527
2528        if let Ok(dt) = datetime_str.parse::<chrono::NaiveDateTime>() {
2529            return Ok(Value::Date(DateTimeValue {
2530                year: dt.year(),
2531                month: dt.month(),
2532                day: dt.day(),
2533                hour: dt.hour(),
2534                minute: dt.minute(),
2535                second: dt.second(),
2536                timezone: None,
2537            }));
2538        }
2539
2540        if let Ok(d) = datetime_str.parse::<chrono::NaiveDate>() {
2541            return Ok(Value::Date(DateTimeValue {
2542                year: d.year(),
2543                month: d.month(),
2544                day: d.day(),
2545                hour: 0,
2546                minute: 0,
2547                second: 0,
2548                timezone: None,
2549            }));
2550        }
2551
2552        Err(LemmaError::engine(
2553            format!("Invalid date/time format: '{}'. Expected one of: YYYY-MM-DD, YYYY-MM-DDTHH:MM:SS, or YYYY-MM-DDTHH:MM:SSZ", raw),
2554            Span { start: 0, end: 0, line: 1, col: 0 },
2555            "<unknown>",
2556            Arc::from(raw),
2557            "<unknown>",
2558            1,
2559            None::<String>,
2560        ))
2561    }
2562
2563    fn parse_time_value(raw: &str) -> Result<Value, LemmaError> {
2564        let time_str = raw.trim();
2565
2566        // Try parsing with timezone (HH:MM:SSZ or HH:MM:SS+HH:MM)
2567        if let Ok(dt) = time_str.parse::<chrono::DateTime<chrono::FixedOffset>>() {
2568            let offset = dt.offset().local_minus_utc();
2569            return Ok(Value::Time(TimeValue {
2570                hour: dt.hour() as u8,
2571                minute: dt.minute() as u8,
2572                second: dt.second() as u8,
2573                timezone: Some(TimezoneValue {
2574                    offset_hours: (offset / 3600) as i8,
2575                    offset_minutes: ((offset % 3600) / 60) as u8,
2576                }),
2577            }));
2578        }
2579
2580        // Try parsing as NaiveTime (HH:MM:SS or HH:MM)
2581        if let Ok(nt) = time_str.parse::<chrono::NaiveTime>() {
2582            return Ok(Value::Time(TimeValue {
2583                hour: nt.hour() as u8,
2584                minute: nt.minute() as u8,
2585                second: nt.second() as u8,
2586                timezone: None,
2587            }));
2588        }
2589
2590        // Try parsing manually for formats like "14:30" or "14:30:00"
2591        let parts: Vec<&str> = time_str.split(':').collect();
2592        if parts.len() == 2 || parts.len() == 3 {
2593            if let (Ok(hour_u32), Ok(minute_u32)) =
2594                (parts[0].parse::<u32>(), parts[1].parse::<u32>())
2595            {
2596                if hour_u32 < 24 && minute_u32 < 60 {
2597                    let second_u32 = if parts.len() == 3 {
2598                        parts[2].parse::<u32>().unwrap_or(0)
2599                    } else {
2600                        0
2601                    };
2602                    if second_u32 < 60 {
2603                        return Ok(Value::Time(TimeValue {
2604                            hour: hour_u32 as u8,
2605                            minute: minute_u32 as u8,
2606                            second: second_u32 as u8,
2607                            timezone: None,
2608                        }));
2609                    }
2610                }
2611            }
2612        }
2613
2614        Err(LemmaError::engine(
2615            format!(
2616                "Invalid time format: '{}'. Expected: HH:MM or HH:MM:SS (e.g., 14:30 or 14:30:00)",
2617                raw
2618            ),
2619            Span {
2620                start: 0,
2621                end: 0,
2622                line: 1,
2623                col: 0,
2624            },
2625            "<unknown>",
2626            Arc::from(raw),
2627            "<unknown>",
2628            1,
2629            None::<String>,
2630        ))
2631    }
2632
2633    fn parse_duration_value(raw: &str) -> Result<Value, LemmaError> {
2634        // Parse duration like "90 minutes" or "2 hours"
2635        let parts: Vec<&str> = raw.split_whitespace().collect();
2636        if parts.len() != 2 {
2637            return Err(LemmaError::engine(
2638                format!(
2639                    "Invalid duration: '{}'. Expected format: NUMBER UNIT (e.g., '90 minutes')",
2640                    raw
2641                ),
2642                Span {
2643                    start: 0,
2644                    end: 0,
2645                    line: 1,
2646                    col: 0,
2647                },
2648                "<unknown>",
2649                Arc::from(raw),
2650                "<unknown>",
2651                1,
2652                None::<String>,
2653            ));
2654        }
2655
2656        let number_str = parts[0].replace(['_', ','], "");
2657        let value = Decimal::from_str(&number_str).map_err(|_| {
2658            LemmaError::engine(
2659                format!("Invalid number in duration: '{}'", parts[0]),
2660                Span {
2661                    start: 0,
2662                    end: 0,
2663                    line: 1,
2664                    col: 0,
2665                },
2666                "<unknown>",
2667                Arc::from(raw),
2668                "<unknown>",
2669                1,
2670                None::<String>,
2671            )
2672        })?;
2673        let unit = parts[1];
2674
2675        // Parse duration unit
2676        let unit_lower = unit.to_lowercase();
2677        let duration_unit = match unit_lower.as_str() {
2678            "year" | "years" => DurationUnit::Year,
2679            "month" | "months" => DurationUnit::Month,
2680            "week" | "weeks" => DurationUnit::Week,
2681            "day" | "days" => DurationUnit::Day,
2682            "hour" | "hours" => DurationUnit::Hour,
2683            "minute" | "minutes" => DurationUnit::Minute,
2684            "second" | "seconds" => DurationUnit::Second,
2685            "millisecond" | "milliseconds" => DurationUnit::Millisecond,
2686            "microsecond" | "microseconds" => DurationUnit::Microsecond,
2687            _ => {
2688                return Err(LemmaError::engine(
2689                    format!("Unknown duration unit: '{}'. Expected one of: years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds", unit),
2690                    Span { start: 0, end: 0, line: 1, col: 0 },
2691                    "<unknown>",
2692                    Arc::from(raw),
2693                    "<unknown>",
2694                    1,
2695                    None::<String>,
2696                ));
2697            }
2698        };
2699        Ok(Value::Duration(value, duration_unit))
2700    }
2701
2702    fn parse_ratio_value(raw: &str) -> Result<Value, LemmaError> {
2703        // Parse ratio as a decimal number
2704        let clean_number = raw.replace(['_', ','], "");
2705        let decimal = Decimal::from_str(&clean_number).map_err(|_| {
2706            LemmaError::engine(
2707                format!("Invalid ratio: '{}'. Expected a valid decimal number", raw),
2708                Span {
2709                    start: 0,
2710                    end: 0,
2711                    line: 1,
2712                    col: 0,
2713                },
2714                "<unknown>",
2715                Arc::from(raw),
2716                "<unknown>",
2717                1,
2718                None::<String>,
2719            )
2720        })?;
2721        Ok(Value::Ratio(decimal, None))
2722    }
2723}
2724
2725// Private statics for lazy initialization
2726static STANDARD_BOOLEAN: OnceLock<LemmaType> = OnceLock::new();
2727static STANDARD_SCALE: OnceLock<LemmaType> = OnceLock::new();
2728static STANDARD_NUMBER: OnceLock<LemmaType> = OnceLock::new();
2729static STANDARD_TEXT: OnceLock<LemmaType> = OnceLock::new();
2730static STANDARD_DATE: OnceLock<LemmaType> = OnceLock::new();
2731static STANDARD_TIME: OnceLock<LemmaType> = OnceLock::new();
2732static STANDARD_DURATION: OnceLock<LemmaType> = OnceLock::new();
2733static STANDARD_RATIO: OnceLock<LemmaType> = OnceLock::new();
2734
2735/// Get the standard boolean type
2736pub fn standard_boolean() -> &'static LemmaType {
2737    STANDARD_BOOLEAN.get_or_init(|| LemmaType {
2738        name: None,
2739        specifications: TypeSpecification::Boolean {
2740            help: None,
2741            default: None,
2742        },
2743    })
2744}
2745
2746/// Get the standard scale type (can have units)
2747pub fn standard_scale() -> &'static LemmaType {
2748    STANDARD_SCALE.get_or_init(|| LemmaType {
2749        name: None,
2750        specifications: TypeSpecification::Scale {
2751            minimum: None,
2752            maximum: None,
2753            decimals: None,
2754            precision: None,
2755            units: Vec::new(),
2756            help: None,
2757            default: None,
2758        },
2759    })
2760}
2761
2762/// Get the standard number type (dimensionless, no units)
2763pub fn standard_number() -> &'static LemmaType {
2764    STANDARD_NUMBER.get_or_init(|| LemmaType {
2765        name: None,
2766        specifications: TypeSpecification::Number {
2767            minimum: None,
2768            maximum: None,
2769            decimals: None,
2770            precision: None,
2771            help: None,
2772            default: None,
2773        },
2774    })
2775}
2776
2777/// Get the standard text type
2778pub fn standard_text() -> &'static LemmaType {
2779    STANDARD_TEXT.get_or_init(|| LemmaType {
2780        name: None,
2781        specifications: TypeSpecification::Text {
2782            minimum: None,
2783            maximum: None,
2784            length: None,
2785            options: Vec::new(),
2786            help: None,
2787            default: None,
2788        },
2789    })
2790}
2791
2792/// Get the standard date type
2793pub fn standard_date() -> &'static LemmaType {
2794    STANDARD_DATE.get_or_init(|| LemmaType {
2795        name: None,
2796        specifications: TypeSpecification::Date {
2797            minimum: None,
2798            maximum: None,
2799            help: None,
2800            default: None,
2801        },
2802    })
2803}
2804
2805/// Get the standard time type
2806pub fn standard_time() -> &'static LemmaType {
2807    STANDARD_TIME.get_or_init(|| LemmaType {
2808        name: None,
2809        specifications: TypeSpecification::Time {
2810            minimum: None,
2811            maximum: None,
2812            help: None,
2813            default: None,
2814        },
2815    })
2816}
2817
2818/// Get the standard duration type
2819pub fn standard_duration() -> &'static LemmaType {
2820    STANDARD_DURATION.get_or_init(|| LemmaType {
2821        name: None,
2822        specifications: TypeSpecification::Duration {
2823            help: None,
2824            default: None,
2825        },
2826    })
2827}
2828
2829/// Get the standard ratio type
2830pub fn standard_ratio() -> &'static LemmaType {
2831    STANDARD_RATIO.get_or_init(|| LemmaType {
2832        name: None,
2833        specifications: TypeSpecification::Ratio {
2834            minimum: None,
2835            maximum: None,
2836            units: vec![
2837                Unit {
2838                    name: "percent".to_string(),
2839                    value: Decimal::from(100),
2840                },
2841                Unit {
2842                    name: "permille".to_string(),
2843                    value: Decimal::from(1000),
2844                },
2845            ],
2846            help: None,
2847            default: None,
2848        },
2849    })
2850}
2851
2852// Helper macros to initialize standard types
2853
2854impl LemmaType {
2855    /// Get an example value string for this type, suitable for UI help text
2856    pub fn example_value(&self) -> &'static str {
2857        match &self.specifications {
2858            TypeSpecification::Text { .. } => "\"hello world\"",
2859            TypeSpecification::Scale { .. } => "3.14",
2860            TypeSpecification::Number { .. } => "3.14",
2861            TypeSpecification::Boolean { .. } => "true",
2862            TypeSpecification::Date { .. } => "2023-12-25T14:30:00Z",
2863            TypeSpecification::Veto { .. } => "veto",
2864            TypeSpecification::Time { .. } => "14:30:00",
2865            TypeSpecification::Duration { .. } => "90 minutes",
2866            TypeSpecification::Ratio { .. } => "50%",
2867        }
2868    }
2869}
2870
2871fn strip_surrounding_quotes(s: &str) -> String {
2872    let bytes = s.as_bytes();
2873    if bytes.len() >= 2 {
2874        let first = bytes[0];
2875        let last = bytes[bytes.len() - 1];
2876        if (first == b'"' && last == b'"') || (first == b'\'' && last == b'\'') {
2877            return s[1..bytes.len() - 1].to_string();
2878        }
2879    }
2880    s.to_string()
2881}
2882
2883impl fmt::Display for LemmaType {
2884    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2885        write!(f, "{}", self.name())
2886    }
2887}
2888
2889#[cfg(test)]
2890mod tests {
2891    use super::*;
2892    use rust_decimal::Decimal;
2893    use std::str::FromStr;
2894
2895    #[test]
2896    fn test_arithmetic_operation_name() {
2897        assert_eq!(ArithmeticComputation::Add.name(), "addition");
2898        assert_eq!(ArithmeticComputation::Subtract.name(), "subtraction");
2899        assert_eq!(ArithmeticComputation::Multiply.name(), "multiplication");
2900        assert_eq!(ArithmeticComputation::Divide.name(), "division");
2901        assert_eq!(ArithmeticComputation::Modulo.name(), "modulo");
2902        assert_eq!(ArithmeticComputation::Power.name(), "exponentiation");
2903    }
2904
2905    #[test]
2906    fn test_comparison_operator_name() {
2907        assert_eq!(ComparisonComputation::GreaterThan.name(), "greater than");
2908        assert_eq!(ComparisonComputation::LessThan.name(), "less than");
2909        assert_eq!(
2910            ComparisonComputation::GreaterThanOrEqual.name(),
2911            "greater than or equal"
2912        );
2913        assert_eq!(
2914            ComparisonComputation::LessThanOrEqual.name(),
2915            "less than or equal"
2916        );
2917        assert_eq!(ComparisonComputation::Equal.name(), "equal");
2918        assert_eq!(ComparisonComputation::NotEqual.name(), "not equal");
2919        assert_eq!(ComparisonComputation::Is.name(), "is");
2920        assert_eq!(ComparisonComputation::IsNot.name(), "is not");
2921    }
2922
2923    #[test]
2924    fn test_literal_value_to_standard_type() {
2925        let one = Decimal::from_str("1").unwrap();
2926
2927        assert_eq!(LiteralValue::text("".to_string()).lemma_type.name(), "text");
2928        assert_eq!(LiteralValue::number(one).lemma_type.name(), "number");
2929        assert_eq!(
2930            LiteralValue::boolean(BooleanValue::True).lemma_type.name(),
2931            "boolean"
2932        );
2933
2934        let dt = DateTimeValue {
2935            year: 2024,
2936            month: 1,
2937            day: 1,
2938            hour: 0,
2939            minute: 0,
2940            second: 0,
2941            timezone: None,
2942        };
2943        assert_eq!(LiteralValue::date(dt).lemma_type.name(), "date");
2944        assert_eq!(
2945            LiteralValue::ratio(one / Decimal::from(100), Some("percent".to_string()))
2946                .lemma_type
2947                .name(),
2948            "ratio"
2949        );
2950        assert_eq!(
2951            LiteralValue::duration(one, DurationUnit::Second)
2952                .lemma_type
2953                .name(),
2954            "duration"
2955        );
2956    }
2957
2958    #[test]
2959    fn test_arithmetic_operation_display() {
2960        assert_eq!(format!("{}", ArithmeticComputation::Add), "+");
2961        assert_eq!(format!("{}", ArithmeticComputation::Subtract), "-");
2962        assert_eq!(format!("{}", ArithmeticComputation::Multiply), "*");
2963        assert_eq!(format!("{}", ArithmeticComputation::Divide), "/");
2964        assert_eq!(format!("{}", ArithmeticComputation::Modulo), "%");
2965        assert_eq!(format!("{}", ArithmeticComputation::Power), "^");
2966    }
2967
2968    #[test]
2969    fn test_comparison_operator_display() {
2970        assert_eq!(format!("{}", ComparisonComputation::GreaterThan), ">");
2971        assert_eq!(format!("{}", ComparisonComputation::LessThan), "<");
2972        assert_eq!(
2973            format!("{}", ComparisonComputation::GreaterThanOrEqual),
2974            ">="
2975        );
2976        assert_eq!(format!("{}", ComparisonComputation::LessThanOrEqual), "<=");
2977        assert_eq!(format!("{}", ComparisonComputation::Equal), "==");
2978        assert_eq!(format!("{}", ComparisonComputation::NotEqual), "!=");
2979        assert_eq!(format!("{}", ComparisonComputation::Is), "is");
2980        assert_eq!(format!("{}", ComparisonComputation::IsNot), "is not");
2981    }
2982
2983    #[test]
2984    fn test_duration_unit_display() {
2985        assert_eq!(format!("{}", DurationUnit::Second), "seconds");
2986        assert_eq!(format!("{}", DurationUnit::Minute), "minutes");
2987        assert_eq!(format!("{}", DurationUnit::Hour), "hours");
2988        assert_eq!(format!("{}", DurationUnit::Day), "days");
2989        assert_eq!(format!("{}", DurationUnit::Week), "weeks");
2990        assert_eq!(format!("{}", DurationUnit::Millisecond), "milliseconds");
2991        assert_eq!(format!("{}", DurationUnit::Microsecond), "microseconds");
2992    }
2993
2994    #[test]
2995    fn test_conversion_target_display() {
2996        assert_eq!(format!("{}", ConversionTarget::Percentage), "percent");
2997        assert_eq!(
2998            format!("{}", ConversionTarget::Duration(DurationUnit::Hour)),
2999            "hours"
3000        );
3001    }
3002
3003    #[test]
3004    fn test_doc_type_display() {
3005        assert_eq!(format!("{}", standard_text()), "text");
3006        assert_eq!(format!("{}", standard_number()), "number");
3007        assert_eq!(format!("{}", standard_date()), "date");
3008        assert_eq!(format!("{}", standard_boolean()), "boolean");
3009        assert_eq!(format!("{}", standard_duration()), "duration");
3010    }
3011
3012    #[test]
3013    fn test_type_constructor() {
3014        let specs = TypeSpecification::number();
3015        let lemma_type = LemmaType::new("dice".to_string(), specs);
3016        assert_eq!(lemma_type.name(), "dice");
3017    }
3018
3019    #[test]
3020    fn test_type_display() {
3021        let specs = TypeSpecification::text();
3022        let lemma_type = LemmaType::new("name".to_string(), specs);
3023        assert_eq!(format!("{}", lemma_type), "name");
3024    }
3025
3026    #[test]
3027    fn test_type_equality() {
3028        let specs1 = TypeSpecification::number();
3029        let specs2 = TypeSpecification::number();
3030        let lemma_type1 = LemmaType::new("dice".to_string(), specs1);
3031        let lemma_type2 = LemmaType::new("dice".to_string(), specs2);
3032        assert_eq!(lemma_type1, lemma_type2);
3033    }
3034
3035    #[test]
3036    fn test_type_serialization() {
3037        let specs = TypeSpecification::number();
3038        let lemma_type = LemmaType::new("dice".to_string(), specs);
3039        let serialized = serde_json::to_string(&lemma_type).unwrap();
3040        let deserialized: LemmaType = serde_json::from_str(&serialized).unwrap();
3041        assert_eq!(lemma_type, deserialized);
3042    }
3043
3044    #[test]
3045    fn test_literal_value_display_value() {
3046        let ten = Decimal::from_str("10").unwrap();
3047
3048        assert_eq!(
3049            LiteralValue::text("hello".to_string()).display_value(),
3050            "\"hello\""
3051        );
3052        assert_eq!(LiteralValue::number(ten).display_value(), "10");
3053        assert_eq!(
3054            LiteralValue::boolean(BooleanValue::True).display_value(),
3055            "true"
3056        );
3057        assert_eq!(
3058            LiteralValue::boolean(BooleanValue::False).display_value(),
3059            "false"
3060        );
3061        // 10% stored as 0.10 ratio with "percent" unit
3062        let ten_percent_ratio = LiteralValue::ratio(
3063            Decimal::from_str("0.10").unwrap(),
3064            Some("percent".to_string()),
3065        );
3066        // ratio with "percent" unit should display as percent
3067        assert_eq!(ten_percent_ratio.display_value(), "10%");
3068
3069        let time = TimeValue {
3070            hour: 14,
3071            minute: 30,
3072            second: 0,
3073            timezone: None,
3074        };
3075        let time_display = LiteralValue::time(time).display_value();
3076        assert!(time_display.contains("14"));
3077        assert!(time_display.contains("30"));
3078    }
3079
3080    #[test]
3081    fn test_literal_value_time_type() {
3082        let time = TimeValue {
3083            hour: 14,
3084            minute: 30,
3085            second: 0,
3086            timezone: None,
3087        };
3088        assert_eq!(LiteralValue::time(time).lemma_type.name(), "time");
3089    }
3090
3091    #[test]
3092    fn test_datetime_value_display() {
3093        let dt = DateTimeValue {
3094            year: 2024,
3095            month: 12,
3096            day: 25,
3097            hour: 14,
3098            minute: 30,
3099            second: 45,
3100            timezone: Some(TimezoneValue {
3101                offset_hours: 1,
3102                offset_minutes: 0,
3103            }),
3104        };
3105        let display = format!("{}", dt);
3106        assert!(display.contains("2024"));
3107        assert!(display.contains("12"));
3108        assert!(display.contains("25"));
3109    }
3110
3111    #[test]
3112    fn test_time_value_display() {
3113        let time = TimeValue {
3114            hour: 14,
3115            minute: 30,
3116            second: 45,
3117            timezone: Some(TimezoneValue {
3118                offset_hours: -5,
3119                offset_minutes: 30,
3120            }),
3121        };
3122        let display = format!("{}", time);
3123        assert!(display.contains("14"));
3124        assert!(display.contains("30"));
3125        assert!(display.contains("45"));
3126    }
3127
3128    #[test]
3129    fn test_timezone_value() {
3130        let tz_positive = TimezoneValue {
3131            offset_hours: 5,
3132            offset_minutes: 30,
3133        };
3134        assert_eq!(tz_positive.offset_hours, 5);
3135        assert_eq!(tz_positive.offset_minutes, 30);
3136
3137        let tz_negative = TimezoneValue {
3138            offset_hours: -8,
3139            offset_minutes: 0,
3140        };
3141        assert_eq!(tz_negative.offset_hours, -8);
3142    }
3143
3144    #[test]
3145    fn test_negation_types() {
3146        let json = serde_json::to_string(&NegationType::Not).expect("serialize NegationType");
3147        let decoded: NegationType = serde_json::from_str(&json).expect("deserialize NegationType");
3148        assert_eq!(decoded, NegationType::Not);
3149    }
3150
3151    #[test]
3152    fn test_veto_expression() {
3153        let veto_with_message = VetoExpression {
3154            message: Some("Must be over 18".to_string()),
3155        };
3156        assert_eq!(
3157            veto_with_message.message,
3158            Some("Must be over 18".to_string())
3159        );
3160
3161        let veto_without_message = VetoExpression { message: None };
3162        assert!(veto_without_message.message.is_none());
3163    }
3164
3165    #[test]
3166    fn test_expression_get_source_text_with_location() {
3167        use crate::{Expression, ExpressionKind, LiteralValue, Source, Span};
3168        use std::collections::HashMap;
3169
3170        let source = "fact value = 42";
3171        let mut sources = HashMap::new();
3172        sources.insert("test.lemma".to_string(), source.to_string());
3173
3174        let span = Span {
3175            start: 13,
3176            end: 15,
3177            line: 1,
3178            col: 13,
3179        };
3180        let source_location = Some(Source::new("test.lemma", span, "test"));
3181        let expr = Expression::new(
3182            ExpressionKind::Literal(LiteralValue::number(rust_decimal::Decimal::new(42, 0))),
3183            source_location,
3184        );
3185
3186        assert_eq!(expr.get_source_text(&sources), Some("42".to_string()));
3187    }
3188
3189    #[test]
3190    fn test_expression_get_source_text_no_location() {
3191        use crate::{Expression, ExpressionKind, LiteralValue};
3192        use std::collections::HashMap;
3193
3194        let mut sources = HashMap::new();
3195        sources.insert("test.lemma".to_string(), "fact value = 42".to_string());
3196
3197        let expr = Expression::new(
3198            ExpressionKind::Literal(LiteralValue::number(rust_decimal::Decimal::new(42, 0))),
3199            None,
3200        );
3201
3202        assert_eq!(expr.get_source_text(&sources), None);
3203    }
3204
3205    #[test]
3206    fn test_expression_get_source_text_source_not_found() {
3207        use crate::{Expression, ExpressionKind, LiteralValue, Source, Span};
3208        use std::collections::HashMap;
3209
3210        let sources = HashMap::new();
3211        let span = Span {
3212            start: 0,
3213            end: 5,
3214            line: 1,
3215            col: 0,
3216        };
3217        let source_location = Some(Source::new("missing.lemma", span, "test"));
3218        let expr = Expression::new(
3219            ExpressionKind::Literal(LiteralValue::number(rust_decimal::Decimal::new(42, 0))),
3220            source_location,
3221        );
3222
3223        assert_eq!(expr.get_source_text(&sources), None);
3224    }
3225}