Skip to main content

legalis_core/
lib.rs

1//! Legalis-Core: Core types and traits for the Legalis-RS legal framework.
2//!
3//! This module defines the foundational types that represent legal concepts,
4//! including the distinction between deterministic (computable) and
5//! discretionary (requiring human judgment) legal outcomes.
6//!
7//! ## Design Philosophy
8//!
9//! ### "Not Everything Should Be Computable"
10//!
11//! Legalis-RS is built on the principle that while many legal determinations can be
12//! automated, some require human judgment and interpretation. This is reflected in the
13//! [`LegalResult`] type, which explicitly distinguishes between:
14//!
15//! - **Deterministic** outcomes: Mechanically derivable from rules (age >= 18, income < $50k)
16//! - **Judicial Discretion**: Requires human interpretation (just cause, public welfare)
17//! - **Void**: Logical contradictions in the law itself
18//!
19//! This design prevents "AI theocracy" by preserving human agency in legal interpretation
20//! where it matters most.
21//!
22//! ### Type-Safe Legal Modeling
23//!
24//! The crate uses Rust's type system to enforce legal invariants at compile time:
25//!
26//! - Statutes must have IDs, titles, and effects
27//! - Temporal validity is checked (expiry dates must follow effective dates)
28//! - Conditions are strongly typed and composable
29//! - Entity attributes can be type-safe via [`TypedEntity`]
30//!
31//! ### Builder Pattern for Clarity
32//!
33//! Complex legal structures use the builder pattern for readability:
34//!
35//! ```no_run
36//! # use legalis_core::{Statute, Effect, EffectType, Condition, ComparisonOp, TemporalValidity};
37//! # use chrono::NaiveDate;
38//! let statute = Statute::new("tax-law-2025", "Income Tax", Effect::new(EffectType::Grant, "Tax credit"))
39//!     .with_precondition(Condition::Income { operator: ComparisonOp::LessThan, value: 50000 })
40//!     .with_temporal_validity(TemporalValidity::new()
41//!         .with_effective_date(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap()))
42//!     .with_jurisdiction("US")
43//!     .with_version(1);
44//! ```
45//!
46//! ### Validation Over Panics
47//!
48//! The crate prefers validation methods that return errors over runtime panics:
49//!
50//! - [`Statute::validate()`] returns a list of validation errors
51//! - [`Statute::validated()`] returns `Result<Statute, Vec<ValidationError>>`
52//! - [`TypedEntity`] returns `Result` for type mismatches
53//!
54//! This allows calling code to decide how to handle invalid states.
55//!
56//! ### Temporal Awareness
57//!
58//! Legal rules change over time. The [`TemporalValidity`] type tracks:
59//!
60//! - Effective dates (when laws come into force)
61//! - Expiry dates (sunset clauses)
62//! - Enactment and amendment timestamps
63//!
64//! This enables historical legal queries and version management.
65//!
66//! ## Architecture Decisions
67//!
68//! ### Why ADTs for Conditions?
69//!
70//! The [`Condition`] enum uses algebraic data types (ADTs) with recursive composition
71//! (AND/OR/NOT) rather than a trait-based visitor pattern. This provides:
72//!
73//! - Pattern matching exhaustiveness checking at compile time
74//! - Easier serialization/deserialization
75//! - Simpler mental model for legal rule composition
76//! - Better performance (no dynamic dispatch)
77//!
78//! ### Why Trait Objects for Entities?
79//!
80//! [`LegalEntity`] is a trait rather than a concrete type because:
81//!
82//! - Different systems may have different entity storage needs
83//! - Allows integration with existing database models
84//! - Supports both simple ([`BasicEntity`]) and type-safe ([`TypedEntity`]) implementations
85//! - Enables custom entity types for domain-specific needs
86//!
87//! ### Why HashMap for Effect Parameters?
88//!
89//! Effect parameters use `HashMap<String, String>` for flexibility:
90//!
91//! - Legal effects vary widely in their parameters
92//! - Allows extension without breaking changes
93//! - Simple serialization format
94//! - Type safety can be added at higher layers if needed
95//!
96//! ## Type Relationships
97//!
98//! The following diagrams illustrate the core type relationships in legalis-core:
99//!
100//! ### Core Legal Types
101//!
102//! ```mermaid
103//! classDiagram
104//!     class Statute {
105//!         +String id
106//!         +String title
107//!         +Effect effect
108//!         +Option~Condition~ precondition
109//!         +Option~TemporalValidity~ temporal
110//!         +Vec~String~ tags
111//!         +validate() Vec~ValidationError~
112//!     }
113//!
114//!     class Effect {
115//!         +EffectType effect_type
116//!         +String description
117//!         +HashMap parameters
118//!     }
119//!
120//!     class EffectType {
121//!         <<enumeration>>
122//!         Grant
123//!         Obligation
124//!         Prohibition
125//!         Conditional
126//!         Delayed
127//!         Compound
128//!     }
129//!
130//!     class LegalResult~T~ {
131//!         <<enumeration>>
132//!         Deterministic(T)
133//!         JudicialDiscretion
134//!         Void
135//!         +map(f) LegalResult~U~
136//!         +and_then(f) LegalResult~U~
137//!     }
138//!
139//!     class TemporalValidity {
140//!         +Option~NaiveDate~ effective_date
141//!         +Option~NaiveDate~ expiry_date
142//!         +Option~DateTime~ enactment_date
143//!         +is_valid_on(date) bool
144//!     }
145//!
146//!     Statute --> Effect : contains
147//!     Statute --> Condition : optional precondition
148//!     Statute --> TemporalValidity : optional temporal
149//!     Effect --> EffectType : has type
150//!     Statute ..> LegalResult : validation returns
151//! ```
152//!
153//! ### Condition Composition
154//!
155//! ```mermaid
156//! classDiagram
157//!     class Condition {
158//!         <<enumeration>>
159//!         Age
160//!         Income
161//!         Geographic
162//!         DateRange
163//!         EntityRelationship
164//!         ResidencyDuration
165//!         And(Vec~Condition~)
166//!         Or(Vec~Condition~)
167//!         Not(Box~Condition~)
168//!         +evaluate(entity) LegalResult~bool~
169//!         +normalize() Condition
170//!     }
171//!
172//!     class ComparisonOp {
173//!         <<enumeration>>
174//!         Equal
175//!         NotEqual
176//!         LessThan
177//!         LessThanOrEqual
178//!         GreaterThan
179//!         GreaterThanOrEqual
180//!     }
181//!
182//!     Condition --> Condition : recursive composition
183//!     Condition --> ComparisonOp : uses for comparisons
184//! ```
185//!
186//! ### Entity Type Hierarchy
187//!
188//! ```mermaid
189//! classDiagram
190//!     class LegalEntity {
191//!         <<trait>>
192//!         +id() String
193//!         +entity_type() String
194//!         +get_attribute(key) Option~String~
195//!         +set_attribute(key, value)
196//!         +attributes() HashMap
197//!     }
198//!
199//!     class BasicEntity {
200//!         +String id
201//!         +String entity_type
202//!         +HashMap attributes
203//!     }
204//!
205//!     class TypedEntity {
206//!         +String id
207//!         +String entity_type
208//!         +TypedAttributes attributes
209//!         +get_typed~T~(key) Result~T~
210//!         +set_typed~T~(key, value)
211//!     }
212//!
213//!     class TypedAttributes {
214//!         +HashMap~String,AttributeValue~ data
215//!         +get~T~(key) Result~T~
216//!         +set~T~(key, value)
217//!     }
218//!
219//!     class AttributeValue {
220//!         <<enumeration>>
221//!         String(String)
222//!         U32(u32)
223//!         Bool(bool)
224//!         Date(NaiveDate)
225//!     }
226//!
227//!     LegalEntity <|.. BasicEntity : implements
228//!     LegalEntity <|.. TypedEntity : implements
229//!     TypedEntity --> TypedAttributes : contains
230//!     TypedAttributes --> AttributeValue : stores
231//! ```
232//!
233//! ### Case Law Structure
234//!
235//! ```mermaid
236//! classDiagram
237//!     class Case {
238//!         +String id
239//!         +String title
240//!         +Court court
241//!         +NaiveDate decision_date
242//!         +Vec~CaseRule~ rules
243//!     }
244//!
245//!     class Court {
246//!         +String name
247//!         +String jurisdiction
248//!         +u8 level
249//!     }
250//!
251//!     class CaseRule {
252//!         +String principle
253//!         +String reasoning
254//!         +Vec~String~ facts
255//!     }
256//!
257//!     class Precedent {
258//!         +String case_id
259//!         +PrecedentWeight weight
260//!     }
261//!
262//!     class PrecedentWeight {
263//!         <<enumeration>>
264//!         Binding
265//!         Persuasive
266//!         Distinguishable
267//!     }
268//!
269//!     Case --> Court : decided by
270//!     Case --> CaseRule : contains
271//!     Precedent --> PrecedentWeight : has
272//! ```
273//!
274//! ## Features
275//!
276//! - `serde` (default): Enable serialization/deserialization support for all types
277//!
278//! ## Performance Considerations
279//!
280//! - Condition evaluation is non-allocating where possible
281//! - Entity attributes use copy-on-write semantics
282//! - Statutes are cheaply cloneable (most fields are small or reference-counted)
283//! - Property-based tests ensure reasonable performance across edge cases
284
285pub mod case_law;
286pub mod const_collections;
287pub mod formats;
288pub mod testing;
289pub mod transactions;
290pub mod typed_attributes;
291pub mod typed_effects;
292pub mod workflows;
293
294// Performance & Memory (v0.1.9)
295pub mod arena;
296pub mod compact;
297pub mod interning;
298pub mod lazy;
299pub mod parallel_eval;
300
301// Distributed Legal Reasoning (v0.2.0)
302pub mod distributed;
303
304// Formal Methods Integration (v0.2.1)
305pub mod formal_methods;
306
307// Legal Knowledge Graphs (v0.2.2)
308pub mod knowledge_graph;
309
310// Advanced Temporal Logic (v0.2.3)
311pub mod temporal;
312
313// Legal Document Processing (v0.2.4)
314pub mod document_processing;
315
316// Probabilistic Legal Reasoning (v0.2.5)
317pub mod probabilistic;
318
319// Multi-Jurisdictional Support (v0.2.6)
320pub mod multi_jurisdictional;
321
322// Legal Explanation Generation (v0.2.7)
323pub mod explanation;
324
325// Rule Learning & Discovery (v0.2.8)
326pub mod rule_learning;
327
328// Performance Optimization (v0.2.9)
329pub mod adaptive_cache;
330pub mod gpu_offload;
331pub mod jit_framework;
332pub mod memory_pool;
333pub mod simd_eval;
334
335// AI-Native Legal Reasoning (v0.3.0)
336pub mod explainable_ai;
337pub mod finetuned_llm;
338pub mod hybrid_reasoning;
339pub mod llm_interpretation;
340pub mod neural_entailment;
341
342// Blockchain & Smart Contract Bridge (v0.3.1)
343pub mod blockchain_verification;
344pub mod decentralized_registry;
345pub mod oracle;
346pub mod smart_contract;
347pub mod zkp;
348
349// Legal Digital Twins (v0.3.2)
350pub mod digital_twin;
351pub mod event_sourcing;
352pub mod realtime_sync;
353pub mod scenario_simulation;
354pub mod time_travel;
355
356// Quantum-Ready Legal Logic (v0.3.3)
357pub mod quantum;
358
359// Autonomous Legal Agents (v0.3.4)
360pub mod autonomous_agents;
361
362use chrono::{DateTime, Datelike, NaiveDate, Utc};
363use std::collections::HashMap;
364use std::fmt;
365use uuid::Uuid;
366
367#[cfg(feature = "serde")]
368use serde::{Deserialize, Serialize};
369
370#[cfg(feature = "schema")]
371use schemars::JsonSchema;
372
373// Re-export Common Law types
374pub use case_law::{
375    Case, CaseDatabase, CaseRule, Court, DamageType, Precedent, PrecedentApplication,
376    PrecedentWeight,
377};
378
379// Re-export Typed Attributes
380pub use typed_attributes::{AttributeError, AttributeValue, TypedAttributes};
381
382/// Legal judgment result as an Algebraic Data Type (ADT).
383///
384/// This type embodies the core philosophy of Legalis-RS:
385/// "Not everything should be computable" - preserving human agency
386/// in legal interpretation.
387///
388/// # Examples
389///
390/// ## Deterministic Result
391///
392/// ```
393/// use legalis_core::LegalResult;
394///
395/// let age = 25;
396/// let result: LegalResult<bool> = if age >= 18 {
397///     LegalResult::Deterministic(true)
398/// } else {
399///     LegalResult::Deterministic(false)
400/// };
401///
402/// assert!(result.is_deterministic());
403/// ```
404///
405/// ## Judicial Discretion
406///
407/// ```
408/// use legalis_core::LegalResult;
409/// use uuid::Uuid;
410///
411/// let result: LegalResult<bool> = LegalResult::JudicialDiscretion {
412///     issue: "Determine if there is just cause for termination".to_string(),
413///     context_id: Uuid::new_v4(),
414///     narrative_hint: Some("Consider employment history and circumstances".to_string()),
415/// };
416///
417/// assert!(result.requires_discretion());
418/// ```
419///
420/// ## Mapping Values
421///
422/// ```
423/// use legalis_core::LegalResult;
424///
425/// let amount: LegalResult<u32> = LegalResult::Deterministic(100);
426/// let doubled = amount.map(|x| x * 2);
427///
428/// assert_eq!(doubled, LegalResult::Deterministic(200));
429/// ```
430#[derive(Debug, Clone, PartialEq)]
431#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
432#[cfg_attr(feature = "schema", derive(JsonSchema))]
433pub enum LegalResult<T> {
434    /// Deterministic domain: Results derived automatically through computation.
435    /// Examples: age requirements, income limits, deadline calculations.
436    Deterministic(T),
437
438    /// Discretionary domain: Cannot be determined by logic alone,
439    /// requires human "narrative" (interpretation).
440    /// This is the safeguard against "AI theocracy".
441    /// The system halts here and passes the ball to humans.
442    JudicialDiscretion {
443        /// The issue at hand (e.g., "existence of just cause", "violation of public welfare")
444        issue: String,
445        /// Reference to context data
446        context_id: Uuid,
447        /// Recommended judgment materials (generated by LLM, but does not decide)
448        narrative_hint: Option<String>,
449    },
450
451    /// Logical contradiction: A bug in the law itself.
452    Void { reason: String },
453}
454
455impl<T> LegalResult<T> {
456    /// Returns true if this is a deterministic result.
457    pub fn is_deterministic(&self) -> bool {
458        matches!(self, Self::Deterministic(_))
459    }
460
461    /// Returns true if judicial discretion is required.
462    pub fn requires_discretion(&self) -> bool {
463        matches!(self, Self::JudicialDiscretion { .. })
464    }
465
466    /// Returns true if this represents a void/invalid state.
467    pub fn is_void(&self) -> bool {
468        matches!(self, Self::Void { .. })
469    }
470
471    /// Maps a deterministic value using the provided function.
472    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> LegalResult<U> {
473        match self {
474            Self::Deterministic(t) => LegalResult::Deterministic(f(t)),
475            Self::JudicialDiscretion {
476                issue,
477                context_id,
478                narrative_hint,
479            } => LegalResult::JudicialDiscretion {
480                issue,
481                context_id,
482                narrative_hint,
483            },
484            Self::Void { reason } => LegalResult::Void { reason },
485        }
486    }
487}
488
489impl<T: fmt::Display> fmt::Display for LegalResult<T> {
490    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
491        match self {
492            Self::Deterministic(value) => write!(f, "Deterministic({})", value),
493            Self::JudicialDiscretion {
494                issue,
495                narrative_hint,
496                ..
497            } => {
498                write!(f, "JudicialDiscretion: {}", issue)?;
499                if let Some(hint) = narrative_hint {
500                    write!(f, " [hint: {}]", hint)?;
501                }
502                Ok(())
503            }
504            Self::Void { reason } => write!(f, "Void: {}", reason),
505        }
506    }
507}
508
509/// Legal entity (natural person, legal person, or AI agent).
510///
511/// This trait represents any entity that can participate in legal relationships
512/// and be subject to legal rules. Implementors can store and retrieve attributes
513/// that are used in legal condition evaluation.
514///
515/// # Examples
516///
517/// ```
518/// use legalis_core::{BasicEntity, LegalEntity};
519///
520/// let mut entity = BasicEntity::new();
521/// entity.set_attribute("age", "25".to_string());
522/// entity.set_attribute("citizenship", "US".to_string());
523///
524/// assert_eq!(entity.get_attribute("age"), Some("25".to_string()));
525/// assert_eq!(entity.get_attribute("citizenship"), Some("US".to_string()));
526/// assert_eq!(entity.get_attribute("nonexistent"), None);
527/// ```
528pub trait LegalEntity: Send + Sync {
529    /// Returns the unique identifier of this entity.
530    fn id(&self) -> Uuid;
531
532    /// Gets an attribute value by key.
533    fn get_attribute(&self, key: &str) -> Option<String>;
534
535    /// Sets an attribute value.
536    fn set_attribute(&mut self, key: &str, value: String);
537}
538
539/// A simple implementation of LegalEntity for testing and basic use cases.
540///
541/// This struct provides a straightforward key-value string storage for entity attributes.
542/// For type-safe attribute handling, consider using [`TypedEntity`] instead.
543///
544/// # Examples
545///
546/// ```
547/// use legalis_core::{BasicEntity, LegalEntity};
548///
549/// let mut person = BasicEntity::new();
550/// person.set_attribute("name", "Alice".to_string());
551/// person.set_attribute("age", "30".to_string());
552///
553/// assert_eq!(person.get_attribute("name"), Some("Alice".to_string()));
554/// ```
555#[derive(Debug, Clone)]
556#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
557#[cfg_attr(feature = "schema", derive(JsonSchema))]
558pub struct BasicEntity {
559    id: Uuid,
560    attributes: std::collections::HashMap<String, String>,
561}
562
563impl BasicEntity {
564    /// Creates a new BasicEntity with a random UUID.
565    pub fn new() -> Self {
566        Self {
567            id: Uuid::new_v4(),
568            attributes: std::collections::HashMap::new(),
569        }
570    }
571
572    /// Creates a new BasicEntity with a specific UUID.
573    pub fn with_id(id: Uuid) -> Self {
574        Self {
575            id,
576            attributes: std::collections::HashMap::new(),
577        }
578    }
579}
580
581impl Default for BasicEntity {
582    fn default() -> Self {
583        Self::new()
584    }
585}
586
587impl LegalEntity for BasicEntity {
588    fn id(&self) -> Uuid {
589        self.id
590    }
591
592    fn get_attribute(&self, key: &str) -> Option<String> {
593        self.attributes.get(key).cloned()
594    }
595
596    fn set_attribute(&mut self, key: &str, value: String) {
597        self.attributes.insert(key.to_string(), value);
598    }
599}
600
601/// A type-safe implementation of LegalEntity using strongly-typed attributes.
602///
603/// This provides compile-time type safety and runtime validation for entity attributes,
604/// replacing error-prone string parsing with explicit type handling.
605///
606/// # Examples
607///
608/// ```
609/// use legalis_core::TypedEntity;
610/// use chrono::NaiveDate;
611///
612/// let mut person = TypedEntity::new();
613/// person.set_string("name", "Bob");
614/// person.set_u32("age", 25);
615/// person.set_bool("is_citizen", true);
616/// person.set_date("birth_date", NaiveDate::from_ymd_opt(1999, 1, 15).unwrap());
617///
618/// assert_eq!(person.get_string("name").unwrap(), "Bob");
619/// assert_eq!(person.get_u32("age").unwrap(), 25);
620/// assert!(person.get_bool("is_citizen").unwrap());
621///
622/// // Type safety: attempting to get a string as a number returns an error
623/// assert!(person.get_u32("name").is_err());
624/// ```
625#[derive(Debug, Clone)]
626#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
627#[cfg_attr(feature = "schema", derive(JsonSchema))]
628pub struct TypedEntity {
629    id: Uuid,
630    attributes: TypedAttributes,
631}
632
633impl TypedEntity {
634    /// Creates a new TypedEntity with a random UUID.
635    pub fn new() -> Self {
636        Self {
637            id: Uuid::new_v4(),
638            attributes: TypedAttributes::new(),
639        }
640    }
641
642    /// Creates a new TypedEntity with a specific UUID.
643    pub fn with_id(id: Uuid) -> Self {
644        Self {
645            id,
646            attributes: TypedAttributes::new(),
647        }
648    }
649
650    /// Gets the typed attributes storage.
651    pub fn attributes(&self) -> &TypedAttributes {
652        &self.attributes
653    }
654
655    /// Gets mutable access to the typed attributes storage.
656    pub fn attributes_mut(&mut self) -> &mut TypedAttributes {
657        &mut self.attributes
658    }
659
660    /// Sets a u32 attribute.
661    pub fn set_u32(&mut self, key: impl Into<String>, value: u32) {
662        self.attributes.set_u32(key, value);
663    }
664
665    /// Gets a u32 attribute.
666    pub fn get_u32(&self, key: &str) -> Result<u32, AttributeError> {
667        self.attributes.get_u32(key)
668    }
669
670    /// Sets a u64 attribute.
671    pub fn set_u64(&mut self, key: impl Into<String>, value: u64) {
672        self.attributes.set_u64(key, value);
673    }
674
675    /// Gets a u64 attribute.
676    pub fn get_u64(&self, key: &str) -> Result<u64, AttributeError> {
677        self.attributes.get_u64(key)
678    }
679
680    /// Sets a boolean attribute.
681    pub fn set_bool(&mut self, key: impl Into<String>, value: bool) {
682        self.attributes.set_bool(key, value);
683    }
684
685    /// Gets a boolean attribute.
686    pub fn get_bool(&self, key: &str) -> Result<bool, AttributeError> {
687        self.attributes.get_bool(key)
688    }
689
690    /// Sets a string attribute.
691    pub fn set_string(&mut self, key: impl Into<String>, value: impl Into<String>) {
692        self.attributes.set_string(key, value);
693    }
694
695    /// Gets a string attribute.
696    pub fn get_string(&self, key: &str) -> Result<&str, AttributeError> {
697        self.attributes.get_string(key)
698    }
699
700    /// Sets a date attribute.
701    pub fn set_date(&mut self, key: impl Into<String>, value: NaiveDate) {
702        self.attributes.set_date(key, value);
703    }
704
705    /// Gets a date attribute.
706    pub fn get_date(&self, key: &str) -> Result<NaiveDate, AttributeError> {
707        self.attributes.get_date(key)
708    }
709
710    /// Sets an f64 attribute.
711    pub fn set_f64(&mut self, key: impl Into<String>, value: f64) {
712        self.attributes.set_f64(key, value);
713    }
714
715    /// Gets an f64 attribute.
716    pub fn get_f64(&self, key: &str) -> Result<f64, AttributeError> {
717        self.attributes.get_f64(key)
718    }
719
720    /// Sets a typed attribute value.
721    pub fn set_typed(&mut self, key: impl Into<String>, value: AttributeValue) {
722        self.attributes.set(key, value);
723    }
724
725    /// Gets a typed attribute value.
726    pub fn get_typed(&self, key: &str) -> Option<&AttributeValue> {
727        self.attributes.get(key)
728    }
729
730    /// Checks if an attribute exists.
731    pub fn has_attribute(&self, key: &str) -> bool {
732        self.attributes.has(key)
733    }
734}
735
736impl Default for TypedEntity {
737    fn default() -> Self {
738        Self::new()
739    }
740}
741
742impl LegalEntity for TypedEntity {
743    fn id(&self) -> Uuid {
744        self.id
745    }
746
747    fn get_attribute(&self, key: &str) -> Option<String> {
748        self.attributes.get(key).map(|v| v.to_string_value())
749    }
750
751    fn set_attribute(&mut self, key: &str, value: String) {
752        self.attributes
753            .set(key, AttributeValue::parse_from_string(&value));
754    }
755}
756
757/// Time unit for duration conditions.
758#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
759#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
760#[cfg_attr(feature = "schema", derive(JsonSchema))]
761pub enum DurationUnit {
762    /// Days
763    Days,
764    /// Weeks
765    Weeks,
766    /// Months
767    Months,
768    /// Years
769    Years,
770}
771
772impl fmt::Display for DurationUnit {
773    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
774        match self {
775            Self::Days => write!(f, "days"),
776            Self::Weeks => write!(f, "weeks"),
777            Self::Months => write!(f, "months"),
778            Self::Years => write!(f, "years"),
779        }
780    }
781}
782
783/// Three-valued logic result with uncertainty propagation.
784///
785/// Represents the result of partial evaluation where some data may be unknown.
786/// Each value includes a confidence score (0.0 to 1.0) representing certainty.
787///
788/// # Uncertainty Propagation
789///
790/// - **AND**: Confidence is minimum of operands; False propagates immediately
791/// - **OR**: Confidence is minimum of operands; True propagates immediately
792/// - **NOT**: Confidence is preserved; value is inverted
793///
794/// # Examples
795///
796/// ```
797/// # use legalis_core::PartialBool;
798/// let definite_true = PartialBool::true_with_confidence(1.0);
799/// let uncertain = PartialBool::unknown(0.5, "missing data");
800/// let definite_false = PartialBool::false_with_confidence(1.0);
801///
802/// assert!(matches!(definite_true, PartialBool::True { confidence, .. } if confidence == 1.0));
803/// assert!(matches!(uncertain, PartialBool::Unknown { confidence, .. } if confidence == 0.5));
804/// ```
805#[derive(Debug, Clone, PartialEq)]
806#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
807#[cfg_attr(feature = "schema", derive(JsonSchema))]
808pub enum PartialBool {
809    /// Definitely true with confidence score.
810    True {
811        /// Confidence in this result (0.0 to 1.0).
812        confidence: f64,
813        /// Optional reason or explanation.
814        reason: String,
815    },
816    /// Definitely false with confidence score.
817    False {
818        /// Confidence in this result (0.0 to 1.0).
819        confidence: f64,
820        /// Optional reason or explanation.
821        reason: String,
822    },
823    /// Unknown (insufficient data) with confidence score.
824    Unknown {
825        /// Confidence in knowing it's unknown (0.0 to 1.0).
826        confidence: f64,
827        /// Reason why value is unknown.
828        reason: String,
829    },
830}
831
832impl PartialBool {
833    /// Creates a True value with the given confidence.
834    #[must_use]
835    pub fn true_with_confidence(confidence: f64) -> Self {
836        Self::True {
837            confidence,
838            reason: String::new(),
839        }
840    }
841
842    /// Creates a True value with confidence and reason.
843    #[must_use]
844    pub fn true_with_confidence_and_reason(confidence: f64, reason: &str) -> Self {
845        Self::True {
846            confidence,
847            reason: reason.to_string(),
848        }
849    }
850
851    /// Creates a False value with the given confidence.
852    #[must_use]
853    pub fn false_with_confidence(confidence: f64) -> Self {
854        Self::False {
855            confidence,
856            reason: String::new(),
857        }
858    }
859
860    /// Creates a False value with confidence and reason.
861    #[must_use]
862    pub fn false_with_confidence_and_reason(confidence: f64, reason: &str) -> Self {
863        Self::False {
864            confidence,
865            reason: reason.to_string(),
866        }
867    }
868
869    /// Creates an Unknown value with confidence and reason.
870    #[must_use]
871    pub fn unknown(confidence: f64, reason: &str) -> Self {
872        Self::Unknown {
873            confidence,
874            reason: reason.to_string(),
875        }
876    }
877
878    /// Returns the confidence score.
879    #[must_use]
880    pub fn confidence(&self) -> f64 {
881        match self {
882            Self::True { confidence, .. }
883            | Self::False { confidence, .. }
884            | Self::Unknown { confidence, .. } => *confidence,
885        }
886    }
887
888    /// Returns the reason or explanation.
889    #[must_use]
890    pub fn reason(&self) -> &str {
891        match self {
892            Self::True { reason, .. }
893            | Self::False { reason, .. }
894            | Self::Unknown { reason, .. } => reason,
895        }
896    }
897
898    /// Checks if the result is definitely true.
899    #[must_use]
900    pub fn is_true(&self) -> bool {
901        matches!(self, Self::True { .. })
902    }
903
904    /// Checks if the result is definitely false.
905    #[must_use]
906    pub fn is_false(&self) -> bool {
907        matches!(self, Self::False { .. })
908    }
909
910    /// Checks if the result is unknown.
911    #[must_use]
912    pub fn is_unknown(&self) -> bool {
913        matches!(self, Self::Unknown { .. })
914    }
915}
916
917impl fmt::Display for PartialBool {
918    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
919        match self {
920            Self::True { confidence, reason } => {
921                if reason.is_empty() {
922                    write!(f, "True (confidence: {:.2})", confidence)
923                } else {
924                    write!(
925                        f,
926                        "True (confidence: {:.2}, reason: {})",
927                        confidence, reason
928                    )
929                }
930            }
931            Self::False { confidence, reason } => {
932                if reason.is_empty() {
933                    write!(f, "False (confidence: {:.2})", confidence)
934                } else {
935                    write!(
936                        f,
937                        "False (confidence: {:.2}, reason: {})",
938                        confidence, reason
939                    )
940                }
941            }
942            Self::Unknown { confidence, reason } => {
943                write!(
944                    f,
945                    "Unknown (confidence: {:.2}, reason: {})",
946                    confidence, reason
947                )
948            }
949        }
950    }
951}
952
953/// Detailed explanation of a condition evaluation.
954///
955/// Contains the evaluation result, the condition evaluated, and a trace
956/// of all sub-evaluations that led to the final result.
957///
958/// # Examples
959///
960/// ```
961/// # use legalis_core::{Condition, ComparisonOp, AttributeBasedContext, EvaluationExplanation};
962/// # use std::collections::HashMap;
963/// let mut attributes = HashMap::new();
964/// attributes.insert("age".to_string(), "25".to_string());
965/// attributes.insert("income".to_string(), "50000".to_string());
966/// let ctx = AttributeBasedContext::new(attributes);
967///
968/// let age_check = Condition::age(ComparisonOp::GreaterOrEqual, 18);
969/// let income_check = Condition::income(ComparisonOp::GreaterOrEqual, 30000);
970/// let condition = age_check.and(income_check);
971///
972/// let (result, explanation) = condition.evaluate_with_explanation(&ctx).unwrap();
973/// assert!(result);
974/// println!("Explanation:\n{}", explanation);
975/// ```
976#[derive(Debug, Clone)]
977#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
978pub struct EvaluationExplanation {
979    /// The condition that was evaluated (formatted as string).
980    pub condition: String,
981    /// The final evaluation result.
982    pub conclusion: bool,
983    /// Step-by-step trace of the evaluation.
984    pub steps: Vec<ExplanationStep>,
985}
986
987impl fmt::Display for EvaluationExplanation {
988    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
989        writeln!(f, "Evaluation of: {}", self.condition)?;
990        writeln!(f, "Result: {}", self.conclusion)?;
991        writeln!(f, "\nEvaluation trace:")?;
992        for (i, step) in self.steps.iter().enumerate() {
993            let indent = "  ".repeat(step.depth);
994            writeln!(
995                f,
996                "{}{}. {} -> {} ({}μs)",
997                indent,
998                i + 1,
999                step.condition,
1000                step.result,
1001                step.duration_micros
1002            )?;
1003            if !step.details.is_empty() {
1004                writeln!(f, "{}   Details: {}", indent, step.details)?;
1005            }
1006        }
1007        Ok(())
1008    }
1009}
1010
1011/// A single step in the evaluation trace.
1012///
1013/// Records one condition evaluation including timing and context.
1014#[derive(Debug, Clone)]
1015#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1016pub struct ExplanationStep {
1017    /// The condition being evaluated (formatted as string).
1018    pub condition: String,
1019    /// The result of this evaluation step.
1020    pub result: bool,
1021    /// Additional details about how the result was determined.
1022    pub details: String,
1023    /// Nesting depth (for compound conditions).
1024    pub depth: usize,
1025    /// Time taken for this evaluation step (in microseconds).
1026    pub duration_micros: u64,
1027}
1028
1029/// Condition type for statute preconditions.
1030///
1031/// Conditions represent the requirements that must be met for a statute to apply.
1032/// They can be simple (age checks, attribute checks) or complex (combinations using AND/OR/NOT).
1033///
1034/// # Examples
1035///
1036/// ## Simple Condition
1037///
1038/// ```
1039/// use legalis_core::{Condition, ComparisonOp};
1040///
1041/// let age_check = Condition::Age {
1042///     operator: ComparisonOp::GreaterOrEqual,
1043///     value: 18,
1044/// };
1045///
1046/// assert_eq!(format!("{}", age_check), "age >= 18");
1047/// ```
1048///
1049/// ## Complex Condition
1050///
1051/// ```
1052/// use legalis_core::{Condition, ComparisonOp};
1053///
1054/// let age_check = Condition::Age {
1055///     operator: ComparisonOp::GreaterOrEqual,
1056///     value: 65,
1057/// };
1058/// let income_check = Condition::Income {
1059///     operator: ComparisonOp::LessThan,
1060///     value: 30000,
1061/// };
1062/// let eligibility = Condition::And(
1063///     Box::new(age_check),
1064///     Box::new(income_check),
1065/// );
1066///
1067/// assert!(format!("{}", eligibility).contains("AND"));
1068/// ```
1069#[derive(Debug, Clone, PartialEq)]
1070#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1071#[cfg_attr(feature = "schema", derive(JsonSchema))]
1072pub enum Condition {
1073    /// Age comparison (e.g., age >= 18)
1074    Age { operator: ComparisonOp, value: u32 },
1075    /// Income comparison
1076    Income { operator: ComparisonOp, value: u64 },
1077    /// Attribute existence check
1078    HasAttribute { key: String },
1079    /// Attribute value check
1080    AttributeEquals { key: String, value: String },
1081    /// Date range check (effective within date range)
1082    DateRange {
1083        start: Option<NaiveDate>,
1084        end: Option<NaiveDate>,
1085    },
1086    /// Geographic region check
1087    Geographic {
1088        region_type: RegionType,
1089        region_id: String,
1090    },
1091    /// Entity relationship check
1092    EntityRelationship {
1093        relationship_type: RelationshipType,
1094        target_entity_id: Option<String>,
1095    },
1096    /// Residency duration check
1097    ResidencyDuration { operator: ComparisonOp, months: u32 },
1098    /// Duration check (time periods, e.g., employment duration >= 5 years)
1099    Duration {
1100        operator: ComparisonOp,
1101        value: u32,
1102        unit: DurationUnit,
1103    },
1104    /// Percentage check (e.g., ownership >= 25%)
1105    Percentage {
1106        operator: ComparisonOp,
1107        value: u32,
1108        context: String,
1109    },
1110    /// Set membership check (e.g., status in {active, pending})
1111    SetMembership {
1112        attribute: String,
1113        values: Vec<String>,
1114        negated: bool,
1115    },
1116    /// Pattern matching check (regex for identifiers, codes, etc.)
1117    Pattern {
1118        attribute: String,
1119        pattern: String,
1120        negated: bool,
1121    },
1122    /// Calculation check (derived values, formulas)
1123    /// Example: `tax_owed = income * 0.2` where operator compares tax_owed
1124    Calculation {
1125        formula: String,
1126        operator: ComparisonOp,
1127        value: f64,
1128    },
1129    /// Composite condition - combines multiple conditions with weighted scoring
1130    /// Useful for complex eligibility where multiple factors contribute to a decision
1131    Composite {
1132        /// List of weighted conditions (weight, condition)
1133        /// Weights should be positive, typically 0.0-1.0 but not enforced
1134        conditions: Vec<(f64, Box<Condition>)>,
1135        /// Minimum total score required (sum of weights for satisfied conditions)
1136        threshold: f64,
1137    },
1138    /// Threshold condition - aggregate scoring across multiple numeric attributes
1139    /// Example: Combined income/asset test where total must exceed threshold
1140    Threshold {
1141        /// Attributes to sum (with optional multipliers)
1142        attributes: Vec<(String, f64)>,
1143        /// Comparison operator
1144        operator: ComparisonOp,
1145        /// Threshold value
1146        value: f64,
1147    },
1148    /// Fuzzy logic condition - membership in fuzzy set
1149    /// Supports gradual transitions between true/false
1150    Fuzzy {
1151        /// Attribute to evaluate
1152        attribute: String,
1153        /// Fuzzy set definition (value -> membership degree 0.0-1.0)
1154        /// For simplicity, uses linear interpolation between points
1155        membership_points: Vec<(f64, f64)>,
1156        /// Minimum membership degree required (0.0-1.0)
1157        min_membership: f64,
1158    },
1159    /// Probabilistic condition - probability-based evaluation
1160    /// Useful for modeling uncertain conditions or risk assessment
1161    Probabilistic {
1162        /// Base condition to evaluate
1163        condition: Box<Condition>,
1164        /// Probability that this condition is relevant (0.0-1.0)
1165        /// If p < 1.0, condition might be randomly evaluated as uncertain
1166        probability: f64,
1167        /// Minimum probability to consider condition satisfied
1168        threshold: f64,
1169    },
1170    /// Temporal condition - time-sensitive condition with decay/growth
1171    /// Value changes over time according to a decay or growth function
1172    Temporal {
1173        /// Base value at reference time
1174        base_value: f64,
1175        /// Reference timestamp (when base_value applies)
1176        reference_time: i64,
1177        /// Decay/growth rate per time unit (negative for decay, positive for growth)
1178        /// Applied as: value = base_value * (1 + rate)^time_elapsed
1179        rate: f64,
1180        /// Comparison operator
1181        operator: ComparisonOp,
1182        /// Target value to compare against
1183        target_value: f64,
1184    },
1185    /// Logical AND of conditions
1186    And(Box<Condition>, Box<Condition>),
1187    /// Logical OR of conditions
1188    Or(Box<Condition>, Box<Condition>),
1189    /// Logical NOT
1190    Not(Box<Condition>),
1191    /// Custom condition with description
1192    Custom { description: String },
1193}
1194
1195impl Condition {
1196    /// Returns true if this is a compound condition (AND/OR/NOT).
1197    #[must_use]
1198    pub const fn is_compound(&self) -> bool {
1199        matches!(self, Self::And(..) | Self::Or(..) | Self::Not(..))
1200    }
1201
1202    /// Returns true if this is a simple (non-compound) condition.
1203    #[must_use]
1204    pub const fn is_simple(&self) -> bool {
1205        !self.is_compound()
1206    }
1207
1208    /// Returns true if this is a logical negation.
1209    #[must_use]
1210    pub const fn is_negation(&self) -> bool {
1211        matches!(self, Self::Not(..))
1212    }
1213
1214    /// Counts the total number of conditions (including nested ones).
1215    #[must_use]
1216    pub fn count_conditions(&self) -> usize {
1217        match self {
1218            Self::And(left, right) | Self::Or(left, right) => {
1219                1 + left.count_conditions() + right.count_conditions()
1220            }
1221            Self::Not(inner) => 1 + inner.count_conditions(),
1222            Self::Composite { conditions, .. } => {
1223                1 + conditions
1224                    .iter()
1225                    .map(|(_, c)| c.count_conditions())
1226                    .sum::<usize>()
1227            }
1228            Self::Probabilistic { condition, .. } => 1 + condition.count_conditions(),
1229            _ => 1,
1230        }
1231    }
1232
1233    /// Returns the depth of nested conditions.
1234    #[must_use]
1235    pub fn depth(&self) -> usize {
1236        match self {
1237            Self::And(left, right) | Self::Or(left, right) => 1 + left.depth().max(right.depth()),
1238            Self::Not(inner) => 1 + inner.depth(),
1239            Self::Composite { conditions, .. } => {
1240                1 + conditions.iter().map(|(_, c)| c.depth()).max().unwrap_or(0)
1241            }
1242            Self::Probabilistic { condition, .. } => 1 + condition.depth(),
1243            _ => 1,
1244        }
1245    }
1246
1247    /// Creates a new Age condition.
1248    pub fn age(operator: ComparisonOp, value: u32) -> Self {
1249        Self::Age { operator, value }
1250    }
1251
1252    /// Creates a new Income condition.
1253    pub fn income(operator: ComparisonOp, value: u64) -> Self {
1254        Self::Income { operator, value }
1255    }
1256
1257    /// Creates a new HasAttribute condition.
1258    pub fn has_attribute(key: impl Into<String>) -> Self {
1259        Self::HasAttribute { key: key.into() }
1260    }
1261
1262    /// Creates a new AttributeEquals condition.
1263    pub fn attribute_equals(key: impl Into<String>, value: impl Into<String>) -> Self {
1264        Self::AttributeEquals {
1265            key: key.into(),
1266            value: value.into(),
1267        }
1268    }
1269
1270    /// Creates a new Custom condition.
1271    pub fn custom(description: impl Into<String>) -> Self {
1272        Self::Custom {
1273            description: description.into(),
1274        }
1275    }
1276
1277    /// Creates a new Duration condition.
1278    pub fn duration(operator: ComparisonOp, value: u32, unit: DurationUnit) -> Self {
1279        Self::Duration {
1280            operator,
1281            value,
1282            unit,
1283        }
1284    }
1285
1286    /// Creates a new Percentage condition.
1287    pub fn percentage(operator: ComparisonOp, value: u32, context: impl Into<String>) -> Self {
1288        Self::Percentage {
1289            operator,
1290            value,
1291            context: context.into(),
1292        }
1293    }
1294
1295    /// Creates a new SetMembership condition (attribute must be in set).
1296    pub fn in_set(attribute: impl Into<String>, values: Vec<String>) -> Self {
1297        Self::SetMembership {
1298            attribute: attribute.into(),
1299            values,
1300            negated: false,
1301        }
1302    }
1303
1304    /// Creates a new SetMembership condition (attribute must NOT be in set).
1305    pub fn not_in_set(attribute: impl Into<String>, values: Vec<String>) -> Self {
1306        Self::SetMembership {
1307            attribute: attribute.into(),
1308            values,
1309            negated: true,
1310        }
1311    }
1312
1313    /// Creates a new Pattern condition (attribute matches regex).
1314    pub fn matches_pattern(attribute: impl Into<String>, pattern: impl Into<String>) -> Self {
1315        Self::Pattern {
1316            attribute: attribute.into(),
1317            pattern: pattern.into(),
1318            negated: false,
1319        }
1320    }
1321
1322    /// Creates a new Pattern condition (attribute does NOT match regex).
1323    pub fn not_matches_pattern(attribute: impl Into<String>, pattern: impl Into<String>) -> Self {
1324        Self::Pattern {
1325            attribute: attribute.into(),
1326            pattern: pattern.into(),
1327            negated: true,
1328        }
1329    }
1330
1331    /// Creates a new Calculation condition (formula-based check).
1332    ///
1333    /// # Examples
1334    /// ```
1335    /// use legalis_core::{Condition, ComparisonOp};
1336    ///
1337    /// let tax_check = Condition::calculation("income * 0.2", ComparisonOp::GreaterThan, 5000.0);
1338    /// ```
1339    pub fn calculation(formula: impl Into<String>, operator: ComparisonOp, value: f64) -> Self {
1340        Self::Calculation {
1341            formula: formula.into(),
1342            operator,
1343            value,
1344        }
1345    }
1346
1347    /// Creates a new Composite condition with weighted sub-conditions.
1348    ///
1349    /// # Arguments
1350    /// * `conditions` - Vector of (weight, condition) pairs
1351    /// * `threshold` - Minimum total score required
1352    ///
1353    /// # Example
1354    /// ```
1355    /// # use legalis_core::{Condition, ComparisonOp};
1356    /// let cond = Condition::composite(
1357    ///     vec![
1358    ///         (0.5, Box::new(Condition::age(ComparisonOp::GreaterOrEqual, 18))),
1359    ///         (0.3, Box::new(Condition::income(ComparisonOp::GreaterOrEqual, 30000))),
1360    ///     ],
1361    ///     0.6
1362    /// );
1363    /// ```
1364    pub fn composite(conditions: Vec<(f64, Box<Condition>)>, threshold: f64) -> Self {
1365        Self::Composite {
1366            conditions,
1367            threshold,
1368        }
1369    }
1370
1371    /// Creates a new Threshold condition for aggregate scoring.
1372    ///
1373    /// # Arguments
1374    /// * `attributes` - Vector of (attribute_name, multiplier) pairs
1375    /// * `operator` - Comparison operator
1376    /// * `value` - Threshold value
1377    ///
1378    /// # Example
1379    /// ```
1380    /// # use legalis_core::{Condition, ComparisonOp};
1381    /// // Total assets (income + 10*savings) must be >= 50000
1382    /// let cond = Condition::threshold(
1383    ///     vec![("income".to_string(), 1.0), ("savings".to_string(), 10.0)],
1384    ///     ComparisonOp::GreaterOrEqual,
1385    ///     50000.0
1386    /// );
1387    /// ```
1388    pub fn threshold(attributes: Vec<(String, f64)>, operator: ComparisonOp, value: f64) -> Self {
1389        Self::Threshold {
1390            attributes,
1391            operator,
1392            value,
1393        }
1394    }
1395
1396    /// Creates a new Fuzzy condition for gradual membership.
1397    ///
1398    /// # Arguments
1399    /// * `attribute` - Attribute to evaluate
1400    /// * `membership_points` - Vector of (value, membership_degree) pairs for linear interpolation
1401    /// * `min_membership` - Minimum membership degree required (0.0-1.0)
1402    ///
1403    /// # Example
1404    /// ```
1405    /// # use legalis_core::Condition;
1406    /// // Age is "young" with fuzzy membership
1407    /// let cond = Condition::fuzzy(
1408    ///     "age".to_string(),
1409    ///     vec![(0.0, 1.0), (25.0, 0.5), (50.0, 0.0)],
1410    ///     0.5
1411    /// );
1412    /// ```
1413    pub fn fuzzy(
1414        attribute: String,
1415        membership_points: Vec<(f64, f64)>,
1416        min_membership: f64,
1417    ) -> Self {
1418        Self::Fuzzy {
1419            attribute,
1420            membership_points,
1421            min_membership,
1422        }
1423    }
1424
1425    /// Creates a new Probabilistic condition.
1426    ///
1427    /// # Arguments
1428    /// * `condition` - Base condition to evaluate
1429    /// * `probability` - Probability that this condition is relevant (0.0-1.0)
1430    /// * `threshold` - Minimum probability to consider satisfied
1431    ///
1432    /// # Example
1433    /// ```
1434    /// # use legalis_core::{Condition, ComparisonOp};
1435    /// // 80% chance that age >= 18 is relevant
1436    /// let cond = Condition::probabilistic(
1437    ///     Box::new(Condition::age(ComparisonOp::GreaterOrEqual, 18)),
1438    ///     0.8,
1439    ///     0.5
1440    /// );
1441    /// ```
1442    pub fn probabilistic(condition: Box<Condition>, probability: f64, threshold: f64) -> Self {
1443        Self::Probabilistic {
1444            condition,
1445            probability,
1446            threshold,
1447        }
1448    }
1449
1450    /// Creates a new Temporal condition with decay/growth over time.
1451    ///
1452    /// # Arguments
1453    /// * `base_value` - Value at reference time
1454    /// * `reference_time` - Reference timestamp
1455    /// * `rate` - Decay/growth rate (negative for decay, positive for growth)
1456    /// * `operator` - Comparison operator
1457    /// * `target_value` - Target value to compare against
1458    ///
1459    /// # Example
1460    /// ```
1461    /// # use legalis_core::{Condition, ComparisonOp};
1462    /// // Asset value decays 5% per year, must stay above 10000
1463    /// let cond = Condition::temporal(
1464    ///     100000.0,
1465    ///     1609459200, // Jan 1, 2021
1466    ///     -0.05,
1467    ///     ComparisonOp::GreaterOrEqual,
1468    ///     10000.0
1469    /// );
1470    /// ```
1471    pub fn temporal(
1472        base_value: f64,
1473        reference_time: i64,
1474        rate: f64,
1475        operator: ComparisonOp,
1476        target_value: f64,
1477    ) -> Self {
1478        Self::Temporal {
1479            base_value,
1480            reference_time,
1481            rate,
1482            operator,
1483            target_value,
1484        }
1485    }
1486
1487    /// Combines this condition with another using AND.
1488    pub fn and(self, other: Condition) -> Self {
1489        Self::And(Box::new(self), Box::new(other))
1490    }
1491
1492    /// Combines this condition with another using OR.
1493    pub fn or(self, other: Condition) -> Self {
1494        Self::Or(Box::new(self), Box::new(other))
1495    }
1496
1497    /// Negates this condition.
1498    #[allow(clippy::should_implement_trait)]
1499    pub fn not(self) -> Self {
1500        Self::Not(Box::new(self))
1501    }
1502
1503    /// Normalizes this condition by applying logical simplifications.
1504    ///
1505    /// This method optimizes conditions by:
1506    /// - Removing double negations: `NOT (NOT A)` → `A`
1507    /// - Applying De Morgan's laws: `NOT (A AND B)` → `(NOT A) OR (NOT B)`
1508    /// - Recursively normalizing sub-conditions
1509    ///
1510    /// # Examples
1511    ///
1512    /// ```
1513    /// use legalis_core::{Condition, ComparisonOp};
1514    ///
1515    /// // Double negation elimination
1516    /// let condition = Condition::age(ComparisonOp::GreaterOrEqual, 18).not().not();
1517    /// let normalized = condition.normalize();
1518    /// // normalized is equivalent to: age >= 18
1519    /// ```
1520    #[must_use]
1521    pub fn normalize(self) -> Self {
1522        match self {
1523            // Double negation elimination: NOT (NOT A) → A
1524            Self::Not(inner) => match *inner {
1525                Self::Not(double_inner) => double_inner.normalize(),
1526                // De Morgan's laws
1527                Self::And(left, right) => {
1528                    // NOT (A AND B) → (NOT A) OR (NOT B)
1529                    Self::Or(
1530                        Box::new(Self::Not(left).normalize()),
1531                        Box::new(Self::Not(right).normalize()),
1532                    )
1533                }
1534                Self::Or(left, right) => {
1535                    // NOT (A OR B) → (NOT A) AND (NOT B)
1536                    Self::And(
1537                        Box::new(Self::Not(left).normalize()),
1538                        Box::new(Self::Not(right).normalize()),
1539                    )
1540                }
1541                other => Self::Not(Box::new(other.normalize())),
1542            },
1543            // Recursively normalize compound conditions
1544            Self::And(left, right) => {
1545                Self::And(Box::new(left.normalize()), Box::new(right.normalize()))
1546            }
1547            Self::Or(left, right) => {
1548                Self::Or(Box::new(left.normalize()), Box::new(right.normalize()))
1549            }
1550            // Simple conditions are already normalized
1551            other => other,
1552        }
1553    }
1554
1555    /// Checks if this condition is in normalized form.
1556    #[must_use]
1557    pub fn is_normalized(&self) -> bool {
1558        match self {
1559            // Check for double negation
1560            Self::Not(inner) => !matches!(**inner, Self::Not(_)) && inner.is_normalized(),
1561            Self::And(left, right) | Self::Or(left, right) => {
1562                left.is_normalized() && right.is_normalized()
1563            }
1564            _ => true,
1565        }
1566    }
1567
1568    /// Evaluates this condition with lazy evaluation and short-circuit logic.
1569    ///
1570    /// This method implements:
1571    /// - **Short-circuit AND**: Returns false as soon as any condition is false
1572    /// - **Short-circuit OR**: Returns true as soon as any condition is true
1573    /// - **Maximum depth protection**: Prevents stack overflow from deeply nested conditions
1574    ///
1575    /// # Arguments
1576    ///
1577    /// * `ctx` - Evaluation context containing entity data and settings
1578    ///
1579    /// # Errors
1580    ///
1581    /// Returns [`ConditionError`] if:
1582    /// - Required attributes are missing
1583    /// - Type mismatches occur
1584    /// - Formula evaluation fails
1585    /// - Maximum evaluation depth is exceeded
1586    ///
1587    /// # Examples
1588    ///
1589    /// ```
1590    /// use legalis_core::{Condition, ComparisonOp, AttributeBasedContext};
1591    /// use std::collections::HashMap;
1592    ///
1593    /// let mut attrs = HashMap::new();
1594    /// attrs.insert("age".to_string(), "25".to_string());
1595    /// attrs.insert("income".to_string(), "45000".to_string());
1596    ///
1597    /// let ctx = AttributeBasedContext::new(attrs);
1598    /// let condition = Condition::age(ComparisonOp::GreaterOrEqual, 18)
1599    ///     .and(Condition::income(ComparisonOp::LessThan, 50000));
1600    ///
1601    /// assert_eq!(condition.evaluate_simple(&ctx).unwrap(), true);
1602    /// ```
1603    pub fn evaluate_simple(&self, ctx: &AttributeBasedContext) -> Result<bool, ConditionError> {
1604        self.evaluate_simple_with_depth(ctx, 0)
1605    }
1606
1607    /// Internal evaluation with depth tracking.
1608    fn evaluate_simple_with_depth(
1609        &self,
1610        ctx: &AttributeBasedContext,
1611        depth: usize,
1612    ) -> Result<bool, ConditionError> {
1613        // Protect against stack overflow from deeply nested conditions
1614        if depth > ctx.max_depth {
1615            return Err(ConditionError::MaxDepthExceeded {
1616                max_depth: ctx.max_depth,
1617            });
1618        }
1619
1620        match self {
1621            // Lazy evaluation with short-circuit for AND
1622            Self::And(left, right) => {
1623                let left_result = left.evaluate_simple_with_depth(ctx, depth + 1)?;
1624                if !left_result {
1625                    // Short-circuit: if left is false, return false immediately
1626                    return Ok(false);
1627                }
1628                // Only evaluate right if left is true
1629                right.evaluate_simple_with_depth(ctx, depth + 1)
1630            }
1631            // Lazy evaluation with short-circuit for OR
1632            Self::Or(left, right) => {
1633                let left_result = left.evaluate_simple_with_depth(ctx, depth + 1)?;
1634                if left_result {
1635                    // Short-circuit: if left is true, return true immediately
1636                    return Ok(true);
1637                }
1638                // Only evaluate right if left is false
1639                right.evaluate_simple_with_depth(ctx, depth + 1)
1640            }
1641            Self::Not(inner) => {
1642                let result = inner.evaluate_simple_with_depth(ctx, depth + 1)?;
1643                Ok(!result)
1644            }
1645            Self::Age { operator, value } => {
1646                let age_str =
1647                    ctx.attributes
1648                        .get("age")
1649                        .ok_or_else(|| ConditionError::MissingAttribute {
1650                            key: "age".to_string(),
1651                        })?;
1652                let age: u32 = age_str.parse().map_err(|_| ConditionError::TypeMismatch {
1653                    expected: "u32".to_string(),
1654                    actual: age_str.clone(),
1655                })?;
1656                Ok(operator.compare_u32(age, *value))
1657            }
1658            Self::Income { operator, value } => {
1659                let income_str = ctx.attributes.get("income").ok_or_else(|| {
1660                    ConditionError::MissingAttribute {
1661                        key: "income".to_string(),
1662                    }
1663                })?;
1664                let income: u64 = income_str
1665                    .parse()
1666                    .map_err(|_| ConditionError::TypeMismatch {
1667                        expected: "u64".to_string(),
1668                        actual: income_str.clone(),
1669                    })?;
1670                Ok(operator.compare_u64(income, *value))
1671            }
1672            Self::HasAttribute { key } => Ok(ctx.attributes.contains_key(key)),
1673            Self::AttributeEquals { key, value } => Ok(ctx.attributes.get(key) == Some(value)),
1674            Self::Calculation {
1675                formula,
1676                operator,
1677                value,
1678            } => {
1679                // Simple formula evaluation (can be extended with a proper expression parser)
1680                let result = Self::evaluate_formula(formula, ctx)?;
1681                Ok(operator.compare_f64(result, *value))
1682            }
1683            Self::Pattern {
1684                attribute,
1685                pattern,
1686                negated,
1687            } => {
1688                let attr_value = ctx.attributes.get(attribute).ok_or_else(|| {
1689                    ConditionError::MissingAttribute {
1690                        key: attribute.clone(),
1691                    }
1692                })?;
1693                // Simple substring matching (can be extended with regex crate if needed)
1694                let matches = attr_value.contains(pattern);
1695                Ok(if *negated { !matches } else { matches })
1696            }
1697            Self::ResidencyDuration { operator, months } => {
1698                let residency_str = ctx.attributes.get("residency_months").ok_or_else(|| {
1699                    ConditionError::MissingAttribute {
1700                        key: "residency_months".to_string(),
1701                    }
1702                })?;
1703                let residency: u32 =
1704                    residency_str
1705                        .parse()
1706                        .map_err(|_| ConditionError::TypeMismatch {
1707                            expected: "u32".to_string(),
1708                            actual: residency_str.clone(),
1709                        })?;
1710                Ok(operator.compare_u32(residency, *months))
1711            }
1712            // For other conditions, return Ok(true) as a placeholder
1713            // (implementation depends on specific evaluation logic)
1714            _ => Ok(true),
1715        }
1716    }
1717
1718    /// Evaluates a simple formula.
1719    /// This is a basic implementation - can be extended with a proper expression parser.
1720    #[allow(dead_code)]
1721    fn evaluate_formula(
1722        formula: &str,
1723        _ctx: &AttributeBasedContext,
1724    ) -> Result<f64, ConditionError> {
1725        // Simple implementation: support basic arithmetic with attributes
1726        // For production use, consider using a proper expression parser like `meval` or `evalexpr`
1727
1728        // For now, just return an error indicating formula evaluation needs implementation
1729        Err(ConditionError::InvalidFormula {
1730            formula: formula.to_string(),
1731            error: "Formula evaluation not yet implemented - consider using 'meval' or 'evalexpr' crate".to_string(),
1732        })
1733    }
1734
1735    /// Evaluates this condition using the `EvaluationContext` trait.
1736    ///
1737    /// This is the trait-based evaluation method that allows custom context implementations.
1738    /// For a simpler attribute-based approach, see [`evaluate_simple`](Self::evaluate_simple).
1739    ///
1740    /// # Examples
1741    ///
1742    /// ```
1743    /// use legalis_core::{Condition, ComparisonOp, EvaluationContext, RegionType, RelationshipType, DurationUnit};
1744    /// use chrono::NaiveDate;
1745    ///
1746    /// struct MyContext {
1747    ///     age: u32,
1748    ///     income: u64,
1749    /// }
1750    ///
1751    /// impl EvaluationContext for MyContext {
1752    ///     fn get_attribute(&self, _key: &str) -> Option<String> { None }
1753    ///     fn get_age(&self) -> Option<u32> { Some(self.age) }
1754    ///     fn get_income(&self) -> Option<u64> { Some(self.income) }
1755    ///     fn get_current_date(&self) -> Option<NaiveDate> { None }
1756    ///     fn check_geographic(&self, _region_type: RegionType, _region_id: &str) -> bool { false }
1757    ///     fn check_relationship(&self, _relationship_type: RelationshipType, _target_id: Option<&str>) -> bool { false }
1758    ///     fn get_residency_months(&self) -> Option<u32> { None }
1759    ///     fn get_duration(&self, _unit: DurationUnit) -> Option<u32> { None }
1760    ///     fn get_percentage(&self, _context: &str) -> Option<u32> { None }
1761    ///     fn evaluate_formula(&self, _formula: &str) -> Option<f64> { None }
1762    /// }
1763    ///
1764    /// let ctx = MyContext { age: 25, income: 45000 };
1765    /// let condition = Condition::age(ComparisonOp::GreaterOrEqual, 18)
1766    ///     .and(Condition::income(ComparisonOp::LessThan, 50000));
1767    ///
1768    /// assert_eq!(condition.evaluate(&ctx).unwrap(), true);
1769    /// ```
1770    pub fn evaluate<C: EvaluationContext>(&self, context: &C) -> Result<bool, EvaluationError> {
1771        self.evaluate_with_depth(context, 0)
1772    }
1773
1774    /// Internal evaluation with depth tracking using the `EvaluationContext` trait.
1775    fn evaluate_with_depth<C: EvaluationContext>(
1776        &self,
1777        context: &C,
1778        depth: usize,
1779    ) -> Result<bool, EvaluationError> {
1780        const MAX_DEPTH: usize = 100;
1781
1782        // Protect against stack overflow from deeply nested conditions
1783        if depth > MAX_DEPTH {
1784            return Err(EvaluationError::MaxDepthExceeded {
1785                max_depth: MAX_DEPTH,
1786            });
1787        }
1788
1789        match self {
1790            // Lazy evaluation with short-circuit for AND
1791            Self::And(left, right) => {
1792                let left_result = left.evaluate_with_depth(context, depth + 1)?;
1793                if !left_result {
1794                    return Ok(false);
1795                }
1796                right.evaluate_with_depth(context, depth + 1)
1797            }
1798            // Lazy evaluation with short-circuit for OR
1799            Self::Or(left, right) => {
1800                let left_result = left.evaluate_with_depth(context, depth + 1)?;
1801                if left_result {
1802                    return Ok(true);
1803                }
1804                right.evaluate_with_depth(context, depth + 1)
1805            }
1806            Self::Not(inner) => {
1807                let result = inner.evaluate_with_depth(context, depth + 1)?;
1808                Ok(!result)
1809            }
1810            Self::Age { operator, value } => {
1811                let age = context
1812                    .get_age()
1813                    .ok_or_else(|| EvaluationError::MissingAttribute {
1814                        key: "age".to_string(),
1815                    })?;
1816                Ok(operator.compare_u32(age, *value))
1817            }
1818            Self::Income { operator, value } => {
1819                let income =
1820                    context
1821                        .get_income()
1822                        .ok_or_else(|| EvaluationError::MissingAttribute {
1823                            key: "income".to_string(),
1824                        })?;
1825                Ok(operator.compare_u64(income, *value))
1826            }
1827            Self::HasAttribute { key } => Ok(context.get_attribute(key).is_some()),
1828            Self::AttributeEquals { key, value } => {
1829                Ok(context.get_attribute(key).as_ref() == Some(value))
1830            }
1831            Self::Geographic {
1832                region_type,
1833                region_id,
1834            } => Ok(context.check_geographic(*region_type, region_id)),
1835            Self::EntityRelationship {
1836                relationship_type,
1837                target_entity_id,
1838            } => Ok(context.check_relationship(*relationship_type, target_entity_id.as_deref())),
1839            Self::ResidencyDuration { operator, months } => {
1840                let residency = context.get_residency_months().ok_or_else(|| {
1841                    EvaluationError::MissingContext {
1842                        description: "residency months".to_string(),
1843                    }
1844                })?;
1845                Ok(operator.compare_u32(residency, *months))
1846            }
1847            Self::Duration {
1848                operator,
1849                value,
1850                unit,
1851            } => {
1852                let duration =
1853                    context
1854                        .get_duration(*unit)
1855                        .ok_or_else(|| EvaluationError::MissingContext {
1856                            description: format!("duration for unit {:?}", unit),
1857                        })?;
1858                Ok(operator.compare_u32(duration, *value))
1859            }
1860            Self::Percentage {
1861                operator,
1862                value,
1863                context: pct_context,
1864            } => {
1865                let percentage = context.get_percentage(pct_context).ok_or_else(|| {
1866                    EvaluationError::MissingContext {
1867                        description: format!("percentage for context '{}'", pct_context),
1868                    }
1869                })?;
1870                Ok(operator.compare_u32(percentage, *value))
1871            }
1872            Self::Calculation {
1873                formula,
1874                operator,
1875                value,
1876            } => {
1877                let result = context.evaluate_formula(formula).ok_or_else(|| {
1878                    EvaluationError::InvalidFormula {
1879                        formula: formula.clone(),
1880                        reason: "Formula evaluation not supported".to_string(),
1881                    }
1882                })?;
1883                Ok(operator.compare_f64(result, *value))
1884            }
1885            Self::Pattern {
1886                attribute,
1887                pattern,
1888                negated,
1889            } => {
1890                let attr_value = context.get_attribute(attribute).ok_or_else(|| {
1891                    EvaluationError::MissingAttribute {
1892                        key: attribute.clone(),
1893                    }
1894                })?;
1895                let matches = attr_value.contains(pattern);
1896                Ok(if *negated { !matches } else { matches })
1897            }
1898            Self::SetMembership {
1899                attribute,
1900                values,
1901                negated,
1902            } => {
1903                let attr_value = context.get_attribute(attribute).ok_or_else(|| {
1904                    EvaluationError::MissingAttribute {
1905                        key: attribute.clone(),
1906                    }
1907                })?;
1908                let is_member = values.contains(&attr_value);
1909                Ok(if *negated { !is_member } else { is_member })
1910            }
1911            Self::DateRange { start, end } => {
1912                let current_date =
1913                    context
1914                        .get_current_date()
1915                        .ok_or_else(|| EvaluationError::MissingContext {
1916                            description: "current date".to_string(),
1917                        })?;
1918                let after_start = start.is_none_or(|s| current_date >= s);
1919                let before_end = end.is_none_or(|e| current_date <= e);
1920                Ok(after_start && before_end)
1921            }
1922            Self::Composite {
1923                conditions,
1924                threshold,
1925            } => {
1926                let mut total_score = 0.0;
1927                for (weight, condition) in conditions {
1928                    let satisfied = condition.evaluate_with_depth(context, depth + 1)?;
1929                    if satisfied {
1930                        total_score += weight;
1931                    }
1932                }
1933                Ok(total_score >= *threshold)
1934            }
1935            Self::Threshold {
1936                attributes,
1937                operator,
1938                value,
1939            } => {
1940                let mut total = 0.0;
1941                for (attr_name, multiplier) in attributes {
1942                    // Try to get attribute as f64
1943                    let attr_value = context
1944                        .get_attribute(attr_name)
1945                        .and_then(|s| s.parse::<f64>().ok())
1946                        .ok_or_else(|| EvaluationError::MissingAttribute {
1947                            key: attr_name.clone(),
1948                        })?;
1949                    total += attr_value * multiplier;
1950                }
1951                Ok(operator.compare_f64(total, *value))
1952            }
1953            Self::Fuzzy {
1954                attribute,
1955                membership_points,
1956                min_membership,
1957            } => {
1958                // Get attribute value
1959                let attr_value = context
1960                    .get_attribute(attribute)
1961                    .and_then(|s| s.parse::<f64>().ok())
1962                    .ok_or_else(|| EvaluationError::MissingAttribute {
1963                        key: attribute.clone(),
1964                    })?;
1965
1966                // Linear interpolation of membership degree
1967                let membership = if membership_points.is_empty() {
1968                    0.0
1969                } else if membership_points.len() == 1 {
1970                    membership_points[0].1
1971                } else {
1972                    // Sort points by value
1973                    let mut sorted = membership_points.clone();
1974                    sorted
1975                        .sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
1976
1977                    // Find interpolation range
1978                    if attr_value <= sorted[0].0 {
1979                        sorted[0].1
1980                    } else if attr_value >= sorted[sorted.len() - 1].0 {
1981                        sorted[sorted.len() - 1].1
1982                    } else {
1983                        // Linear interpolation
1984                        let mut result = 0.0;
1985                        for i in 0..sorted.len() - 1 {
1986                            if attr_value >= sorted[i].0 && attr_value <= sorted[i + 1].0 {
1987                                let (x0, y0) = sorted[i];
1988                                let (x1, y1) = sorted[i + 1];
1989                                let t = (attr_value - x0) / (x1 - x0);
1990                                result = y0 + t * (y1 - y0);
1991                                break;
1992                            }
1993                        }
1994                        result
1995                    }
1996                };
1997
1998                Ok(membership >= *min_membership)
1999            }
2000            Self::Probabilistic {
2001                condition,
2002                probability,
2003                threshold,
2004            } => {
2005                // Evaluate the base condition
2006                let satisfied = condition.evaluate_with_depth(context, depth + 1)?;
2007
2008                // If condition is satisfied, check if probability meets threshold
2009                // If not satisfied, probability is 0
2010                let effective_probability = if satisfied { *probability } else { 0.0 };
2011                Ok(effective_probability >= *threshold)
2012            }
2013            Self::Temporal {
2014                base_value,
2015                reference_time,
2016                rate,
2017                operator,
2018                target_value,
2019            } => {
2020                // Get current timestamp from context
2021                let current_time = context.get_current_timestamp().ok_or_else(|| {
2022                    EvaluationError::MissingContext {
2023                        description: "current timestamp".to_string(),
2024                    }
2025                })?;
2026
2027                // Calculate time elapsed (in some unit, e.g., years)
2028                // Assuming timestamps are Unix timestamps (seconds)
2029                let time_elapsed =
2030                    (current_time - reference_time) as f64 / (365.25 * 24.0 * 3600.0); // Convert to years
2031
2032                // Apply decay/growth: value = base_value * (1 + rate)^time_elapsed
2033                let current_value = base_value * (1.0 + rate).powf(time_elapsed);
2034
2035                Ok(operator.compare_f64(current_value, *target_value))
2036            }
2037            // For Custom conditions, we can't evaluate without more context
2038            Self::Custom { description } => Err(EvaluationError::Custom {
2039                message: format!("Cannot evaluate custom condition: {}", description),
2040            }),
2041        }
2042    }
2043
2044    /// Evaluates the condition with detailed step-by-step explanation.
2045    ///
2046    /// This method provides a full trace of the evaluation process, useful for:
2047    /// - Debugging complex conditions
2048    /// - Explaining legal decisions to users
2049    /// - Auditing and compliance
2050    /// - Educational purposes
2051    ///
2052    /// # Example
2053    /// ```
2054    /// # use legalis_core::{Condition, ComparisonOp, AttributeBasedContext};
2055    /// # use std::collections::HashMap;
2056    /// let mut attributes = HashMap::new();
2057    /// attributes.insert("age".to_string(), "25".to_string());
2058    /// attributes.insert("income".to_string(), "50000".to_string());
2059    /// let ctx = AttributeBasedContext::new(attributes);
2060    ///
2061    /// let age_check = Condition::age(ComparisonOp::GreaterOrEqual, 18);
2062    /// let income_check = Condition::income(ComparisonOp::GreaterOrEqual, 30000);
2063    /// let condition = age_check.and(income_check);
2064    ///
2065    /// let (result, explanation) = condition.evaluate_with_explanation(&ctx).unwrap();
2066    /// assert!(result);
2067    /// assert!(explanation.steps.len() >= 3); // AND, Age, Income checks
2068    /// assert!(explanation.conclusion);
2069    /// ```
2070    pub fn evaluate_with_explanation<C: EvaluationContext>(
2071        &self,
2072        context: &C,
2073    ) -> Result<(bool, EvaluationExplanation), EvaluationError> {
2074        let mut steps = Vec::new();
2075        let result = self.evaluate_with_explanation_recursive(context, 0, &mut steps)?;
2076
2077        let explanation = EvaluationExplanation {
2078            condition: format!("{}", self),
2079            conclusion: result,
2080            steps,
2081        };
2082
2083        Ok((result, explanation))
2084    }
2085
2086    /// Internal recursive helper for evaluation with explanation.
2087    fn evaluate_with_explanation_recursive<C: EvaluationContext>(
2088        &self,
2089        context: &C,
2090        depth: usize,
2091        steps: &mut Vec<ExplanationStep>,
2092    ) -> Result<bool, EvaluationError> {
2093        const MAX_DEPTH: usize = 100;
2094
2095        if depth > MAX_DEPTH {
2096            return Err(EvaluationError::MaxDepthExceeded {
2097                max_depth: MAX_DEPTH,
2098            });
2099        }
2100
2101        let start_time = std::time::Instant::now();
2102
2103        let (result, details) = match self {
2104            Self::And(left, right) => {
2105                let left_result =
2106                    left.evaluate_with_explanation_recursive(context, depth + 1, steps)?;
2107                if !left_result {
2108                    (
2109                        false,
2110                        "AND operation short-circuited (left operand is false)".to_string(),
2111                    )
2112                } else {
2113                    let right_result =
2114                        right.evaluate_with_explanation_recursive(context, depth + 1, steps)?;
2115                    (
2116                        right_result,
2117                        format!("AND: left={}, right={}", left_result, right_result),
2118                    )
2119                }
2120            }
2121            Self::Or(left, right) => {
2122                let left_result =
2123                    left.evaluate_with_explanation_recursive(context, depth + 1, steps)?;
2124                if left_result {
2125                    (
2126                        true,
2127                        "OR operation short-circuited (left operand is true)".to_string(),
2128                    )
2129                } else {
2130                    let right_result =
2131                        right.evaluate_with_explanation_recursive(context, depth + 1, steps)?;
2132                    (
2133                        right_result,
2134                        format!("OR: left={}, right={}", left_result, right_result),
2135                    )
2136                }
2137            }
2138            Self::Not(inner) => {
2139                let inner_result =
2140                    inner.evaluate_with_explanation_recursive(context, depth + 1, steps)?;
2141                (
2142                    !inner_result,
2143                    format!("NOT: inner={} -> {}", inner_result, !inner_result),
2144                )
2145            }
2146            Self::Age { operator, value } => {
2147                let age = context
2148                    .get_age()
2149                    .ok_or_else(|| EvaluationError::MissingAttribute {
2150                        key: "age".to_string(),
2151                    })?;
2152                let result = operator.compare_u32(age, *value);
2153                (
2154                    result,
2155                    format!("Age check: {} {} {} = {}", age, operator, value, result),
2156                )
2157            }
2158            Self::Income { operator, value } => {
2159                let income =
2160                    context
2161                        .get_income()
2162                        .ok_or_else(|| EvaluationError::MissingAttribute {
2163                            key: "income".to_string(),
2164                        })?;
2165                let result = operator.compare_u64(income, *value);
2166                (
2167                    result,
2168                    format!(
2169                        "Income check: {} {} {} = {}",
2170                        income, operator, value, result
2171                    ),
2172                )
2173            }
2174            Self::HasAttribute { key } => {
2175                let has_it = context.get_attribute(key).is_some();
2176                (has_it, format!("HasAttribute '{}': {}", key, has_it))
2177            }
2178            Self::AttributeEquals { key, value } => {
2179                let actual = context.get_attribute(key);
2180                let equals = actual.as_ref() == Some(value);
2181                (
2182                    equals,
2183                    format!(
2184                        "AttributeEquals '{}': expected='{}', actual={:?}, result={}",
2185                        key, value, actual, equals
2186                    ),
2187                )
2188            }
2189            _ => {
2190                // For other condition types, use regular evaluation
2191                let result = self.evaluate_with_depth(context, depth)?;
2192                (
2193                    result,
2194                    format!("Condition '{}' evaluated to {}", self, result),
2195                )
2196            }
2197        };
2198
2199        let elapsed = start_time.elapsed().as_micros() as u64;
2200
2201        steps.push(ExplanationStep {
2202            condition: format!("{}", self),
2203            result,
2204            details,
2205            depth,
2206            duration_micros: elapsed,
2207        });
2208
2209        Ok(result)
2210    }
2211
2212    /// Performs partial evaluation, allowing unknown values.
2213    ///
2214    /// Unlike `evaluate()`, this method can handle cases where some attributes
2215    /// or context values are unknown. It returns a three-valued logic result:
2216    /// - `PartialBool::True` - Definitely true
2217    /// - `PartialBool::False` - Definitely false
2218    /// - `PartialBool::Unknown` - Cannot determine (missing data)
2219    ///
2220    /// This is useful for:
2221    /// - Pre-checking eligibility with incomplete data
2222    /// - Planning data collection (what's missing?)
2223    /// - Optimistic evaluation strategies
2224    ///
2225    /// # Example
2226    /// ```
2227    /// # use legalis_core::{Condition, ComparisonOp, PartialBool, AttributeBasedContext};
2228    /// # use std::collections::HashMap;
2229    /// let mut attributes = HashMap::new();
2230    /// attributes.insert("age".to_string(), "25".to_string());
2231    /// // income is missing
2232    /// let ctx = AttributeBasedContext::new(attributes);
2233    ///
2234    /// let age_check = Condition::age(ComparisonOp::GreaterOrEqual, 18);
2235    /// let income_check = Condition::income(ComparisonOp::GreaterOrEqual, 30000);
2236    ///
2237    /// // Age check has data -> True
2238    /// assert!(matches!(age_check.partial_evaluate(&ctx), PartialBool::True { .. }));
2239    ///
2240    /// // Income check is missing data -> Unknown
2241    /// assert!(matches!(income_check.partial_evaluate(&ctx), PartialBool::Unknown { .. }));
2242    ///
2243    /// // AND with unknown propagates uncertainty
2244    /// let condition = age_check.and(income_check);
2245    /// assert!(matches!(condition.partial_evaluate(&ctx), PartialBool::Unknown { .. }));
2246    /// ```
2247    pub fn partial_evaluate<C: EvaluationContext>(&self, context: &C) -> PartialBool {
2248        self.partial_evaluate_with_depth(context, 0)
2249    }
2250
2251    /// Internal recursive helper for partial evaluation.
2252    fn partial_evaluate_with_depth<C: EvaluationContext>(
2253        &self,
2254        context: &C,
2255        depth: usize,
2256    ) -> PartialBool {
2257        const MAX_DEPTH: usize = 100;
2258
2259        if depth > MAX_DEPTH {
2260            return PartialBool::unknown(0.0, "Maximum depth exceeded");
2261        }
2262
2263        match self {
2264            // Three-valued logic for AND with uncertainty propagation
2265            Self::And(left, right) => {
2266                let left_result = left.partial_evaluate_with_depth(context, depth + 1);
2267                let right_result = right.partial_evaluate_with_depth(context, depth + 1);
2268
2269                match (&left_result, &right_result) {
2270                    (PartialBool::False { .. }, _) => left_result, // False AND anything = False
2271                    (_, PartialBool::False { .. }) => right_result, // anything AND False = False
2272                    (
2273                        PartialBool::True { confidence: c1, .. },
2274                        PartialBool::True { confidence: c2, .. },
2275                    ) => {
2276                        // Both true: confidence is minimum of both
2277                        PartialBool::true_with_confidence((*c1).min(*c2))
2278                    }
2279                    (
2280                        PartialBool::Unknown {
2281                            confidence: c1,
2282                            reason: r1,
2283                        },
2284                        PartialBool::Unknown {
2285                            confidence: c2,
2286                            reason: r2,
2287                        },
2288                    ) => {
2289                        // Both unknown: combine uncertainties
2290                        let combined_confidence = (*c1).min(*c2);
2291                        PartialBool::unknown(combined_confidence, &format!("{} AND {}", r1, r2))
2292                    }
2293                    _ => {
2294                        // One true, one unknown: result is unknown
2295                        PartialBool::unknown(0.5, "AND with unknown operand")
2296                    }
2297                }
2298            }
2299            // Three-valued logic for OR with uncertainty propagation
2300            Self::Or(left, right) => {
2301                let left_result = left.partial_evaluate_with_depth(context, depth + 1);
2302                let right_result = right.partial_evaluate_with_depth(context, depth + 1);
2303
2304                match (&left_result, &right_result) {
2305                    (PartialBool::True { .. }, _) => left_result, // True OR anything = True
2306                    (_, PartialBool::True { .. }) => right_result, // anything OR True = True
2307                    (
2308                        PartialBool::False { confidence: c1, .. },
2309                        PartialBool::False { confidence: c2, .. },
2310                    ) => {
2311                        // Both false: confidence is minimum of both
2312                        PartialBool::false_with_confidence((*c1).min(*c2))
2313                    }
2314                    (
2315                        PartialBool::Unknown {
2316                            confidence: c1,
2317                            reason: r1,
2318                        },
2319                        PartialBool::Unknown {
2320                            confidence: c2,
2321                            reason: r2,
2322                        },
2323                    ) => {
2324                        // Both unknown: combine uncertainties
2325                        let combined_confidence = (*c1).min(*c2);
2326                        PartialBool::unknown(combined_confidence, &format!("{} OR {}", r1, r2))
2327                    }
2328                    _ => {
2329                        // One false, one unknown: result is unknown
2330                        PartialBool::unknown(0.5, "OR with unknown operand")
2331                    }
2332                }
2333            }
2334            // NOT inverts the partial value
2335            Self::Not(inner) => {
2336                let inner_result = inner.partial_evaluate_with_depth(context, depth + 1);
2337                match inner_result {
2338                    PartialBool::True { confidence, reason } => {
2339                        PartialBool::false_with_confidence_and_reason(
2340                            confidence,
2341                            &format!("NOT ({})", reason),
2342                        )
2343                    }
2344                    PartialBool::False { confidence, reason } => {
2345                        PartialBool::true_with_confidence_and_reason(
2346                            confidence,
2347                            &format!("NOT ({})", reason),
2348                        )
2349                    }
2350                    PartialBool::Unknown { confidence, reason } => {
2351                        PartialBool::unknown(confidence, &format!("NOT ({})", reason))
2352                    }
2353                }
2354            }
2355            // Simple conditions
2356            Self::Age { operator, value } => match context.get_age() {
2357                Some(age) => {
2358                    let result = operator.compare_u32(age, *value);
2359                    if result {
2360                        PartialBool::true_with_confidence(1.0)
2361                    } else {
2362                        PartialBool::false_with_confidence(1.0)
2363                    }
2364                }
2365                None => PartialBool::unknown(0.0, "age attribute missing"),
2366            },
2367            Self::Income { operator, value } => match context.get_income() {
2368                Some(income) => {
2369                    let result = operator.compare_u64(income, *value);
2370                    if result {
2371                        PartialBool::true_with_confidence(1.0)
2372                    } else {
2373                        PartialBool::false_with_confidence(1.0)
2374                    }
2375                }
2376                None => PartialBool::unknown(0.0, "income attribute missing"),
2377            },
2378            Self::HasAttribute { key } => match context.get_attribute(key) {
2379                Some(_) => PartialBool::true_with_confidence(1.0),
2380                None => PartialBool::false_with_confidence(1.0),
2381            },
2382            Self::AttributeEquals { key, value } => match context.get_attribute(key) {
2383                Some(actual) => {
2384                    if &actual == value {
2385                        PartialBool::true_with_confidence(1.0)
2386                    } else {
2387                        PartialBool::false_with_confidence(1.0)
2388                    }
2389                }
2390                None => PartialBool::unknown(0.0, &format!("attribute '{}' missing", key)),
2391            },
2392            Self::DateRange { start, end } => match context.get_current_date() {
2393                Some(current_date) => {
2394                    let after_start = start.is_none_or(|s| current_date >= s);
2395                    let before_end = end.is_none_or(|e| current_date <= e);
2396                    let result = after_start && before_end;
2397                    if result {
2398                        PartialBool::true_with_confidence(1.0)
2399                    } else {
2400                        PartialBool::false_with_confidence(1.0)
2401                    }
2402                }
2403                None => PartialBool::unknown(0.0, "current date missing"),
2404            },
2405            // For other complex conditions, try to evaluate or return unknown
2406            _ => match self.evaluate(context) {
2407                Ok(result) => {
2408                    if result {
2409                        PartialBool::true_with_confidence(1.0)
2410                    } else {
2411                        PartialBool::false_with_confidence(1.0)
2412                    }
2413                }
2414                Err(_) => PartialBool::unknown(0.0, "evaluation failed or data missing"),
2415            },
2416        }
2417    }
2418}
2419
2420/// Context for evaluating conditions (simple attribute-based implementation).
2421///
2422/// Contains entity attributes and evaluation settings.
2423/// For a more flexible trait-based approach, see the `EvaluationContext` trait.
2424#[derive(Debug, Clone)]
2425pub struct AttributeBasedContext {
2426    /// Entity attributes as key-value pairs.
2427    pub attributes: HashMap<String, String>,
2428    /// Maximum evaluation depth to prevent stack overflow.
2429    pub max_depth: usize,
2430    /// Optional cache for memoizing condition evaluation results.
2431    pub cache: Option<ConditionCache>,
2432    /// Optional audit trail for tracking evaluation history.
2433    pub audit_trail: Option<EvaluationAuditTrail>,
2434}
2435
2436impl AttributeBasedContext {
2437    /// Creates a new evaluation context with default max depth (100).
2438    #[must_use]
2439    pub fn new(attributes: HashMap<String, String>) -> Self {
2440        Self {
2441            attributes,
2442            max_depth: 100,
2443            cache: None,
2444            audit_trail: None,
2445        }
2446    }
2447
2448    /// Creates a new evaluation context with custom max depth.
2449    #[must_use]
2450    pub fn with_max_depth(attributes: HashMap<String, String>, max_depth: usize) -> Self {
2451        Self {
2452            attributes,
2453            max_depth,
2454            cache: None,
2455            audit_trail: None,
2456        }
2457    }
2458
2459    /// Creates a new evaluation context with caching enabled.
2460    #[must_use]
2461    pub fn with_cache(attributes: HashMap<String, String>) -> Self {
2462        Self {
2463            attributes,
2464            max_depth: 100,
2465            cache: Some(ConditionCache::new()),
2466            audit_trail: None,
2467        }
2468    }
2469
2470    /// Creates a new evaluation context with custom max depth and cache capacity.
2471    #[must_use]
2472    pub fn with_cache_capacity(
2473        attributes: HashMap<String, String>,
2474        max_depth: usize,
2475        cache_capacity: usize,
2476    ) -> Self {
2477        Self {
2478            attributes,
2479            max_depth,
2480            cache: Some(ConditionCache::with_capacity(cache_capacity)),
2481            audit_trail: None,
2482        }
2483    }
2484
2485    /// Creates a new evaluation context with audit trail enabled.
2486    #[must_use]
2487    pub fn with_audit_trail(attributes: HashMap<String, String>) -> Self {
2488        Self {
2489            attributes,
2490            max_depth: 100,
2491            cache: None,
2492            audit_trail: Some(EvaluationAuditTrail::new()),
2493        }
2494    }
2495
2496    /// Records an evaluation in the audit trail if enabled.
2497    pub fn record_evaluation(&mut self, condition: &str, result: bool, duration_micros: u64) {
2498        if let Some(trail) = &mut self.audit_trail {
2499            trail.record(condition.to_string(), result, duration_micros);
2500        }
2501    }
2502}
2503
2504/// Cache for memoizing condition evaluation results.
2505///
2506/// This cache improves performance when the same conditions are evaluated repeatedly
2507/// with the same entity attributes.
2508#[derive(Debug, Clone)]
2509pub struct ConditionCache {
2510    /// Cache storage mapping condition strings to evaluation results.
2511    cache: HashMap<String, bool>,
2512    /// Maximum number of entries to store (LRU eviction).
2513    max_capacity: usize,
2514    /// Access order for LRU eviction.
2515    access_order: Vec<String>,
2516}
2517
2518impl ConditionCache {
2519    /// Creates a new cache with default capacity (1000).
2520    #[must_use]
2521    pub fn new() -> Self {
2522        Self {
2523            cache: HashMap::new(),
2524            max_capacity: 1000,
2525            access_order: Vec::new(),
2526        }
2527    }
2528
2529    /// Creates a new cache with custom capacity.
2530    #[must_use]
2531    pub fn with_capacity(capacity: usize) -> Self {
2532        Self {
2533            cache: HashMap::with_capacity(capacity),
2534            max_capacity: capacity,
2535            access_order: Vec::with_capacity(capacity),
2536        }
2537    }
2538
2539    /// Gets a cached evaluation result if available.
2540    pub fn get(&mut self, condition_key: &str) -> Option<bool> {
2541        if let Some(&result) = self.cache.get(condition_key) {
2542            // Update access order (move to end for LRU)
2543            if let Some(pos) = self.access_order.iter().position(|k| k == condition_key) {
2544                self.access_order.remove(pos);
2545            }
2546            self.access_order.push(condition_key.to_string());
2547            Some(result)
2548        } else {
2549            None
2550        }
2551    }
2552
2553    /// Stores an evaluation result in the cache.
2554    pub fn insert(&mut self, condition_key: String, result: bool) {
2555        // Evict oldest entry if at capacity
2556        if self.cache.len() >= self.max_capacity
2557            && !self.cache.contains_key(&condition_key)
2558            && let Some(oldest_key) = self.access_order.first().cloned()
2559        {
2560            self.cache.remove(&oldest_key);
2561            self.access_order.remove(0);
2562        }
2563
2564        self.cache.insert(condition_key.clone(), result);
2565        self.access_order.push(condition_key);
2566    }
2567
2568    /// Clears all cached entries.
2569    pub fn clear(&mut self) {
2570        self.cache.clear();
2571        self.access_order.clear();
2572    }
2573
2574    /// Returns the number of cached entries.
2575    #[must_use]
2576    pub fn len(&self) -> usize {
2577        self.cache.len()
2578    }
2579
2580    /// Returns true if the cache is empty.
2581    #[must_use]
2582    pub fn is_empty(&self) -> bool {
2583        self.cache.is_empty()
2584    }
2585
2586    /// Returns cache hit rate (for performance monitoring).
2587    #[must_use]
2588    pub fn hit_rate(&self) -> f64 {
2589        // Note: This is a simplified implementation
2590        // For production, track hits/misses separately
2591        0.0
2592    }
2593}
2594
2595impl Default for ConditionCache {
2596    fn default() -> Self {
2597        Self::new()
2598    }
2599}
2600
2601/// Audit trail for tracking condition evaluations.
2602///
2603/// Records each evaluation with timestamp, condition, result, and duration.
2604/// Useful for debugging, compliance, and performance analysis.
2605#[derive(Debug, Clone)]
2606pub struct EvaluationAuditTrail {
2607    /// List of evaluation records
2608    records: Vec<EvaluationRecord>,
2609    /// Maximum number of records to keep
2610    max_records: usize,
2611}
2612
2613impl EvaluationAuditTrail {
2614    /// Creates a new audit trail with default capacity (1000 records).
2615    #[must_use]
2616    pub fn new() -> Self {
2617        Self {
2618            records: Vec::new(),
2619            max_records: 1000,
2620        }
2621    }
2622
2623    /// Creates a new audit trail with custom capacity.
2624    #[must_use]
2625    pub fn with_capacity(capacity: usize) -> Self {
2626        Self {
2627            records: Vec::with_capacity(capacity),
2628            max_records: capacity,
2629        }
2630    }
2631
2632    /// Records an evaluation.
2633    pub fn record(&mut self, condition: String, result: bool, duration_micros: u64) {
2634        // Evict oldest record if at capacity
2635        if self.records.len() >= self.max_records {
2636            self.records.remove(0);
2637        }
2638
2639        self.records.push(EvaluationRecord {
2640            timestamp: Utc::now(),
2641            condition,
2642            result,
2643            duration_micros,
2644        });
2645    }
2646
2647    /// Returns all evaluation records.
2648    #[must_use]
2649    pub fn records(&self) -> &[EvaluationRecord] {
2650        &self.records
2651    }
2652
2653    /// Returns the number of records.
2654    #[must_use]
2655    pub fn len(&self) -> usize {
2656        self.records.len()
2657    }
2658
2659    /// Returns true if there are no records.
2660    #[must_use]
2661    pub fn is_empty(&self) -> bool {
2662        self.records.is_empty()
2663    }
2664
2665    /// Clears all records.
2666    pub fn clear(&mut self) {
2667        self.records.clear();
2668    }
2669
2670    /// Returns average evaluation duration in microseconds.
2671    #[must_use]
2672    pub fn average_duration(&self) -> f64 {
2673        if self.records.is_empty() {
2674            return 0.0;
2675        }
2676        let total: u64 = self.records.iter().map(|r| r.duration_micros).sum();
2677        total as f64 / self.records.len() as f64
2678    }
2679
2680    /// Returns the slowest evaluation record.
2681    #[must_use]
2682    pub fn slowest_evaluation(&self) -> Option<&EvaluationRecord> {
2683        self.records.iter().max_by_key(|r| r.duration_micros)
2684    }
2685
2686    /// Returns records where evaluation took longer than threshold (microseconds).
2687    #[must_use]
2688    pub fn slow_evaluations(&self, threshold_micros: u64) -> Vec<&EvaluationRecord> {
2689        self.records
2690            .iter()
2691            .filter(|r| r.duration_micros > threshold_micros)
2692            .collect()
2693    }
2694}
2695
2696impl Default for EvaluationAuditTrail {
2697    fn default() -> Self {
2698        Self::new()
2699    }
2700}
2701
2702/// A single evaluation record in the audit trail.
2703#[derive(Debug, Clone)]
2704#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2705#[cfg_attr(feature = "schema", derive(JsonSchema))]
2706pub struct EvaluationRecord {
2707    /// When the evaluation occurred
2708    pub timestamp: DateTime<Utc>,
2709    /// The condition that was evaluated (as string)
2710    pub condition: String,
2711    /// The result of the evaluation
2712    pub result: bool,
2713    /// How long the evaluation took (microseconds)
2714    pub duration_micros: u64,
2715}
2716
2717impl fmt::Display for EvaluationRecord {
2718    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2719        write!(
2720            f,
2721            "[{}] {} = {} ({} μs)",
2722            self.timestamp.format("%Y-%m-%d %H:%M:%S"),
2723            self.condition,
2724            self.result,
2725            self.duration_micros
2726        )
2727    }
2728}
2729
2730impl fmt::Display for Condition {
2731    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2732        match self {
2733            Self::Age { operator, value } => write!(f, "age {} {}", operator, value),
2734            Self::Income { operator, value } => write!(f, "income {} {}", operator, value),
2735            Self::HasAttribute { key } => write!(f, "has_attribute({})", key),
2736            Self::AttributeEquals { key, value } => write!(f, "{} == \"{}\"", key, value),
2737            Self::DateRange { start, end } => match (start, end) {
2738                (Some(s), Some(e)) => write!(f, "date in [{}, {}]", s, e),
2739                (Some(s), None) => write!(f, "date >= {}", s),
2740                (None, Some(e)) => write!(f, "date <= {}", e),
2741                (None, None) => write!(f, "date (any)"),
2742            },
2743            Self::Geographic {
2744                region_type,
2745                region_id,
2746            } => {
2747                write!(f, "in {:?}({})", region_type, region_id)
2748            }
2749            Self::EntityRelationship {
2750                relationship_type,
2751                target_entity_id,
2752            } => match target_entity_id {
2753                Some(id) => write!(f, "{:?} with {}", relationship_type, id),
2754                None => write!(f, "has {:?}", relationship_type),
2755            },
2756            Self::ResidencyDuration { operator, months } => {
2757                write!(f, "residency {} {} months", operator, months)
2758            }
2759            Self::Duration {
2760                operator,
2761                value,
2762                unit,
2763            } => {
2764                write!(f, "duration {} {} {}", operator, value, unit)
2765            }
2766            Self::Percentage {
2767                operator,
2768                value,
2769                context,
2770            } => {
2771                write!(f, "{} {} {}%", context, operator, value)
2772            }
2773            Self::SetMembership {
2774                attribute,
2775                values,
2776                negated,
2777            } => {
2778                let op = if *negated { "NOT IN" } else { "IN" };
2779                write!(f, "{} {} {{{}}}", attribute, op, values.join(", "))
2780            }
2781            Self::Pattern {
2782                attribute,
2783                pattern,
2784                negated,
2785            } => {
2786                let op = if *negated { "!~" } else { "=~" };
2787                write!(f, "{} {} /{}/", attribute, op, pattern)
2788            }
2789            Self::Calculation {
2790                formula,
2791                operator,
2792                value,
2793            } => {
2794                write!(f, "({}) {} {}", formula, operator, value)
2795            }
2796            Self::Composite {
2797                conditions,
2798                threshold,
2799            } => {
2800                write!(f, "composite[")?;
2801                for (i, (weight, cond)) in conditions.iter().enumerate() {
2802                    if i > 0 {
2803                        write!(f, ", ")?;
2804                    }
2805                    write!(f, "{}*{}", weight, cond)?;
2806                }
2807                write!(f, "] >= {}", threshold)
2808            }
2809            Self::Threshold {
2810                attributes,
2811                operator,
2812                value,
2813            } => {
2814                write!(f, "sum[")?;
2815                for (i, (attr, mult)) in attributes.iter().enumerate() {
2816                    if i > 0 {
2817                        write!(f, " + ")?;
2818                    }
2819                    if (*mult - 1.0).abs() < f64::EPSILON {
2820                        write!(f, "{}", attr)?;
2821                    } else {
2822                        write!(f, "{}*{}", mult, attr)?;
2823                    }
2824                }
2825                write!(f, "] {} {}", operator, value)
2826            }
2827            Self::Fuzzy {
2828                attribute,
2829                membership_points,
2830                min_membership,
2831            } => {
2832                write!(
2833                    f,
2834                    "fuzzy({}, membership={:?}) >= {}",
2835                    attribute, membership_points, min_membership
2836                )
2837            }
2838            Self::Probabilistic {
2839                condition,
2840                probability,
2841                threshold,
2842            } => {
2843                write!(f, "prob({}, p={}) >= {}", condition, probability, threshold)
2844            }
2845            Self::Temporal {
2846                base_value,
2847                reference_time,
2848                rate,
2849                operator,
2850                target_value,
2851            } => {
2852                write!(
2853                    f,
2854                    "temporal(base={}, t0={}, rate={}) {} {}",
2855                    base_value, reference_time, rate, operator, target_value
2856                )
2857            }
2858            Self::And(left, right) => write!(f, "({} AND {})", left, right),
2859            Self::Or(left, right) => write!(f, "({} OR {})", left, right),
2860            Self::Not(inner) => write!(f, "NOT {}", inner),
2861            Self::Custom { description } => write!(f, "custom({})", description),
2862        }
2863    }
2864}
2865
2866/// Geographic region types.
2867#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2868#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2869#[cfg_attr(feature = "schema", derive(JsonSchema))]
2870pub enum RegionType {
2871    /// Country level
2872    Country,
2873    /// State/Province level
2874    State,
2875    /// City/Municipality level
2876    City,
2877    /// District/Ward level
2878    District,
2879    /// Postal/ZIP code area
2880    PostalCode,
2881    /// Custom region
2882    Custom,
2883}
2884
2885/// Entity relationship types.
2886#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2887#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2888#[cfg_attr(feature = "schema", derive(JsonSchema))]
2889pub enum RelationshipType {
2890    /// Parent-child relationship
2891    ParentChild,
2892    /// Spousal relationship
2893    Spouse,
2894    /// Employment relationship
2895    Employment,
2896    /// Guardianship
2897    Guardian,
2898    /// Business ownership
2899    BusinessOwner,
2900    /// Contractual relationship
2901    Contractual,
2902}
2903
2904/// Comparison operators for conditions.
2905///
2906/// Used in conditions to compare numeric values (age, income, duration, etc.).
2907///
2908/// # Examples
2909///
2910/// ```
2911/// use legalis_core::ComparisonOp;
2912///
2913/// let op = ComparisonOp::GreaterOrEqual;
2914/// assert_eq!(format!("{}", op), ">=");
2915///
2916/// let eq = ComparisonOp::Equal;
2917/// assert_eq!(format!("{}", eq), "==");
2918/// ```
2919#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2920#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2921#[cfg_attr(feature = "schema", derive(JsonSchema))]
2922pub enum ComparisonOp {
2923    Equal,
2924    NotEqual,
2925    GreaterThan,
2926    GreaterOrEqual,
2927    LessThan,
2928    LessOrEqual,
2929}
2930
2931impl ComparisonOp {
2932    /// Returns the inverse of this comparison operator.
2933    ///
2934    /// # Examples
2935    ///
2936    /// ```
2937    /// use legalis_core::ComparisonOp;
2938    ///
2939    /// assert_eq!(ComparisonOp::GreaterThan.inverse(), ComparisonOp::LessOrEqual);
2940    /// assert_eq!(ComparisonOp::Equal.inverse(), ComparisonOp::NotEqual);
2941    /// ```
2942    #[must_use]
2943    pub const fn inverse(&self) -> Self {
2944        match self {
2945            Self::Equal => Self::NotEqual,
2946            Self::NotEqual => Self::Equal,
2947            Self::GreaterThan => Self::LessOrEqual,
2948            Self::GreaterOrEqual => Self::LessThan,
2949            Self::LessThan => Self::GreaterOrEqual,
2950            Self::LessOrEqual => Self::GreaterThan,
2951        }
2952    }
2953
2954    /// Returns true if this is an equality check (Equal or NotEqual).
2955    #[must_use]
2956    pub const fn is_equality(&self) -> bool {
2957        matches!(self, Self::Equal | Self::NotEqual)
2958    }
2959
2960    /// Returns true if this is an ordering comparison.
2961    #[must_use]
2962    pub const fn is_ordering(&self) -> bool {
2963        !self.is_equality()
2964    }
2965
2966    /// Compares two u32 values using this operator.
2967    #[must_use]
2968    pub const fn compare_u32(&self, left: u32, right: u32) -> bool {
2969        match self {
2970            Self::Equal => left == right,
2971            Self::NotEqual => left != right,
2972            Self::GreaterThan => left > right,
2973            Self::GreaterOrEqual => left >= right,
2974            Self::LessThan => left < right,
2975            Self::LessOrEqual => left <= right,
2976        }
2977    }
2978
2979    /// Compares two u64 values using this operator.
2980    #[must_use]
2981    pub const fn compare_u64(&self, left: u64, right: u64) -> bool {
2982        match self {
2983            Self::Equal => left == right,
2984            Self::NotEqual => left != right,
2985            Self::GreaterThan => left > right,
2986            Self::GreaterOrEqual => left >= right,
2987            Self::LessThan => left < right,
2988            Self::LessOrEqual => left <= right,
2989        }
2990    }
2991
2992    /// Compares two i64 values using this operator.
2993    #[must_use]
2994    pub const fn compare_i64(&self, left: i64, right: i64) -> bool {
2995        match self {
2996            Self::Equal => left == right,
2997            Self::NotEqual => left != right,
2998            Self::GreaterThan => left > right,
2999            Self::GreaterOrEqual => left >= right,
3000            Self::LessThan => left < right,
3001            Self::LessOrEqual => left <= right,
3002        }
3003    }
3004
3005    /// Compares two f64 values using this operator.
3006    #[must_use]
3007    pub fn compare_f64(&self, left: f64, right: f64) -> bool {
3008        match self {
3009            Self::Equal => (left - right).abs() < f64::EPSILON,
3010            Self::NotEqual => (left - right).abs() >= f64::EPSILON,
3011            Self::GreaterThan => left > right,
3012            Self::GreaterOrEqual => left >= right,
3013            Self::LessThan => left < right,
3014            Self::LessOrEqual => left <= right,
3015        }
3016    }
3017}
3018
3019impl fmt::Display for ComparisonOp {
3020    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3021        match self {
3022            Self::Equal => write!(f, "=="),
3023            Self::NotEqual => write!(f, "!="),
3024            Self::GreaterThan => write!(f, ">"),
3025            Self::GreaterOrEqual => write!(f, ">="),
3026            Self::LessThan => write!(f, "<"),
3027            Self::LessOrEqual => write!(f, "<="),
3028        }
3029    }
3030}
3031
3032// ==================================================
3033// Evaluation Context Trait for Flexible Condition Evaluation
3034// ==================================================
3035
3036/// Context trait for evaluating conditions against legal entities.
3037///
3038/// Implement this trait to provide custom evaluation logic for your domain.
3039/// This trait-based approach allows integration with any entity storage system.
3040///
3041/// # Examples
3042///
3043/// ```
3044/// use legalis_core::{EvaluationContext, RegionType, RelationshipType, DurationUnit};
3045/// use chrono::NaiveDate;
3046///
3047/// struct MyContext {
3048///     age: u32,
3049///     income: u64,
3050/// }
3051///
3052/// impl EvaluationContext for MyContext {
3053///     fn get_attribute(&self, _key: &str) -> Option<String> { None }
3054///     fn get_age(&self) -> Option<u32> { Some(self.age) }
3055///     fn get_income(&self) -> Option<u64> { Some(self.income) }
3056///     fn get_current_date(&self) -> Option<NaiveDate> { None }
3057///     fn check_geographic(&self, _region_type: RegionType, _region_id: &str) -> bool { false }
3058///     fn check_relationship(&self, _relationship_type: RelationshipType, _target_id: Option<&str>) -> bool { false }
3059///     fn get_residency_months(&self) -> Option<u32> { None }
3060///     fn get_duration(&self, _unit: DurationUnit) -> Option<u32> { None }
3061///     fn get_percentage(&self, _context: &str) -> Option<u32> { None }
3062///     fn evaluate_formula(&self, _formula: &str) -> Option<f64> { None }
3063/// }
3064/// ```
3065pub trait EvaluationContext {
3066    /// Get an attribute value from the entity.
3067    fn get_attribute(&self, key: &str) -> Option<String>;
3068
3069    /// Get entity's age.
3070    fn get_age(&self) -> Option<u32>;
3071
3072    /// Get entity's income.
3073    fn get_income(&self) -> Option<u64>;
3074
3075    /// Get current date for date range checks.
3076    fn get_current_date(&self) -> Option<NaiveDate>;
3077
3078    /// Get current timestamp (Unix timestamp in seconds) for temporal conditions.
3079    fn get_current_timestamp(&self) -> Option<i64> {
3080        None
3081    }
3082
3083    /// Check geographic location.
3084    fn check_geographic(&self, region_type: RegionType, region_id: &str) -> bool;
3085
3086    /// Check entity relationship.
3087    fn check_relationship(
3088        &self,
3089        relationship_type: RelationshipType,
3090        target_id: Option<&str>,
3091    ) -> bool;
3092
3093    /// Get residency duration in months.
3094    fn get_residency_months(&self) -> Option<u32>;
3095
3096    /// Get duration value for a given unit.
3097    fn get_duration(&self, unit: DurationUnit) -> Option<u32>;
3098
3099    /// Get percentage value for a given context.
3100    fn get_percentage(&self, context: &str) -> Option<u32>;
3101
3102    /// Evaluate a custom formula and return the result.
3103    fn evaluate_formula(&self, formula: &str) -> Option<f64>;
3104}
3105
3106/// Errors that can occur during condition evaluation.
3107#[derive(Debug, Clone, PartialEq, Eq)]
3108#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
3109#[cfg_attr(feature = "schema", derive(JsonSchema))]
3110pub enum EvaluationError {
3111    /// Missing required attribute for evaluation
3112    MissingAttribute { key: String },
3113    /// Missing required context data
3114    MissingContext { description: String },
3115    /// Invalid formula or calculation
3116    InvalidFormula { formula: String, reason: String },
3117    /// Pattern matching error
3118    PatternError { pattern: String, reason: String },
3119    /// Maximum evaluation depth exceeded (prevents infinite recursion)
3120    MaxDepthExceeded { max_depth: usize },
3121    /// Custom error
3122    Custom { message: String },
3123}
3124
3125impl fmt::Display for EvaluationError {
3126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3127        match self {
3128            Self::MissingAttribute { key } => write!(f, "Missing attribute: {}", key),
3129            Self::MissingContext { description } => write!(f, "Missing context: {}", description),
3130            Self::InvalidFormula { formula, reason } => {
3131                write!(f, "Invalid formula '{}': {}", formula, reason)
3132            }
3133            Self::PatternError { pattern, reason } => {
3134                write!(f, "Pattern error '{}': {}", pattern, reason)
3135            }
3136            Self::MaxDepthExceeded { max_depth } => {
3137                write!(f, "Maximum evaluation depth {} exceeded", max_depth)
3138            }
3139            Self::Custom { message } => write!(f, "{}", message),
3140        }
3141    }
3142}
3143
3144impl std::error::Error for EvaluationError {}
3145
3146/// Context wrapper that provides default values for missing attributes.
3147///
3148/// This is useful for handling optional attributes with sensible defaults.
3149///
3150/// # Example
3151/// ```
3152/// # use legalis_core::{Condition, ComparisonOp, AttributeBasedContext, DefaultValueContext};
3153/// # use std::collections::HashMap;
3154/// let mut attributes = HashMap::new();
3155/// attributes.insert("name".to_string(), "Alice".to_string());
3156/// // age is missing
3157/// let entity = AttributeBasedContext::new(attributes);
3158///
3159/// let mut defaults = HashMap::new();
3160/// defaults.insert("age".to_string(), "18".to_string());
3161///
3162/// let ctx_with_defaults = DefaultValueContext::new(&entity, defaults);
3163///
3164/// // Will use default age of 18
3165/// let condition = Condition::age(ComparisonOp::GreaterOrEqual, 18);
3166/// assert!(condition.evaluate(&ctx_with_defaults).unwrap());
3167/// ```
3168#[derive(Debug)]
3169pub struct DefaultValueContext<'a, C: EvaluationContext> {
3170    inner: &'a C,
3171    defaults: HashMap<String, String>,
3172}
3173
3174impl<'a, C: EvaluationContext> DefaultValueContext<'a, C> {
3175    /// Creates a new context with default values.
3176    pub fn new(inner: &'a C, defaults: HashMap<String, String>) -> Self {
3177        Self { inner, defaults }
3178    }
3179
3180    /// Adds a default value for an attribute.
3181    pub fn with_default(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
3182        self.defaults.insert(key.into(), value.into());
3183        self
3184    }
3185}
3186
3187impl<'a, C: EvaluationContext> EvaluationContext for DefaultValueContext<'a, C> {
3188    fn get_attribute(&self, key: &str) -> Option<String> {
3189        self.inner
3190            .get_attribute(key)
3191            .or_else(|| self.defaults.get(key).cloned())
3192    }
3193
3194    fn get_age(&self) -> Option<u32> {
3195        self.inner
3196            .get_age()
3197            .or_else(|| self.defaults.get("age").and_then(|s| s.parse::<u32>().ok()))
3198    }
3199
3200    fn get_income(&self) -> Option<u64> {
3201        self.inner.get_income().or_else(|| {
3202            self.defaults
3203                .get("income")
3204                .and_then(|s| s.parse::<u64>().ok())
3205        })
3206    }
3207
3208    fn get_current_date(&self) -> Option<NaiveDate> {
3209        self.inner.get_current_date()
3210    }
3211
3212    fn get_current_timestamp(&self) -> Option<i64> {
3213        self.inner.get_current_timestamp()
3214    }
3215
3216    fn check_geographic(&self, region_type: RegionType, region_id: &str) -> bool {
3217        self.inner.check_geographic(region_type, region_id)
3218    }
3219
3220    fn check_relationship(
3221        &self,
3222        relationship_type: RelationshipType,
3223        target_id: Option<&str>,
3224    ) -> bool {
3225        self.inner.check_relationship(relationship_type, target_id)
3226    }
3227
3228    fn get_residency_months(&self) -> Option<u32> {
3229        self.inner.get_residency_months()
3230    }
3231
3232    fn get_duration(&self, unit: DurationUnit) -> Option<u32> {
3233        self.inner.get_duration(unit)
3234    }
3235
3236    fn get_percentage(&self, context: &str) -> Option<u32> {
3237        self.inner.get_percentage(context)
3238    }
3239
3240    fn evaluate_formula(&self, formula: &str) -> Option<f64> {
3241        self.inner.evaluate_formula(formula)
3242    }
3243}
3244
3245/// Context wrapper that provides fallback evaluation strategies.
3246///
3247/// When the primary context cannot provide a value, it falls back to a secondary context.
3248///
3249/// # Example
3250/// ```
3251/// # use legalis_core::{Condition, ComparisonOp, AttributeBasedContext, FallbackContext, EvaluationContext};
3252/// # use std::collections::HashMap;
3253/// let mut primary_attrs = HashMap::new();
3254/// primary_attrs.insert("name".to_string(), "Alice".to_string());
3255/// let primary = AttributeBasedContext::new(primary_attrs);
3256///
3257/// let mut fallback_attrs = HashMap::new();
3258/// fallback_attrs.insert("age".to_string(), "25".to_string());
3259/// fallback_attrs.insert("name".to_string(), "Bob".to_string()); // Will not be used
3260/// let fallback = AttributeBasedContext::new(fallback_attrs);
3261///
3262/// let ctx = FallbackContext::new(&primary, &fallback);
3263///
3264/// // name comes from primary
3265/// assert_eq!(ctx.get_attribute("name"), Some("Alice".to_string()));
3266/// // age comes from fallback
3267/// assert_eq!(ctx.get_attribute("age"), Some("25".to_string()));
3268/// ```
3269#[derive(Debug)]
3270pub struct FallbackContext<'a, C1: EvaluationContext, C2: EvaluationContext> {
3271    primary: &'a C1,
3272    fallback: &'a C2,
3273}
3274
3275impl<'a, C1: EvaluationContext, C2: EvaluationContext> FallbackContext<'a, C1, C2> {
3276    /// Creates a new context with fallback.
3277    pub fn new(primary: &'a C1, fallback: &'a C2) -> Self {
3278        Self { primary, fallback }
3279    }
3280}
3281
3282impl<'a, C1: EvaluationContext, C2: EvaluationContext> EvaluationContext
3283    for FallbackContext<'a, C1, C2>
3284{
3285    fn get_attribute(&self, key: &str) -> Option<String> {
3286        self.primary
3287            .get_attribute(key)
3288            .or_else(|| self.fallback.get_attribute(key))
3289    }
3290
3291    fn get_age(&self) -> Option<u32> {
3292        self.primary.get_age().or_else(|| self.fallback.get_age())
3293    }
3294
3295    fn get_income(&self) -> Option<u64> {
3296        self.primary
3297            .get_income()
3298            .or_else(|| self.fallback.get_income())
3299    }
3300
3301    fn get_current_date(&self) -> Option<NaiveDate> {
3302        self.primary
3303            .get_current_date()
3304            .or_else(|| self.fallback.get_current_date())
3305    }
3306
3307    fn get_current_timestamp(&self) -> Option<i64> {
3308        self.primary
3309            .get_current_timestamp()
3310            .or_else(|| self.fallback.get_current_timestamp())
3311    }
3312
3313    fn check_geographic(&self, region_type: RegionType, region_id: &str) -> bool {
3314        self.primary.check_geographic(region_type, region_id)
3315            || self.fallback.check_geographic(region_type, region_id)
3316    }
3317
3318    fn check_relationship(
3319        &self,
3320        relationship_type: RelationshipType,
3321        target_id: Option<&str>,
3322    ) -> bool {
3323        self.primary
3324            .check_relationship(relationship_type, target_id)
3325            || self
3326                .fallback
3327                .check_relationship(relationship_type, target_id)
3328    }
3329
3330    fn get_residency_months(&self) -> Option<u32> {
3331        self.primary
3332            .get_residency_months()
3333            .or_else(|| self.fallback.get_residency_months())
3334    }
3335
3336    fn get_duration(&self, unit: DurationUnit) -> Option<u32> {
3337        self.primary
3338            .get_duration(unit)
3339            .or_else(|| self.fallback.get_duration(unit))
3340    }
3341
3342    fn get_percentage(&self, context: &str) -> Option<u32> {
3343        self.primary
3344            .get_percentage(context)
3345            .or_else(|| self.fallback.get_percentage(context))
3346    }
3347
3348    fn evaluate_formula(&self, formula: &str) -> Option<f64> {
3349        self.primary
3350            .evaluate_formula(formula)
3351            .or_else(|| self.fallback.evaluate_formula(formula))
3352    }
3353}
3354
3355/// Implement EvaluationContext for AttributeBasedContext for compatibility.
3356impl EvaluationContext for AttributeBasedContext {
3357    fn get_attribute(&self, key: &str) -> Option<String> {
3358        self.attributes.get(key).cloned()
3359    }
3360
3361    fn get_age(&self) -> Option<u32> {
3362        self.attributes.get("age").and_then(|v| v.parse().ok())
3363    }
3364
3365    fn get_income(&self) -> Option<u64> {
3366        self.attributes.get("income").and_then(|v| v.parse().ok())
3367    }
3368
3369    fn get_current_date(&self) -> Option<NaiveDate> {
3370        self.attributes
3371            .get("current_date")
3372            .and_then(|v| NaiveDate::parse_from_str(v, "%Y-%m-%d").ok())
3373    }
3374
3375    fn check_geographic(&self, _region_type: RegionType, region_id: &str) -> bool {
3376        self.attributes
3377            .get("region")
3378            .is_some_and(|v| v == region_id)
3379    }
3380
3381    fn check_relationship(
3382        &self,
3383        _relationship_type: RelationshipType,
3384        target_id: Option<&str>,
3385    ) -> bool {
3386        if let Some(target) = target_id {
3387            self.attributes
3388                .get("relationship")
3389                .is_some_and(|v| v == target)
3390        } else {
3391            self.attributes.contains_key("relationship")
3392        }
3393    }
3394
3395    fn get_residency_months(&self) -> Option<u32> {
3396        self.attributes
3397            .get("residency_months")
3398            .and_then(|v| v.parse().ok())
3399    }
3400
3401    fn get_duration(&self, unit: DurationUnit) -> Option<u32> {
3402        let key = format!("duration_{:?}", unit).to_lowercase();
3403        self.attributes.get(&key).and_then(|v| v.parse().ok())
3404    }
3405
3406    fn get_percentage(&self, context: &str) -> Option<u32> {
3407        let key = format!("percentage_{}", context);
3408        self.attributes.get(&key).and_then(|v| v.parse().ok())
3409    }
3410
3411    fn evaluate_formula(&self, _formula: &str) -> Option<f64> {
3412        // Basic formula evaluation - can be extended with a proper parser
3413        None
3414    }
3415}
3416
3417/// Memoization cache for condition evaluation results.
3418///
3419/// Caches evaluation results to avoid re-evaluating the same conditions.
3420///
3421/// # Examples
3422///
3423/// ```
3424/// use legalis_core::{Condition, ComparisonOp, ConditionEvaluator};
3425/// use legalis_core::{EvaluationContext, RegionType, RelationshipType, DurationUnit};
3426/// use chrono::NaiveDate;
3427///
3428/// struct MyContext { age: u32 }
3429///
3430/// impl EvaluationContext for MyContext {
3431///     fn get_attribute(&self, _key: &str) -> Option<String> { None }
3432///     fn get_age(&self) -> Option<u32> { Some(self.age) }
3433///     fn get_income(&self) -> Option<u64> { None }
3434///     fn get_current_date(&self) -> Option<NaiveDate> { None }
3435///     fn check_geographic(&self, _region_type: RegionType, _region_id: &str) -> bool { false }
3436///     fn check_relationship(&self, _relationship_type: RelationshipType, _target_id: Option<&str>) -> bool { false }
3437///     fn get_residency_months(&self) -> Option<u32> { None }
3438///     fn get_duration(&self, _unit: DurationUnit) -> Option<u32> { None }
3439///     fn get_percentage(&self, _context: &str) -> Option<u32> { None }
3440///     fn evaluate_formula(&self, _formula: &str) -> Option<f64> { None }
3441/// }
3442///
3443/// let mut evaluator = ConditionEvaluator::new();
3444/// let ctx = MyContext { age: 25 };
3445/// let condition = Condition::age(ComparisonOp::GreaterOrEqual, 18);
3446///
3447/// // First evaluation - not cached
3448/// assert_eq!(evaluator.evaluate(&condition, &ctx).ok(), Some(true));
3449///
3450/// // Second evaluation - retrieved from cache
3451/// assert_eq!(evaluator.evaluate(&condition, &ctx).ok(), Some(true));
3452/// assert_eq!(evaluator.cache_hits(), 1);
3453/// ```
3454#[derive(Debug, Default)]
3455pub struct ConditionEvaluator {
3456    cache: std::collections::HashMap<String, bool>,
3457    cache_hits: usize,
3458    cache_misses: usize,
3459}
3460
3461impl ConditionEvaluator {
3462    /// Creates a new condition evaluator with an empty cache.
3463    #[must_use]
3464    pub fn new() -> Self {
3465        Self::default()
3466    }
3467
3468    /// Evaluates a condition with memoization.
3469    ///
3470    /// Results are cached based on the condition's string representation.
3471    pub fn evaluate<C: EvaluationContext>(
3472        &mut self,
3473        condition: &Condition,
3474        context: &C,
3475    ) -> Result<bool, EvaluationError> {
3476        let cache_key = format!("{}", condition);
3477
3478        if let Some(&result) = self.cache.get(&cache_key) {
3479            self.cache_hits += 1;
3480            return Ok(result);
3481        }
3482
3483        self.cache_misses += 1;
3484        let result = condition.evaluate(context)?;
3485        self.cache.insert(cache_key, result);
3486        Ok(result)
3487    }
3488
3489    /// Clears the evaluation cache.
3490    pub fn clear_cache(&mut self) {
3491        self.cache.clear();
3492        self.cache_hits = 0;
3493        self.cache_misses = 0;
3494    }
3495
3496    /// Returns the number of cache hits.
3497    #[must_use]
3498    pub const fn cache_hits(&self) -> usize {
3499        self.cache_hits
3500    }
3501
3502    /// Returns the number of cache misses.
3503    #[must_use]
3504    pub const fn cache_misses(&self) -> usize {
3505        self.cache_misses
3506    }
3507
3508    /// Returns the cache hit ratio (0.0 to 1.0).
3509    #[must_use]
3510    pub fn hit_ratio(&self) -> f64 {
3511        let total = self.cache_hits + self.cache_misses;
3512        if total == 0 {
3513            0.0
3514        } else {
3515            self.cache_hits as f64 / total as f64
3516        }
3517    }
3518}
3519
3520// ==================================================
3521// Parallel Condition Evaluation (requires "parallel" feature)
3522// ==================================================
3523
3524#[cfg(feature = "parallel")]
3525use rayon::prelude::*;
3526
3527impl Condition {
3528    /// Evaluates this condition with parallel processing for independent conditions.
3529    ///
3530    /// When the `parallel` feature is enabled, this method will evaluate independent
3531    /// And/Or branches in parallel for better performance on multi-core systems.
3532    ///
3533    /// # Examples
3534    ///
3535    /// ```no_run
3536    /// use legalis_core::{Condition, ComparisonOp, EvaluationContext, RegionType, RelationshipType, DurationUnit};
3537    /// use chrono::NaiveDate;
3538    ///
3539    /// struct MyContext { age: u32, income: u64 }
3540    ///
3541    /// impl EvaluationContext for MyContext {
3542    ///     fn get_attribute(&self, _key: &str) -> Option<String> { None }
3543    ///     fn get_age(&self) -> Option<u32> { Some(self.age) }
3544    ///     fn get_income(&self) -> Option<u64> { Some(self.income) }
3545    ///     fn get_current_date(&self) -> Option<NaiveDate> { None }
3546    ///     fn check_geographic(&self, _region_type: RegionType, _region_id: &str) -> bool { false }
3547    ///     fn check_relationship(&self, _relationship_type: RelationshipType, _target_id: Option<&str>) -> bool { false }
3548    ///     fn get_residency_months(&self) -> Option<u32> { None }
3549    ///     fn get_duration(&self, _unit: DurationUnit) -> Option<u32> { None }
3550    ///     fn get_percentage(&self, _context: &str) -> Option<u32> { None }
3551    ///     fn evaluate_formula(&self, _formula: &str) -> Option<f64> { None }
3552    /// }
3553    ///
3554    /// let ctx = MyContext { age: 25, income: 45000 };
3555    ///
3556    /// // Complex condition with multiple independent checks
3557    /// let condition = Condition::age(ComparisonOp::GreaterOrEqual, 18)
3558    ///     .and(Condition::income(ComparisonOp::LessThan, 50000));
3559    ///
3560    /// // Evaluates branches in parallel when possible
3561    /// let result = condition.evaluate_parallel(&ctx);
3562    /// ```
3563    #[cfg(feature = "parallel")]
3564    pub fn evaluate_parallel<C: EvaluationContext + Sync>(
3565        &self,
3566        context: &C,
3567    ) -> Result<bool, EvaluationError> {
3568        self.evaluate_parallel_with_depth(context, 0, 100)
3569    }
3570
3571    #[cfg(feature = "parallel")]
3572    #[allow(clippy::too_many_lines)]
3573    fn evaluate_parallel_with_depth<C: EvaluationContext + Sync>(
3574        &self,
3575        context: &C,
3576        depth: usize,
3577        max_depth: usize,
3578    ) -> Result<bool, EvaluationError> {
3579        if depth > max_depth {
3580            return Err(EvaluationError::MaxDepthExceeded { max_depth });
3581        }
3582
3583        match self {
3584            // For simple conditions, use sequential evaluation
3585            Self::Age { .. }
3586            | Self::Income { .. }
3587            | Self::HasAttribute { .. }
3588            | Self::AttributeEquals { .. }
3589            | Self::DateRange { .. }
3590            | Self::Geographic { .. }
3591            | Self::EntityRelationship { .. }
3592            | Self::ResidencyDuration { .. }
3593            | Self::Duration { .. }
3594            | Self::Percentage { .. }
3595            | Self::SetMembership { .. }
3596            | Self::Pattern { .. }
3597            | Self::Calculation { .. }
3598            | Self::Threshold { .. }
3599            | Self::Fuzzy { .. }
3600            | Self::Temporal { .. }
3601            | Self::Custom { .. } => {
3602                // Delegate to sequential evaluation
3603                self.evaluate(context)
3604            }
3605
3606            // Composite and Probabilistic have nested conditions, evaluate them recursively
3607            Self::Composite {
3608                conditions,
3609                threshold,
3610            } => {
3611                // Evaluate all conditions in parallel
3612                let results: Vec<_> = conditions
3613                    .par_iter()
3614                    .map(|(weight, cond)| {
3615                        cond.evaluate_parallel_with_depth(context, depth + 1, max_depth)
3616                            .map(|satisfied| if satisfied { *weight } else { 0.0 })
3617                    })
3618                    .collect();
3619
3620                // Check for errors
3621                for result in &results {
3622                    if let Err(e) = result {
3623                        return Err(e.clone());
3624                    }
3625                }
3626
3627                // Sum up the scores
3628                let total_score: f64 = results.iter().filter_map(|r| r.as_ref().ok()).sum();
3629                Ok(total_score >= *threshold)
3630            }
3631
3632            Self::Probabilistic {
3633                condition,
3634                probability,
3635                threshold,
3636            } => {
3637                let satisfied =
3638                    condition.evaluate_parallel_with_depth(context, depth + 1, max_depth)?;
3639                let effective_probability = if satisfied { *probability } else { 0.0 };
3640                Ok(effective_probability >= *threshold)
3641            }
3642
3643            // Parallel evaluation for compound conditions
3644            Self::And(left, right) => {
3645                // Evaluate both sides in parallel
3646                let (left_result, right_result) = rayon::join(
3647                    || left.evaluate_parallel_with_depth(context, depth + 1, max_depth),
3648                    || right.evaluate_parallel_with_depth(context, depth + 1, max_depth),
3649                );
3650
3651                // Both must succeed and be true
3652                match (left_result, right_result) {
3653                    (Ok(true), Ok(true)) => Ok(true),
3654                    (Ok(false), _) | (_, Ok(false)) => Ok(false),
3655                    (Err(e), _) | (_, Err(e)) => Err(e),
3656                }
3657            }
3658
3659            Self::Or(left, right) => {
3660                // Evaluate both sides in parallel
3661                let (left_result, right_result) = rayon::join(
3662                    || left.evaluate_parallel_with_depth(context, depth + 1, max_depth),
3663                    || right.evaluate_parallel_with_depth(context, depth + 1, max_depth),
3664                );
3665
3666                // Either can be true
3667                match (left_result, right_result) {
3668                    (Ok(true), _) | (_, Ok(true)) => Ok(true),
3669                    (Ok(false), Ok(false)) => Ok(false),
3670                    (Err(e), _) | (_, Err(e)) => Err(e),
3671                }
3672            }
3673
3674            Self::Not(inner) => {
3675                let result = inner.evaluate_parallel_with_depth(context, depth + 1, max_depth)?;
3676                Ok(!result)
3677            }
3678        }
3679    }
3680
3681    /// Evaluates a collection of conditions in parallel.
3682    ///
3683    /// This is useful when you have multiple independent conditions to evaluate
3684    /// and want to leverage parallel processing.
3685    ///
3686    /// # Examples
3687    ///
3688    /// ```no_run
3689    /// use legalis_core::{Condition, ComparisonOp, EvaluationContext, RegionType, RelationshipType, DurationUnit};
3690    /// use chrono::NaiveDate;
3691    ///
3692    /// struct MyContext { age: u32, income: u64 }
3693    ///
3694    /// impl EvaluationContext for MyContext {
3695    ///     fn get_attribute(&self, _key: &str) -> Option<String> { None }
3696    ///     fn get_age(&self) -> Option<u32> { Some(self.age) }
3697    ///     fn get_income(&self) -> Option<u64> { Some(self.income) }
3698    ///     fn get_current_date(&self) -> Option<NaiveDate> { None }
3699    ///     fn check_geographic(&self, _region_type: RegionType, _region_id: &str) -> bool { false }
3700    ///     fn check_relationship(&self, _relationship_type: RelationshipType, _target_id: Option<&str>) -> bool { false }
3701    ///     fn get_residency_months(&self) -> Option<u32> { None }
3702    ///     fn get_duration(&self, _unit: DurationUnit) -> Option<u32> { None }
3703    ///     fn get_percentage(&self, _context: &str) -> Option<u32> { None }
3704    ///     fn evaluate_formula(&self, _formula: &str) -> Option<f64> { None }
3705    /// }
3706    ///
3707    /// let ctx = MyContext { age: 25, income: 45000 };
3708    /// let conditions = vec![
3709    ///     Condition::age(ComparisonOp::GreaterOrEqual, 18),
3710    ///     Condition::income(ComparisonOp::LessThan, 50000),
3711    /// ];
3712    ///
3713    /// let results = Condition::evaluate_all_parallel(&conditions, &ctx);
3714    /// assert_eq!(results.len(), 2);
3715    /// ```
3716    #[cfg(feature = "parallel")]
3717    pub fn evaluate_all_parallel<C: EvaluationContext + Sync>(
3718        conditions: &[Condition],
3719        context: &C,
3720    ) -> Vec<Result<bool, EvaluationError>> {
3721        conditions
3722            .par_iter()
3723            .map(|cond| cond.evaluate_parallel(context))
3724            .collect()
3725    }
3726}
3727
3728/// Parallel evaluation support for ConditionEvaluator.
3729#[cfg(feature = "parallel")]
3730impl ConditionEvaluator {
3731    /// Evaluates a condition with memoization and parallel processing.
3732    ///
3733    /// Note: The cache is not thread-safe, so this method requires mutable access.
3734    /// For truly concurrent evaluation, use separate evaluators per thread.
3735    pub fn evaluate_parallel<C: EvaluationContext + Sync>(
3736        &mut self,
3737        condition: &Condition,
3738        context: &C,
3739    ) -> Result<bool, EvaluationError> {
3740        let cache_key = format!("{}", condition);
3741
3742        if let Some(&result) = self.cache.get(&cache_key) {
3743            self.cache_hits += 1;
3744            return Ok(result);
3745        }
3746
3747        self.cache_misses += 1;
3748        let result = condition.evaluate_parallel(context)?;
3749        self.cache.insert(cache_key, result);
3750        Ok(result)
3751    }
3752}
3753
3754/// Parallel evaluation support for EntailmentEngine.
3755#[cfg(feature = "parallel")]
3756impl EntailmentEngine {
3757    /// Determines what legal effects follow using parallel evaluation.
3758    ///
3759    /// This method evaluates all statutes in parallel for improved performance
3760    /// on multi-core systems.
3761    pub fn entail_parallel<C: EvaluationContext + Sync>(
3762        &self,
3763        context: &C,
3764    ) -> Vec<EntailmentResult> {
3765        self.statutes
3766            .par_iter()
3767            .map(|statute| self.apply_statute_parallel(statute, context))
3768            .collect()
3769    }
3770
3771    /// Determines what legal effects follow (parallel), filtering to only satisfied statutes.
3772    pub fn entail_satisfied_parallel<C: EvaluationContext + Sync>(
3773        &self,
3774        context: &C,
3775    ) -> Vec<EntailmentResult> {
3776        self.entail_parallel(context)
3777            .into_par_iter()
3778            .filter(|result| result.conditions_satisfied)
3779            .collect()
3780    }
3781
3782    fn apply_statute_parallel<C: EvaluationContext + Sync>(
3783        &self,
3784        statute: &Statute,
3785        context: &C,
3786    ) -> EntailmentResult {
3787        let mut errors = Vec::new();
3788
3789        if statute.preconditions.is_empty() {
3790            return EntailmentResult {
3791                statute_id: statute.id.clone(),
3792                effect: statute.effect.clone(),
3793                conditions_satisfied: true,
3794                errors: Vec::new(),
3795            };
3796        }
3797
3798        // Evaluate all preconditions in parallel
3799        let results: Vec<_> = statute
3800            .preconditions
3801            .par_iter()
3802            .map(|condition| condition.evaluate_parallel(context))
3803            .collect();
3804
3805        let all_satisfied = results.iter().all(|r| matches!(r, Ok(true)));
3806
3807        for result in results {
3808            if let Err(e) = result {
3809                errors.push(format!("{}", e));
3810            }
3811        }
3812
3813        EntailmentResult {
3814            statute_id: statute.id.clone(),
3815            effect: statute.effect.clone(),
3816            conditions_satisfied: all_satisfied,
3817            errors,
3818        }
3819    }
3820}
3821
3822// ==================================================
3823// Statute Subsumption Checking
3824// ==================================================
3825
3826/// Subsumption analyzer for determining if one statute subsumes another.
3827///
3828/// In legal reasoning, statute A subsumes statute B if:
3829/// - A and B have the same legal effect
3830/// - B's conditions are more specific than (or equal to) A's conditions
3831/// - Whenever B applies, A also applies (but not necessarily vice versa)
3832///
3833/// # Examples
3834///
3835/// ```
3836/// use legalis_core::{Statute, Effect, Condition, ComparisonOp, SubsumptionAnalyzer};
3837///
3838/// // General statute: anyone over 18 can vote
3839/// let general = Statute::new("vote-general", "Voting Rights", Effect::grant("vote"))
3840///     .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18));
3841///
3842/// // Specific statute: citizens over 18 can vote (more specific)
3843/// let specific = Statute::new("vote-citizen", "Citizen Voting", Effect::grant("vote"))
3844///     .with_precondition(
3845///         Condition::age(ComparisonOp::GreaterOrEqual, 18)
3846///             .and(Condition::has_attribute("citizenship"))
3847///     );
3848///
3849/// // General subsumes specific
3850/// assert!(SubsumptionAnalyzer::subsumes(&general, &specific));
3851/// assert!(!SubsumptionAnalyzer::subsumes(&specific, &general));
3852/// ```
3853pub struct SubsumptionAnalyzer;
3854
3855impl SubsumptionAnalyzer {
3856    /// Checks if statute A subsumes statute B.
3857    ///
3858    /// Returns `true` if B is more specific than A (A subsumes B).
3859    #[must_use]
3860    pub fn subsumes(a: &Statute, b: &Statute) -> bool {
3861        // Must have compatible effects
3862        if !Self::effects_compatible(&a.effect, &b.effect) {
3863            return false;
3864        }
3865
3866        // If A has no preconditions, it subsumes everything with the same effect
3867        if a.preconditions.is_empty() {
3868            return true;
3869        }
3870
3871        // If A has preconditions but B doesn't, A doesn't subsume B
3872        if b.preconditions.is_empty() {
3873            return false;
3874        }
3875
3876        // Check if B's preconditions are more specific than A's
3877        Self::conditions_subsume(&a.preconditions, &b.preconditions)
3878    }
3879
3880    /// Checks if effects are compatible for subsumption.
3881    fn effects_compatible(a: &Effect, b: &Effect) -> bool {
3882        // For basic subsumption, effects must be identical
3883        a.effect_type == b.effect_type && a.description == b.description
3884    }
3885
3886    /// Checks if condition set A subsumes condition set B.
3887    ///
3888    /// Returns `true` if B is more specific (adds more constraints).
3889    fn conditions_subsume(a_conds: &[Condition], b_conds: &[Condition]) -> bool {
3890        // If B has more conditions than A, it might be more specific
3891        // We need to check if all of A's conditions are present in B
3892
3893        for a_cond in a_conds {
3894            if !Self::condition_present_in(a_cond, b_conds) {
3895                return false;
3896            }
3897        }
3898
3899        true
3900    }
3901
3902    /// Checks if a single condition from A is present (or implied) in B's conditions.
3903    fn condition_present_in(a_cond: &Condition, b_conds: &[Condition]) -> bool {
3904        // Direct match
3905        if b_conds
3906            .iter()
3907            .any(|b_cond| Self::conditions_equivalent(a_cond, b_cond))
3908        {
3909            return true;
3910        }
3911
3912        // Check if any of B's conditions are stricter versions of A's condition
3913        if b_conds
3914            .iter()
3915            .any(|b_cond| Self::condition_subsumes_condition(a_cond, b_cond))
3916        {
3917            return true;
3918        }
3919
3920        // Check if the condition is present in a compound condition
3921        for b_cond in b_conds {
3922            if Self::condition_in_compound(a_cond, b_cond) {
3923                return true;
3924            }
3925        }
3926
3927        false
3928    }
3929
3930    /// Checks if condition A subsumes condition B (B is stricter than A).
3931    fn condition_subsumes_condition(a: &Condition, b: &Condition) -> bool {
3932        match (a, b) {
3933            // Age subsumption: age >= 18 subsumes age >= 21
3934            (
3935                Condition::Age {
3936                    operator: op_a,
3937                    value: val_a,
3938                },
3939                Condition::Age {
3940                    operator: op_b,
3941                    value: val_b,
3942                },
3943            ) => {
3944                match (op_a, op_b) {
3945                    // >= subsumes >= if B's value is higher
3946                    (ComparisonOp::GreaterOrEqual, ComparisonOp::GreaterOrEqual) => val_b >= val_a,
3947                    // <= subsumes <= if B's value is lower
3948                    (ComparisonOp::LessOrEqual, ComparisonOp::LessOrEqual) => val_b <= val_a,
3949                    _ => false,
3950                }
3951            }
3952
3953            // Income subsumption
3954            (
3955                Condition::Income {
3956                    operator: op_a,
3957                    value: val_a,
3958                },
3959                Condition::Income {
3960                    operator: op_b,
3961                    value: val_b,
3962                },
3963            ) => match (op_a, op_b) {
3964                (ComparisonOp::LessThan, ComparisonOp::LessThan) => val_b <= val_a,
3965                (ComparisonOp::GreaterThan, ComparisonOp::GreaterThan) => val_b >= val_a,
3966                _ => false,
3967            },
3968
3969            // Percentage subsumption
3970            (
3971                Condition::Percentage {
3972                    operator: op_a,
3973                    value: val_a,
3974                    context: ctx_a,
3975                },
3976                Condition::Percentage {
3977                    operator: op_b,
3978                    value: val_b,
3979                    context: ctx_b,
3980                },
3981            ) => {
3982                if ctx_a != ctx_b {
3983                    return false;
3984                }
3985                match (op_a, op_b) {
3986                    (ComparisonOp::GreaterOrEqual, ComparisonOp::GreaterOrEqual) => val_b >= val_a,
3987                    (ComparisonOp::LessOrEqual, ComparisonOp::LessOrEqual) => val_b <= val_a,
3988                    _ => false,
3989                }
3990            }
3991
3992            // Compound conditions
3993            (Condition::And(a_left, a_right), Condition::And(b_left, b_right)) => {
3994                Self::condition_subsumes_condition(a_left, b_left)
3995                    && Self::condition_subsumes_condition(a_right, b_right)
3996            }
3997
3998            _ => false,
3999        }
4000    }
4001
4002    /// Checks if two conditions are logically equivalent.
4003    fn conditions_equivalent(a: &Condition, b: &Condition) -> bool {
4004        match (a, b) {
4005            (
4006                Condition::Age {
4007                    operator: op_a,
4008                    value: val_a,
4009                },
4010                Condition::Age {
4011                    operator: op_b,
4012                    value: val_b,
4013                },
4014            ) => op_a == op_b && val_a == val_b,
4015            (
4016                Condition::Income {
4017                    operator: op_a,
4018                    value: val_a,
4019                },
4020                Condition::Income {
4021                    operator: op_b,
4022                    value: val_b,
4023                },
4024            ) => op_a == op_b && val_a == val_b,
4025            (Condition::HasAttribute { key: key_a }, Condition::HasAttribute { key: key_b }) => {
4026                key_a == key_b
4027            }
4028            (
4029                Condition::AttributeEquals {
4030                    key: key_a,
4031                    value: val_a,
4032                },
4033                Condition::AttributeEquals {
4034                    key: key_b,
4035                    value: val_b,
4036                },
4037            ) => key_a == key_b && val_a == val_b,
4038            (
4039                Condition::Geographic {
4040                    region_type: rt_a,
4041                    region_id: rid_a,
4042                },
4043                Condition::Geographic {
4044                    region_type: rt_b,
4045                    region_id: rid_b,
4046                },
4047            ) => rt_a == rt_b && rid_a == rid_b,
4048            _ => false,
4049        }
4050    }
4051
4052    /// Checks if a condition appears within a compound condition.
4053    fn condition_in_compound(target: &Condition, compound: &Condition) -> bool {
4054        match compound {
4055            Condition::And(left, right) | Condition::Or(left, right) => {
4056                Self::conditions_equivalent(target, left)
4057                    || Self::conditions_equivalent(target, right)
4058                    || Self::condition_subsumes_condition(target, left)
4059                    || Self::condition_subsumes_condition(target, right)
4060                    || Self::condition_in_compound(target, left)
4061                    || Self::condition_in_compound(target, right)
4062            }
4063            Condition::Not(inner) => {
4064                Self::conditions_equivalent(target, inner)
4065                    || Self::condition_in_compound(target, inner)
4066            }
4067            _ => false,
4068        }
4069    }
4070
4071    /// Finds all statutes that are subsumed by the given statute.
4072    ///
4073    /// Returns statutes that are more specific than the given statute.
4074    #[must_use]
4075    pub fn find_subsumed<'a>(statute: &Statute, candidates: &'a [Statute]) -> Vec<&'a Statute> {
4076        candidates
4077            .iter()
4078            .filter(|candidate| candidate.id != statute.id && Self::subsumes(statute, candidate))
4079            .collect()
4080    }
4081
4082    /// Finds all statutes that subsume the given statute.
4083    ///
4084    /// Returns statutes that are more general than the given statute.
4085    #[must_use]
4086    pub fn find_subsuming<'a>(statute: &Statute, candidates: &'a [Statute]) -> Vec<&'a Statute> {
4087        candidates
4088            .iter()
4089            .filter(|candidate| candidate.id != statute.id && Self::subsumes(candidate, statute))
4090            .collect()
4091    }
4092}
4093
4094// ==================================================
4095// Legal Entailment Engine
4096// ==================================================
4097
4098/// Result of applying a statute in the entailment process.
4099#[derive(Debug, Clone, PartialEq)]
4100#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
4101#[cfg_attr(feature = "schema", derive(JsonSchema))]
4102pub struct EntailmentResult {
4103    /// The statute that was applied
4104    pub statute_id: String,
4105    /// The effect that was produced
4106    pub effect: Effect,
4107    /// Whether all preconditions were satisfied
4108    pub conditions_satisfied: bool,
4109    /// Evaluation errors if any
4110    pub errors: Vec<String>,
4111}
4112
4113/// Legal entailment engine that determines what conclusions follow from statutes and facts.
4114///
4115/// Given a set of statutes and an evaluation context, the entailment engine:
4116/// 1. Evaluates each statute's preconditions
4117/// 2. Applies statutes whose conditions are met
4118/// 3. Returns the resulting legal effects
4119///
4120/// # Examples
4121///
4122/// ```
4123/// use legalis_core::{Statute, Effect, Condition, ComparisonOp};
4124/// use legalis_core::{EntailmentEngine, AttributeBasedContext};
4125/// use std::collections::HashMap;
4126///
4127/// let voting_statute = Statute::new("vote", "Voting Rights", Effect::grant("vote"))
4128///     .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18));
4129///
4130/// let tax_credit = Statute::new("tax", "Tax Credit", Effect::grant("tax_credit"))
4131///     .with_precondition(Condition::income(ComparisonOp::LessThan, 50000));
4132///
4133/// let mut attributes = HashMap::new();
4134/// attributes.insert("age".to_string(), "25".to_string());
4135/// attributes.insert("income".to_string(), "45000".to_string());
4136/// let context = AttributeBasedContext::new(attributes);
4137///
4138/// let statutes = vec![voting_statute, tax_credit];
4139/// let engine = EntailmentEngine::new(statutes);
4140/// let results = engine.entail(&context);
4141///
4142/// // Both statutes apply
4143/// assert_eq!(results.len(), 2);
4144/// assert!(results.iter().all(|r| r.conditions_satisfied));
4145/// ```
4146#[derive(Debug, Clone)]
4147pub struct EntailmentEngine {
4148    statutes: Vec<Statute>,
4149}
4150
4151impl EntailmentEngine {
4152    /// Creates a new entailment engine with the given statutes.
4153    #[must_use]
4154    pub fn new(statutes: Vec<Statute>) -> Self {
4155        Self { statutes }
4156    }
4157
4158    /// Determines what legal effects follow from the statutes given the context.
4159    ///
4160    /// Returns all applicable effects where preconditions are satisfied.
4161    pub fn entail(&self, context: &AttributeBasedContext) -> Vec<EntailmentResult> {
4162        self.statutes
4163            .iter()
4164            .map(|statute| self.apply_statute(statute, context))
4165            .collect()
4166    }
4167
4168    /// Determines what legal effects follow, filtering to only satisfied statutes.
4169    ///
4170    /// Returns only the effects where all preconditions are met.
4171    pub fn entail_satisfied(&self, context: &AttributeBasedContext) -> Vec<EntailmentResult> {
4172        self.entail(context)
4173            .into_iter()
4174            .filter(|result| result.conditions_satisfied)
4175            .collect()
4176    }
4177
4178    /// Applies a single statute and returns the result.
4179    fn apply_statute(
4180        &self,
4181        statute: &Statute,
4182        context: &AttributeBasedContext,
4183    ) -> EntailmentResult {
4184        let mut errors = Vec::new();
4185        let mut all_satisfied = true;
4186
4187        // If no preconditions, statute always applies
4188        if statute.preconditions.is_empty() {
4189            return EntailmentResult {
4190                statute_id: statute.id.clone(),
4191                effect: statute.effect.clone(),
4192                conditions_satisfied: true,
4193                errors: Vec::new(),
4194            };
4195        }
4196
4197        // Evaluate all preconditions
4198        for condition in &statute.preconditions {
4199            match condition.evaluate_simple(context) {
4200                Ok(true) => {
4201                    // Condition satisfied
4202                }
4203                Ok(false) => {
4204                    all_satisfied = false;
4205                }
4206                Err(e) => {
4207                    all_satisfied = false;
4208                    errors.push(format!("{}", e));
4209                }
4210            }
4211        }
4212
4213        EntailmentResult {
4214            statute_id: statute.id.clone(),
4215            effect: statute.effect.clone(),
4216            conditions_satisfied: all_satisfied,
4217            errors,
4218        }
4219    }
4220
4221    /// Adds a statute to the engine.
4222    pub fn add_statute(&mut self, statute: Statute) {
4223        self.statutes.push(statute);
4224    }
4225
4226    /// Removes a statute by ID.
4227    pub fn remove_statute(&mut self, statute_id: &str) -> bool {
4228        let original_len = self.statutes.len();
4229        self.statutes.retain(|s| s.id != statute_id);
4230        self.statutes.len() < original_len
4231    }
4232
4233    /// Returns a reference to all statutes in the engine.
4234    #[must_use]
4235    pub fn statutes(&self) -> &[Statute] {
4236        &self.statutes
4237    }
4238
4239    /// Returns the number of statutes in the engine.
4240    #[must_use]
4241    pub fn statute_count(&self) -> usize {
4242        self.statutes.len()
4243    }
4244
4245    /// Checks if a statute exists in the engine by ID.
4246    #[must_use]
4247    pub fn has_statute(&self, statute_id: &str) -> bool {
4248        self.statutes.iter().any(|s| s.id == statute_id)
4249    }
4250}
4251
4252/// Inference step in legal reasoning chains.
4253#[derive(Debug, Clone, PartialEq)]
4254#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
4255#[cfg_attr(feature = "schema", derive(JsonSchema))]
4256pub struct InferenceStep {
4257    /// The statute applied in this step
4258    pub statute_id: String,
4259    /// The effect produced
4260    pub effect: Effect,
4261    /// Previous steps this inference depends on
4262    pub depends_on: Vec<usize>,
4263}
4264
4265/// Forward chaining entailment with multi-step inference.
4266///
4267/// This engine can perform multi-step legal reasoning, where the effects
4268/// of one statute can enable the conditions of another statute.
4269///
4270/// # Examples
4271///
4272/// ```
4273/// use legalis_core::{Statute, Effect, Condition, ComparisonOp};
4274/// use legalis_core::{ForwardChainingEngine, AttributeBasedContext};
4275/// use std::collections::HashMap;
4276///
4277/// // Step 1: Being 18+ grants eligibility
4278/// let eligibility = Statute::new("eligibility", "Eligibility", Effect::grant("eligible"))
4279///     .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18));
4280///
4281/// // Step 2: Having eligibility grants voting rights
4282/// let voting = Statute::new("voting", "Voting", Effect::grant("vote"))
4283///     .with_precondition(Condition::has_attribute("eligible"));
4284///
4285/// let mut attributes = HashMap::new();
4286/// attributes.insert("age".to_string(), "25".to_string());
4287/// let context = AttributeBasedContext::new(attributes);
4288///
4289/// let statutes = vec![eligibility, voting];
4290/// let engine = ForwardChainingEngine::new(statutes);
4291/// let chain = engine.infer(&context, 5);
4292///
4293/// // Should derive both eligibility and voting rights
4294/// assert!(!chain.is_empty());
4295/// ```
4296#[derive(Debug, Clone)]
4297pub struct ForwardChainingEngine {
4298    statutes: Vec<Statute>,
4299}
4300
4301impl ForwardChainingEngine {
4302    /// Creates a new forward chaining engine.
4303    #[must_use]
4304    pub fn new(statutes: Vec<Statute>) -> Self {
4305        Self { statutes }
4306    }
4307
4308    /// Performs forward chaining inference up to max_steps.
4309    ///
4310    /// Returns the chain of inferences that can be derived.
4311    pub fn infer(&self, context: &AttributeBasedContext, max_steps: usize) -> Vec<InferenceStep> {
4312        let mut inferences = Vec::new();
4313        let mut changed = true;
4314        let mut steps = 0;
4315
4316        while changed && steps < max_steps {
4317            changed = false;
4318            steps += 1;
4319
4320            for statute in &self.statutes {
4321                // Skip if already inferred
4322                if inferences
4323                    .iter()
4324                    .any(|inf: &InferenceStep| inf.statute_id == statute.id)
4325                {
4326                    continue;
4327                }
4328
4329                // Check if conditions are met
4330                if self.can_apply_statute(statute, context) {
4331                    let depends_on = self.find_dependencies(&inferences, statute);
4332
4333                    inferences.push(InferenceStep {
4334                        statute_id: statute.id.clone(),
4335                        effect: statute.effect.clone(),
4336                        depends_on,
4337                    });
4338
4339                    changed = true;
4340                }
4341            }
4342        }
4343
4344        inferences
4345    }
4346
4347    /// Checks if a statute's conditions can be applied given the current context.
4348    fn can_apply_statute(&self, statute: &Statute, context: &AttributeBasedContext) -> bool {
4349        if statute.preconditions.is_empty() {
4350            return true;
4351        }
4352
4353        statute
4354            .preconditions
4355            .iter()
4356            .all(|cond| cond.evaluate_simple(context).unwrap_or(false))
4357    }
4358
4359    /// Finds which previous inferences this statute depends on.
4360    fn find_dependencies(&self, _inferences: &[InferenceStep], _statute: &Statute) -> Vec<usize> {
4361        // For now, return empty dependencies
4362        // TODO: Implement dependency tracking based on which effects enable conditions
4363        Vec::new()
4364    }
4365}
4366
4367impl fmt::Display for EntailmentResult {
4368    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4369        write!(
4370            f,
4371            "{}: {} (satisfied: {})",
4372            self.statute_id, self.effect, self.conditions_satisfied
4373        )
4374    }
4375}
4376
4377// ==================================================
4378// Abductive Reasoning Engine for Legal Outcome Explanation
4379// ==================================================
4380
4381/// Explanation for why a legal outcome occurred.
4382#[derive(Debug, Clone, PartialEq)]
4383#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
4384#[cfg_attr(feature = "schema", derive(JsonSchema))]
4385pub struct LegalExplanation {
4386    /// The observed outcome being explained
4387    pub outcome: Effect,
4388    /// Statutes that contributed to this outcome
4389    pub applicable_statutes: Vec<String>,
4390    /// Conditions that were satisfied
4391    pub satisfied_conditions: Vec<String>,
4392    /// Conditions that were not satisfied
4393    pub unsatisfied_conditions: Vec<String>,
4394    /// Confidence score (0.0 to 1.0)
4395    pub confidence: f64,
4396    /// Step-by-step reasoning chain
4397    pub reasoning_chain: Vec<ReasoningStep>,
4398}
4399
4400/// A single step in the reasoning chain.
4401#[derive(Debug, Clone, PartialEq)]
4402#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
4403#[cfg_attr(feature = "schema", derive(JsonSchema))]
4404pub struct ReasoningStep {
4405    /// Step number
4406    pub step: usize,
4407    /// Description of this reasoning step
4408    pub description: String,
4409    /// Statute ID involved in this step
4410    pub statute_id: Option<String>,
4411    /// Condition evaluated in this step
4412    pub condition: Option<String>,
4413    /// Result of this step
4414    pub result: StepResult,
4415}
4416
4417/// Result of a reasoning step.
4418#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4419#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
4420#[cfg_attr(feature = "schema", derive(JsonSchema))]
4421pub enum StepResult {
4422    /// Condition was satisfied
4423    Satisfied,
4424    /// Condition was not satisfied
4425    NotSatisfied,
4426    /// Statute was applied
4427    Applied,
4428    /// Statute was not applicable
4429    NotApplicable,
4430    /// Uncertain result
4431    Uncertain,
4432}
4433
4434impl fmt::Display for StepResult {
4435    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4436        match self {
4437            Self::Satisfied => write!(f, "✓ satisfied"),
4438            Self::NotSatisfied => write!(f, "✗ not satisfied"),
4439            Self::Applied => write!(f, "→ applied"),
4440            Self::NotApplicable => write!(f, "- not applicable"),
4441            Self::Uncertain => write!(f, "? uncertain"),
4442        }
4443    }
4444}
4445
4446impl fmt::Display for LegalExplanation {
4447    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4448        writeln!(f, "Explanation for outcome: {}", self.outcome)?;
4449        writeln!(f, "Confidence: {:.0}%", self.confidence * 100.0)?;
4450        writeln!(f)?;
4451
4452        if !self.applicable_statutes.is_empty() {
4453            writeln!(f, "Applicable statutes:")?;
4454            for statute_id in &self.applicable_statutes {
4455                writeln!(f, "  - {}", statute_id)?;
4456            }
4457            writeln!(f)?;
4458        }
4459
4460        if !self.satisfied_conditions.is_empty() {
4461            writeln!(f, "Satisfied conditions:")?;
4462            for condition in &self.satisfied_conditions {
4463                writeln!(f, "  ✓ {}", condition)?;
4464            }
4465            writeln!(f)?;
4466        }
4467
4468        if !self.unsatisfied_conditions.is_empty() {
4469            writeln!(f, "Unsatisfied conditions:")?;
4470            for condition in &self.unsatisfied_conditions {
4471                writeln!(f, "  ✗ {}", condition)?;
4472            }
4473            writeln!(f)?;
4474        }
4475
4476        if !self.reasoning_chain.is_empty() {
4477            writeln!(f, "Reasoning chain:")?;
4478            for step in &self.reasoning_chain {
4479                write!(f, "  {}. {} ", step.step, step.description)?;
4480                writeln!(f, "[{}]", step.result)?;
4481            }
4482        }
4483
4484        Ok(())
4485    }
4486}
4487
4488/// Abductive reasoning engine for explaining legal outcomes.
4489///
4490/// This engine works backwards from an observed outcome to determine which
4491/// statutes and conditions led to that outcome.
4492///
4493/// # Examples
4494///
4495/// ```
4496/// use legalis_core::{AbductiveReasoner, Statute, Effect, EffectType, Condition, ComparisonOp};
4497/// use legalis_core::{EvaluationContext, RegionType, RelationshipType, DurationUnit};
4498/// use chrono::NaiveDate;
4499///
4500/// struct Person { age: u32, income: u64 }
4501///
4502/// impl EvaluationContext for Person {
4503///     fn get_attribute(&self, _key: &str) -> Option<String> { None }
4504///     fn get_age(&self) -> Option<u32> { Some(self.age) }
4505///     fn get_income(&self) -> Option<u64> { Some(self.income) }
4506///     fn get_current_date(&self) -> Option<NaiveDate> { None }
4507///     fn check_geographic(&self, _region_type: RegionType, _region_id: &str) -> bool { false }
4508///     fn check_relationship(&self, _relationship_type: RelationshipType, _target_id: Option<&str>) -> bool { false }
4509///     fn get_residency_months(&self) -> Option<u32> { None }
4510///     fn get_duration(&self, _unit: DurationUnit) -> Option<u32> { None }
4511///     fn get_percentage(&self, _context: &str) -> Option<u32> { None }
4512///     fn evaluate_formula(&self, _formula: &str) -> Option<f64> { None }
4513/// }
4514///
4515/// let voting_law = Statute::new("vote", "Voting Rights", Effect::grant("vote"))
4516///     .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18));
4517///
4518/// let statutes = vec![voting_law];
4519/// let person = Person { age: 25, income: 50000 };
4520///
4521/// let reasoner = AbductiveReasoner::new(statutes);
4522/// let explanations = reasoner.explain_effect(Effect::grant("vote"), &person);
4523///
4524/// assert!(!explanations.is_empty());
4525/// println!("{}", explanations[0]);
4526/// ```
4527#[derive(Debug, Clone)]
4528pub struct AbductiveReasoner {
4529    statutes: Vec<Statute>,
4530}
4531
4532impl AbductiveReasoner {
4533    /// Creates a new abductive reasoner with the given statutes.
4534    #[must_use]
4535    pub fn new(statutes: Vec<Statute>) -> Self {
4536        Self { statutes }
4537    }
4538
4539    /// Explains why a specific effect occurred.
4540    ///
4541    /// Returns all possible explanations ranked by confidence.
4542    pub fn explain_effect<C: EvaluationContext>(
4543        &self,
4544        target_effect: Effect,
4545        context: &C,
4546    ) -> Vec<LegalExplanation> {
4547        let mut explanations = Vec::new();
4548
4549        // Find all statutes that produce this effect
4550        for statute in &self.statutes {
4551            if self.effects_match(&statute.effect, &target_effect)
4552                && let Some(explanation) = self.explain_statute(statute, context)
4553            {
4554                explanations.push(explanation);
4555            }
4556        }
4557
4558        // Sort by confidence (highest first)
4559        explanations.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap());
4560
4561        explanations
4562    }
4563
4564    /// Explains why a specific statute was or was not applied.
4565    pub fn explain_statute<C: EvaluationContext>(
4566        &self,
4567        statute: &Statute,
4568        context: &C,
4569    ) -> Option<LegalExplanation> {
4570        let mut reasoning_chain = Vec::new();
4571        let mut satisfied_conditions = Vec::new();
4572        let mut unsatisfied_conditions = Vec::new();
4573        let mut step_num = 1;
4574
4575        // If no preconditions, statute always applies
4576        if statute.preconditions.is_empty() {
4577            reasoning_chain.push(ReasoningStep {
4578                step: step_num,
4579                description: format!("Statute '{}' has no preconditions", statute.id),
4580                statute_id: Some(statute.id.clone()),
4581                condition: None,
4582                result: StepResult::Applied,
4583            });
4584
4585            return Some(LegalExplanation {
4586                outcome: statute.effect.clone(),
4587                applicable_statutes: vec![statute.id.clone()],
4588                satisfied_conditions: vec!["No preconditions".to_string()],
4589                unsatisfied_conditions: Vec::new(),
4590                confidence: 1.0,
4591                reasoning_chain,
4592            });
4593        }
4594
4595        // Evaluate each precondition
4596        for condition in &statute.preconditions {
4597            let condition_str = format!("{}", condition);
4598
4599            match condition.evaluate(context) {
4600                Ok(true) => {
4601                    satisfied_conditions.push(condition_str.clone());
4602                    reasoning_chain.push(ReasoningStep {
4603                        step: step_num,
4604                        description: format!("Condition satisfied: {}", condition_str),
4605                        statute_id: Some(statute.id.clone()),
4606                        condition: Some(condition_str),
4607                        result: StepResult::Satisfied,
4608                    });
4609                }
4610                Ok(false) => {
4611                    unsatisfied_conditions.push(condition_str.clone());
4612                    reasoning_chain.push(ReasoningStep {
4613                        step: step_num,
4614                        description: format!("Condition not satisfied: {}", condition_str),
4615                        statute_id: Some(statute.id.clone()),
4616                        condition: Some(condition_str),
4617                        result: StepResult::NotSatisfied,
4618                    });
4619                }
4620                Err(_) => {
4621                    unsatisfied_conditions.push(condition_str.clone());
4622                    reasoning_chain.push(ReasoningStep {
4623                        step: step_num,
4624                        description: format!("Condition evaluation failed: {}", condition_str),
4625                        statute_id: Some(statute.id.clone()),
4626                        condition: Some(condition_str),
4627                        result: StepResult::Uncertain,
4628                    });
4629                }
4630            }
4631
4632            step_num += 1;
4633        }
4634
4635        // Calculate confidence based on satisfied conditions
4636        let total_conditions = statute.preconditions.len();
4637        let satisfied_count = satisfied_conditions.len();
4638        let confidence = if total_conditions > 0 {
4639            satisfied_count as f64 / total_conditions as f64
4640        } else {
4641            1.0
4642        };
4643
4644        // Determine if statute was applied
4645        let all_satisfied = unsatisfied_conditions.is_empty();
4646        let applicable_statutes = if all_satisfied {
4647            vec![statute.id.clone()]
4648        } else {
4649            Vec::new()
4650        };
4651
4652        reasoning_chain.push(ReasoningStep {
4653            step: step_num,
4654            description: if all_satisfied {
4655                format!("Statute '{}' applies", statute.id)
4656            } else {
4657                format!("Statute '{}' does not apply", statute.id)
4658            },
4659            statute_id: Some(statute.id.clone()),
4660            condition: None,
4661            result: if all_satisfied {
4662                StepResult::Applied
4663            } else {
4664                StepResult::NotApplicable
4665            },
4666        });
4667
4668        Some(LegalExplanation {
4669            outcome: statute.effect.clone(),
4670            applicable_statutes,
4671            satisfied_conditions,
4672            unsatisfied_conditions,
4673            confidence,
4674            reasoning_chain,
4675        })
4676    }
4677
4678    /// Explains why a specific outcome did NOT occur.
4679    ///
4680    /// This is useful for understanding what conditions would need to be satisfied
4681    /// for a desired outcome.
4682    pub fn explain_why_not<C: EvaluationContext>(
4683        &self,
4684        target_effect: Effect,
4685        context: &C,
4686    ) -> Vec<LegalExplanation> {
4687        let mut explanations = Vec::new();
4688
4689        // Find statutes that could produce this effect but didn't
4690        for statute in &self.statutes {
4691            if self.effects_match(&statute.effect, &target_effect)
4692                && let Some(explanation) = self.explain_statute(statute, context)
4693            {
4694                // Only include if statute didn't apply (confidence < 1.0)
4695                if explanation.confidence < 1.0 {
4696                    explanations.push(explanation);
4697                }
4698            }
4699        }
4700
4701        // Sort by how close they came (highest confidence first)
4702        explanations.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap());
4703
4704        explanations
4705    }
4706
4707    /// Checks if two effects match for explanation purposes.
4708    fn effects_match(&self, effect1: &Effect, effect2: &Effect) -> bool {
4709        effect1.effect_type == effect2.effect_type
4710            && effect1.description.contains(&effect2.description)
4711    }
4712
4713    /// Finds alternative paths to achieve an outcome.
4714    ///
4715    /// Returns explanations for all statutes that could produce the target effect,
4716    /// showing which conditions need to be satisfied for each path.
4717    pub fn find_alternatives<C: EvaluationContext>(
4718        &self,
4719        target_effect: Effect,
4720        context: &C,
4721    ) -> Vec<LegalExplanation> {
4722        let mut alternatives = Vec::new();
4723
4724        for statute in &self.statutes {
4725            if self.effects_match(&statute.effect, &target_effect)
4726                && let Some(explanation) = self.explain_statute(statute, context)
4727            {
4728                alternatives.push(explanation);
4729            }
4730        }
4731
4732        alternatives
4733    }
4734}
4735
4736// ==================================================
4737// Statute Registry Query DSL
4738// ==================================================
4739
4740/// Fluent query builder for searching and filtering statutes.
4741///
4742/// Provides a chainable API for constructing complex queries over statute collections.
4743///
4744/// # Examples
4745///
4746/// ```
4747/// use legalis_core::{Statute, Effect, StatuteQuery, Condition, ComparisonOp};
4748/// use chrono::NaiveDate;
4749///
4750/// let statutes = vec![
4751///     Statute::new("law1", "Voting Rights", Effect::grant("vote"))
4752///         .with_jurisdiction("US")
4753///         .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18)),
4754///     Statute::new("law2", "Tax Credit", Effect::grant("credit"))
4755///         .with_jurisdiction("US-CA")
4756///         .with_precondition(Condition::income(ComparisonOp::LessThan, 50000)),
4757/// ];
4758///
4759/// // Find all US statutes with preconditions
4760/// let results = StatuteQuery::new(&statutes)
4761///     .jurisdiction("US")
4762///     .with_preconditions()
4763///     .execute();
4764///
4765/// assert_eq!(results.len(), 1);
4766/// assert_eq!(results[0].id, "law1");
4767/// ```
4768pub struct StatuteQuery<'a> {
4769    statutes: &'a [Statute],
4770    #[allow(clippy::type_complexity)]
4771    filters: Vec<Box<dyn Fn(&Statute) -> bool + 'a>>,
4772}
4773
4774impl<'a> StatuteQuery<'a> {
4775    /// Creates a new query over the given statute collection.
4776    #[must_use]
4777    pub fn new(statutes: &'a [Statute]) -> Self {
4778        Self {
4779            statutes,
4780            filters: Vec::new(),
4781        }
4782    }
4783
4784    /// Filters statutes by jurisdiction.
4785    #[must_use]
4786    pub fn jurisdiction(mut self, jurisdiction: &'a str) -> Self {
4787        self.filters.push(Box::new(move |s| {
4788            s.jurisdiction.as_ref().is_some_and(|j| j == jurisdiction)
4789        }));
4790        self
4791    }
4792
4793    /// Filters statutes by jurisdiction prefix (e.g., "US" matches "US", "US-CA", "US-NY").
4794    #[must_use]
4795    pub fn jurisdiction_prefix(mut self, prefix: &'a str) -> Self {
4796        self.filters.push(Box::new(move |s| {
4797            s.jurisdiction
4798                .as_ref()
4799                .is_some_and(|j| j.starts_with(prefix))
4800        }));
4801        self
4802    }
4803
4804    /// Filters statutes by effect type.
4805    #[must_use]
4806    pub fn effect_type(mut self, effect_type: EffectType) -> Self {
4807        self.filters
4808            .push(Box::new(move |s| s.effect.effect_type == effect_type));
4809        self
4810    }
4811
4812    /// Filters statutes that grant a specific right or privilege.
4813    #[must_use]
4814    pub fn grants(mut self, description: &'a str) -> Self {
4815        self.filters.push(Box::new(move |s| {
4816            s.effect.effect_type == EffectType::Grant && s.effect.description.contains(description)
4817        }));
4818        self
4819    }
4820
4821    /// Filters statutes that revoke a specific right or privilege.
4822    #[must_use]
4823    pub fn revokes(mut self, description: &'a str) -> Self {
4824        self.filters.push(Box::new(move |s| {
4825            s.effect.effect_type == EffectType::Revoke && s.effect.description.contains(description)
4826        }));
4827        self
4828    }
4829
4830    /// Filters statutes that have preconditions.
4831    #[must_use]
4832    pub fn with_preconditions(mut self) -> Self {
4833        self.filters.push(Box::new(|s| !s.preconditions.is_empty()));
4834        self
4835    }
4836
4837    /// Filters statutes that have no preconditions (unconditional).
4838    #[must_use]
4839    pub fn unconditional(mut self) -> Self {
4840        self.filters.push(Box::new(|s| s.preconditions.is_empty()));
4841        self
4842    }
4843
4844    /// Filters statutes by minimum number of preconditions.
4845    #[must_use]
4846    pub fn min_preconditions(mut self, min: usize) -> Self {
4847        self.filters
4848            .push(Box::new(move |s| s.preconditions.len() >= min));
4849        self
4850    }
4851
4852    /// Filters statutes effective at a given date.
4853    #[must_use]
4854    pub fn effective_at(mut self, date: NaiveDate) -> Self {
4855        self.filters
4856            .push(Box::new(move |s| s.temporal_validity.is_active(date)));
4857        self
4858    }
4859
4860    /// Filters statutes that are currently effective.
4861    #[must_use]
4862    pub fn currently_effective(mut self) -> Self {
4863        let today = chrono::Utc::now().date_naive();
4864        self.filters
4865            .push(Box::new(move |s| s.temporal_validity.is_active(today)));
4866        self
4867    }
4868
4869    /// Filters statutes with a specific version.
4870    #[must_use]
4871    pub fn version(mut self, version: u32) -> Self {
4872        self.filters.push(Box::new(move |s| s.version == version));
4873        self
4874    }
4875
4876    /// Filters statutes by ID prefix.
4877    #[must_use]
4878    pub fn id_prefix(mut self, prefix: &'a str) -> Self {
4879        self.filters
4880            .push(Box::new(move |s| s.id.starts_with(prefix)));
4881        self
4882    }
4883
4884    /// Filters statutes by ID suffix.
4885    #[must_use]
4886    pub fn id_suffix(mut self, suffix: &'a str) -> Self {
4887        self.filters.push(Box::new(move |s| s.id.ends_with(suffix)));
4888        self
4889    }
4890
4891    /// Filters statutes containing a keyword in title or ID.
4892    #[must_use]
4893    pub fn keyword(mut self, keyword: &'a str) -> Self {
4894        self.filters.push(Box::new(move |s| {
4895            s.id.contains(keyword) || s.title.contains(keyword)
4896        }));
4897        self
4898    }
4899
4900    /// Filters statutes with a custom predicate.
4901    #[must_use]
4902    pub fn filter<F>(mut self, predicate: F) -> Self
4903    where
4904        F: Fn(&Statute) -> bool + 'a,
4905    {
4906        self.filters.push(Box::new(predicate));
4907        self
4908    }
4909
4910    /// Executes the query and returns matching statutes.
4911    #[must_use]
4912    pub fn execute(self) -> Vec<&'a Statute> {
4913        self.statutes
4914            .iter()
4915            .filter(|statute| self.filters.iter().all(|f| f(statute)))
4916            .collect()
4917    }
4918
4919    /// Executes the query and returns the first matching statute.
4920    #[must_use]
4921    pub fn first(self) -> Option<&'a Statute> {
4922        self.statutes
4923            .iter()
4924            .find(|statute| self.filters.iter().all(|f| f(statute)))
4925    }
4926
4927    /// Executes the query and returns the count of matching statutes.
4928    #[must_use]
4929    pub fn count(self) -> usize {
4930        self.statutes
4931            .iter()
4932            .filter(|statute| self.filters.iter().all(|f| f(statute)))
4933            .count()
4934    }
4935
4936    /// Executes the query and checks if any statutes match.
4937    #[must_use]
4938    pub fn exists(self) -> bool {
4939        self.statutes
4940            .iter()
4941            .any(|statute| self.filters.iter().all(|f| f(statute)))
4942    }
4943}
4944
4945/// Statute registry for managing collections of statutes with query capabilities.
4946///
4947/// # Examples
4948///
4949/// ```
4950/// use legalis_core::{StatuteRegistry, Statute, Effect};
4951///
4952/// let mut registry = StatuteRegistry::new();
4953/// registry.add(Statute::new("law1", "Example Law", Effect::grant("right")));
4954/// registry.add(Statute::new("law2", "Another Law", Effect::revoke("privilege")));
4955///
4956/// assert_eq!(registry.len(), 2);
4957///
4958/// // Query the registry
4959/// let grants = registry.query().effect_type(legalis_core::EffectType::Grant).execute();
4960/// assert_eq!(grants.len(), 1);
4961/// ```
4962#[derive(Debug, Clone, Default)]
4963pub struct StatuteRegistry {
4964    statutes: Vec<Statute>,
4965}
4966
4967impl StatuteRegistry {
4968    /// Creates a new empty statute registry.
4969    #[must_use]
4970    pub fn new() -> Self {
4971        Self::default()
4972    }
4973
4974    /// Creates a statute registry from a vector of statutes.
4975    #[must_use]
4976    pub fn from_statutes(statutes: Vec<Statute>) -> Self {
4977        Self { statutes }
4978    }
4979
4980    /// Adds a statute to the registry.
4981    pub fn add(&mut self, statute: Statute) {
4982        self.statutes.push(statute);
4983    }
4984
4985    /// Removes a statute by ID.
4986    ///
4987    /// Returns `true` if a statute was removed.
4988    pub fn remove(&mut self, id: &str) -> bool {
4989        let original_len = self.statutes.len();
4990        self.statutes.retain(|s| s.id != id);
4991        self.statutes.len() < original_len
4992    }
4993
4994    /// Gets a statute by ID.
4995    #[must_use]
4996    pub fn get(&self, id: &str) -> Option<&Statute> {
4997        self.statutes.iter().find(|s| s.id == id)
4998    }
4999
5000    /// Gets a mutable reference to a statute by ID.
5001    pub fn get_mut(&mut self, id: &str) -> Option<&mut Statute> {
5002        self.statutes.iter_mut().find(|s| s.id == id)
5003    }
5004
5005    /// Returns the number of statutes in the registry.
5006    #[must_use]
5007    pub fn len(&self) -> usize {
5008        self.statutes.len()
5009    }
5010
5011    /// Returns `true` if the registry is empty.
5012    #[must_use]
5013    pub fn is_empty(&self) -> bool {
5014        self.statutes.is_empty()
5015    }
5016
5017    /// Returns an iterator over all statutes.
5018    pub fn iter(&self) -> impl Iterator<Item = &Statute> {
5019        self.statutes.iter()
5020    }
5021
5022    /// Returns a mutable iterator over all statutes.
5023    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Statute> {
5024        self.statutes.iter_mut()
5025    }
5026
5027    /// Creates a new query over the statutes in this registry.
5028    #[must_use]
5029    pub fn query(&self) -> StatuteQuery<'_> {
5030        StatuteQuery::new(&self.statutes)
5031    }
5032
5033    /// Clears all statutes from the registry.
5034    pub fn clear(&mut self) {
5035        self.statutes.clear();
5036    }
5037
5038    /// Returns all statutes as a vector.
5039    #[must_use]
5040    pub fn all(&self) -> &[Statute] {
5041        &self.statutes
5042    }
5043
5044    /// Finds statutes that conflict with each other at a given date.
5045    #[must_use]
5046    pub fn find_conflicts(&self, date: NaiveDate) -> Vec<(&Statute, &Statute)> {
5047        let mut conflicts = Vec::new();
5048        let effective: Vec<_> = self
5049            .statutes
5050            .iter()
5051            .filter(|s| s.temporal_validity.is_active(date))
5052            .collect();
5053
5054        for i in 0..effective.len() {
5055            for j in (i + 1)..effective.len() {
5056                let a = effective[i];
5057                let b = effective[j];
5058
5059                // Simple conflict check: same effect type but different descriptions
5060                if a.effect.effect_type == b.effect.effect_type
5061                    && a.effect.description != b.effect.description
5062                    && !a.preconditions.is_empty()
5063                    && !b.preconditions.is_empty()
5064                {
5065                    conflicts.push((a, b));
5066                }
5067            }
5068        }
5069
5070        conflicts
5071    }
5072
5073    /// Merges another registry into this one.
5074    pub fn merge(&mut self, other: StatuteRegistry) {
5075        self.statutes.extend(other.statutes);
5076    }
5077}
5078
5079impl IntoIterator for StatuteRegistry {
5080    type Item = Statute;
5081    type IntoIter = std::vec::IntoIter<Statute>;
5082
5083    fn into_iter(self) -> Self::IntoIter {
5084        self.statutes.into_iter()
5085    }
5086}
5087
5088impl<'a> IntoIterator for &'a StatuteRegistry {
5089    type Item = &'a Statute;
5090    type IntoIter = std::slice::Iter<'a, Statute>;
5091
5092    fn into_iter(self) -> Self::IntoIter {
5093        self.statutes.iter()
5094    }
5095}
5096
5097impl FromIterator<Statute> for StatuteRegistry {
5098    fn from_iter<T: IntoIterator<Item = Statute>>(iter: T) -> Self {
5099        Self {
5100            statutes: iter.into_iter().collect(),
5101        }
5102    }
5103}
5104
5105/// Statute dependency graph for tracking relationships between statutes.
5106///
5107/// The `StatuteGraph` maintains a directed graph where nodes are statutes
5108/// and edges represent various relationships (derivation, amendments, cross-references).
5109///
5110/// # Examples
5111///
5112/// ```
5113/// use legalis_core::{StatuteGraph, Statute, Effect};
5114///
5115/// let mut graph = StatuteGraph::new();
5116///
5117/// let federal_law = Statute::new("federal-1", "Federal Law", Effect::grant("Benefit"));
5118/// let state_law = Statute::new("state-1", "State Law", Effect::grant("Benefit"))
5119///     .with_derives_from("federal-1");
5120///
5121/// graph.add_statute(federal_law);
5122/// graph.add_statute(state_law);
5123///
5124/// let derived = graph.find_derived_from("federal-1");
5125/// assert_eq!(derived.len(), 1);
5126/// ```
5127#[derive(Debug, Clone)]
5128#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5129#[cfg_attr(feature = "schema", derive(JsonSchema))]
5130pub struct StatuteGraph {
5131    /// All statutes in the graph
5132    statutes: std::collections::HashMap<String, Statute>,
5133    /// Adjacency list: statute_id -> list of related statute IDs
5134    derivation_edges: std::collections::HashMap<String, Vec<String>>,
5135}
5136
5137impl StatuteGraph {
5138    /// Creates a new empty statute graph.
5139    #[must_use]
5140    pub fn new() -> Self {
5141        Self {
5142            statutes: std::collections::HashMap::new(),
5143            derivation_edges: std::collections::HashMap::new(),
5144        }
5145    }
5146
5147    /// Adds a statute to the graph.
5148    ///
5149    /// This automatically builds derivation edges based on the statute's `derives_from` field.
5150    ///
5151    /// # Examples
5152    ///
5153    /// ```
5154    /// use legalis_core::{StatuteGraph, Statute, Effect};
5155    ///
5156    /// let mut graph = StatuteGraph::new();
5157    /// let statute = Statute::new("law-1", "Law", Effect::grant("Benefit"));
5158    /// graph.add_statute(statute);
5159    ///
5160    /// assert_eq!(graph.len(), 1);
5161    /// ```
5162    pub fn add_statute(&mut self, statute: Statute) {
5163        let id = statute.id.clone();
5164
5165        // Build derivation edges
5166        for source_id in &statute.derives_from {
5167            self.derivation_edges
5168                .entry(source_id.clone())
5169                .or_default()
5170                .push(id.clone());
5171        }
5172
5173        self.statutes.insert(id, statute);
5174    }
5175
5176    /// Returns the number of statutes in the graph.
5177    #[must_use]
5178    pub fn len(&self) -> usize {
5179        self.statutes.len()
5180    }
5181
5182    /// Returns whether the graph is empty.
5183    #[must_use]
5184    pub fn is_empty(&self) -> bool {
5185        self.statutes.is_empty()
5186    }
5187
5188    /// Gets a statute by ID.
5189    #[must_use]
5190    pub fn get(&self, id: &str) -> Option<&Statute> {
5191        self.statutes.get(id)
5192    }
5193
5194    /// Finds all statutes derived from a given statute.
5195    ///
5196    /// # Examples
5197    ///
5198    /// ```
5199    /// use legalis_core::{StatuteGraph, Statute, Effect};
5200    ///
5201    /// let mut graph = StatuteGraph::new();
5202    ///
5203    /// graph.add_statute(Statute::new("parent", "Parent", Effect::grant("Benefit")));
5204    /// graph.add_statute(Statute::new("child", "Child", Effect::grant("Benefit"))
5205    ///     .with_derives_from("parent"));
5206    ///
5207    /// let derived = graph.find_derived_from("parent");
5208    /// assert_eq!(derived.len(), 1);
5209    /// assert_eq!(derived[0].id, "child");
5210    /// ```
5211    #[must_use]
5212    pub fn find_derived_from(&self, source_id: &str) -> Vec<&Statute> {
5213        self.derivation_edges
5214            .get(source_id)
5215            .map(|ids| ids.iter().filter_map(|id| self.statutes.get(id)).collect())
5216            .unwrap_or_default()
5217    }
5218
5219    /// Finds all statutes that a given statute is derived from (its sources).
5220    ///
5221    /// # Examples
5222    ///
5223    /// ```
5224    /// use legalis_core::{StatuteGraph, Statute, Effect};
5225    ///
5226    /// let mut graph = StatuteGraph::new();
5227    ///
5228    /// graph.add_statute(Statute::new("source-1", "Source 1", Effect::grant("B1")));
5229    /// graph.add_statute(Statute::new("source-2", "Source 2", Effect::grant("B2")));
5230    /// graph.add_statute(Statute::new("derived", "Derived", Effect::grant("B"))
5231    ///     .with_derives_from("source-1")
5232    ///     .with_derives_from("source-2"));
5233    ///
5234    /// let sources = graph.find_sources("derived");
5235    /// assert_eq!(sources.len(), 2);
5236    /// ```
5237    #[must_use]
5238    pub fn find_sources(&self, statute_id: &str) -> Vec<&Statute> {
5239        self.statutes
5240            .get(statute_id)
5241            .map(|statute| {
5242                statute
5243                    .derives_from
5244                    .iter()
5245                    .filter_map(|id| self.statutes.get(id))
5246                    .collect()
5247            })
5248            .unwrap_or_default()
5249    }
5250
5251    /// Finds the transitive closure of all statutes derived from a given statute.
5252    ///
5253    /// This includes direct derivatives and all their derivatives recursively.
5254    ///
5255    /// # Examples
5256    ///
5257    /// ```
5258    /// use legalis_core::{StatuteGraph, Statute, Effect};
5259    ///
5260    /// let mut graph = StatuteGraph::new();
5261    ///
5262    /// graph.add_statute(Statute::new("root", "Root", Effect::grant("B")));
5263    /// graph.add_statute(Statute::new("child", "Child", Effect::grant("B"))
5264    ///     .with_derives_from("root"));
5265    /// graph.add_statute(Statute::new("grandchild", "Grandchild", Effect::grant("B"))
5266    ///     .with_derives_from("child"));
5267    ///
5268    /// let all_derived = graph.find_all_derived_from("root");
5269    /// assert_eq!(all_derived.len(), 2); // child and grandchild
5270    /// ```
5271    #[must_use]
5272    pub fn find_all_derived_from(&self, source_id: &str) -> Vec<&Statute> {
5273        let mut result = Vec::new();
5274        let mut visited = std::collections::HashSet::new();
5275        let mut queue = vec![source_id];
5276
5277        while let Some(current_id) = queue.pop() {
5278            if !visited.insert(current_id) {
5279                continue; // Already visited
5280            }
5281
5282            if let Some(derived_ids) = self.derivation_edges.get(current_id) {
5283                for derived_id in derived_ids {
5284                    if let Some(statute) = self.statutes.get(derived_id) {
5285                        result.push(statute);
5286                        queue.push(derived_id);
5287                    }
5288                }
5289            }
5290        }
5291
5292        result
5293    }
5294
5295    /// Detects cycles in the derivation graph.
5296    ///
5297    /// Returns statute IDs that form derivation cycles (circular dependencies).
5298    ///
5299    /// # Examples
5300    ///
5301    /// ```
5302    /// use legalis_core::{StatuteGraph, Statute, Effect};
5303    ///
5304    /// let mut graph = StatuteGraph::new();
5305    ///
5306    /// // Normal case: no cycles
5307    /// graph.add_statute(Statute::new("a", "A", Effect::grant("B")));
5308    /// graph.add_statute(Statute::new("b", "B", Effect::grant("B"))
5309    ///     .with_derives_from("a"));
5310    ///
5311    /// assert!(graph.detect_cycles().is_empty());
5312    /// ```
5313    #[must_use]
5314    pub fn detect_cycles(&self) -> Vec<Vec<String>> {
5315        let mut cycles = Vec::new();
5316        let mut visited = std::collections::HashSet::new();
5317        let mut rec_stack = std::collections::HashSet::new();
5318        let mut path = Vec::new();
5319
5320        for id in self.statutes.keys() {
5321            if !visited.contains(id.as_str()) {
5322                self.detect_cycles_dfs(id, &mut visited, &mut rec_stack, &mut path, &mut cycles);
5323            }
5324        }
5325
5326        cycles
5327    }
5328
5329    #[allow(clippy::too_many_arguments)]
5330    fn detect_cycles_dfs(
5331        &self,
5332        node: &str,
5333        visited: &mut std::collections::HashSet<String>,
5334        rec_stack: &mut std::collections::HashSet<String>,
5335        path: &mut Vec<String>,
5336        cycles: &mut Vec<Vec<String>>,
5337    ) {
5338        visited.insert(node.to_string());
5339        rec_stack.insert(node.to_string());
5340        path.push(node.to_string());
5341
5342        if let Some(neighbors) = self.derivation_edges.get(node) {
5343            for neighbor in neighbors {
5344                if !visited.contains(neighbor) {
5345                    self.detect_cycles_dfs(neighbor, visited, rec_stack, path, cycles);
5346                } else if rec_stack.contains(neighbor) {
5347                    // Found a cycle
5348                    if let Some(pos) = path.iter().position(|x| x == neighbor) {
5349                        cycles.push(path[pos..].to_vec());
5350                    }
5351                }
5352            }
5353        }
5354
5355        path.pop();
5356        rec_stack.remove(node);
5357    }
5358
5359    /// Returns an iterator over all statutes in the graph.
5360    pub fn iter(&self) -> impl Iterator<Item = &Statute> {
5361        self.statutes.values()
5362    }
5363}
5364
5365impl Default for StatuteGraph {
5366    fn default() -> Self {
5367        Self::new()
5368    }
5369}
5370
5371/// Cross-jurisdiction statute equivalence detector.
5372///
5373/// This analyzer identifies statutes from different jurisdictions that serve
5374/// equivalent legal purposes, even if their exact wording differs.
5375///
5376/// # Examples
5377///
5378/// ```
5379/// use legalis_core::{CrossJurisdictionAnalyzer, Statute, Effect, Condition, ComparisonOp};
5380///
5381/// let us_law = Statute::new("us-voting", "Voting Rights", Effect::grant("Vote"))
5382///     .with_jurisdiction("US")
5383///     .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18));
5384///
5385/// let uk_law = Statute::new("uk-voting", "Electoral Rights", Effect::grant("Vote"))
5386///     .with_jurisdiction("UK")
5387///     .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18));
5388///
5389/// let analyzer = CrossJurisdictionAnalyzer::new();
5390/// let candidates = vec![uk_law.clone()];
5391/// let equiv = analyzer.find_equivalents(&us_law, &candidates);
5392///
5393/// assert_eq!(equiv.len(), 1);
5394/// ```
5395pub struct CrossJurisdictionAnalyzer {
5396    /// Similarity threshold (0.0 to 1.0)
5397    similarity_threshold: f64,
5398}
5399
5400impl CrossJurisdictionAnalyzer {
5401    /// Creates a new cross-jurisdiction analyzer with default threshold (0.7).
5402    #[must_use]
5403    pub fn new() -> Self {
5404        Self {
5405            similarity_threshold: 0.7,
5406        }
5407    }
5408
5409    /// Creates a new analyzer with a custom similarity threshold.
5410    ///
5411    /// # Examples
5412    ///
5413    /// ```
5414    /// use legalis_core::CrossJurisdictionAnalyzer;
5415    ///
5416    /// let analyzer = CrossJurisdictionAnalyzer::with_threshold(0.8);
5417    /// ```
5418    #[must_use]
5419    pub fn with_threshold(threshold: f64) -> Self {
5420        Self {
5421            similarity_threshold: threshold.clamp(0.0, 1.0),
5422        }
5423    }
5424
5425    /// Finds statutes from different jurisdictions that are equivalent to the given statute.
5426    ///
5427    /// # Examples
5428    ///
5429    /// ```
5430    /// use legalis_core::{CrossJurisdictionAnalyzer, Statute, Effect, Condition, ComparisonOp};
5431    ///
5432    /// let reference = Statute::new("ref", "Age Requirement", Effect::grant("Benefit"))
5433    ///     .with_jurisdiction("US")
5434    ///     .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 21));
5435    ///
5436    /// let candidate = Statute::new("can", "Age Eligibility", Effect::grant("Benefit"))
5437    ///     .with_jurisdiction("CA")
5438    ///     .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 21));
5439    ///
5440    /// let analyzer = CrossJurisdictionAnalyzer::new();
5441    /// let candidates = vec![candidate];
5442    /// let equivalents = analyzer.find_equivalents(&reference, &candidates);
5443    ///
5444    /// assert_eq!(equivalents.len(), 1);
5445    /// ```
5446    #[must_use]
5447    pub fn find_equivalents<'a>(
5448        &self,
5449        reference: &Statute,
5450        candidates: &'a [Statute],
5451    ) -> Vec<&'a Statute> {
5452        candidates
5453            .iter()
5454            .filter(|candidate| {
5455                // Don't compare statutes from the same jurisdiction
5456                if reference.jurisdiction == candidate.jurisdiction {
5457                    return false;
5458                }
5459
5460                let similarity = self.calculate_similarity(reference, candidate);
5461                similarity >= self.similarity_threshold
5462            })
5463            .collect()
5464    }
5465
5466    /// Calculates similarity score between two statutes (0.0 to 1.0).
5467    ///
5468    /// Higher scores indicate greater equivalence.
5469    #[must_use]
5470    pub fn calculate_similarity(&self, s1: &Statute, s2: &Statute) -> f64 {
5471        let mut score = 0.0;
5472        let mut weight_sum = 0.0;
5473
5474        // Effect similarity (weight: 0.4)
5475        let effect_weight = 0.4;
5476        if s1.effect.effect_type == s2.effect.effect_type {
5477            score += effect_weight;
5478        }
5479        weight_sum += effect_weight;
5480
5481        // Precondition count similarity (weight: 0.3)
5482        let precond_weight = 0.3;
5483        let precond_similarity = if s1.preconditions.is_empty() && s2.preconditions.is_empty() {
5484            1.0
5485        } else {
5486            let min_count = s1.preconditions.len().min(s2.preconditions.len()) as f64;
5487            let max_count = s1.preconditions.len().max(s2.preconditions.len()) as f64;
5488            if max_count == 0.0 {
5489                0.0
5490            } else {
5491                min_count / max_count
5492            }
5493        };
5494        score += precond_weight * precond_similarity;
5495        weight_sum += precond_weight;
5496
5497        // Entity type similarity (weight: 0.3)
5498        let entity_weight = 0.3;
5499        let entity_similarity = if s1.applies_to.is_empty() && s2.applies_to.is_empty() {
5500            1.0 // Both apply to all entities
5501        } else {
5502            let common = s1
5503                .applies_to
5504                .iter()
5505                .filter(|t| s2.applies_to.contains(t))
5506                .count() as f64;
5507            let total = (s1.applies_to.len() + s2.applies_to.len()) as f64;
5508            if total == 0.0 {
5509                0.0
5510            } else {
5511                2.0 * common / total // Jaccard similarity
5512            }
5513        };
5514        score += entity_weight * entity_similarity;
5515        weight_sum += entity_weight;
5516
5517        score / weight_sum
5518    }
5519}
5520
5521impl Default for CrossJurisdictionAnalyzer {
5522    fn default() -> Self {
5523        Self::new()
5524    }
5525}
5526
5527/// Legal effect produced when statute conditions are met.
5528///
5529/// Effects represent the legal consequences that occur when a statute's conditions are satisfied.
5530/// They can include granting rights, imposing obligations, or changing legal status.
5531///
5532/// # Examples
5533///
5534/// ```
5535/// use legalis_core::{Effect, EffectType};
5536///
5537/// let grant = Effect::new(EffectType::Grant, "Right to vote")
5538///     .with_parameter("scope", "federal")
5539///     .with_parameter("duration", "permanent");
5540///
5541/// assert_eq!(grant.effect_type, EffectType::Grant);
5542/// assert_eq!(grant.parameters.get("scope"), Some(&"federal".to_string()));
5543/// ```
5544///
5545/// ```
5546/// use legalis_core::{Effect, EffectType};
5547///
5548/// let tax = Effect::new(EffectType::MonetaryTransfer, "Income tax")
5549///     .with_parameter("rate", "0.22")
5550///     .with_parameter("bracket", "middle");
5551///
5552/// assert!(format!("{}", tax).contains("Income tax"));
5553/// ```
5554#[derive(Debug, Clone, PartialEq)]
5555#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5556#[cfg_attr(feature = "schema", derive(JsonSchema))]
5557pub struct Effect {
5558    /// Type of effect
5559    pub effect_type: EffectType,
5560    /// Description of the effect
5561    pub description: String,
5562    /// Parameters for the effect
5563    pub parameters: std::collections::HashMap<String, String>,
5564}
5565
5566impl Effect {
5567    /// Creates a new Effect.
5568    pub fn new(effect_type: EffectType, description: impl Into<String>) -> Self {
5569        Self {
5570            effect_type,
5571            description: description.into(),
5572            parameters: std::collections::HashMap::new(),
5573        }
5574    }
5575
5576    /// Adds a parameter to the effect.
5577    pub fn with_parameter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
5578        self.parameters.insert(key.into(), value.into());
5579        self
5580    }
5581
5582    /// Gets a parameter value by key.
5583    #[must_use]
5584    pub fn get_parameter(&self, key: &str) -> Option<&String> {
5585        self.parameters.get(key)
5586    }
5587
5588    /// Checks if a parameter exists.
5589    #[must_use]
5590    pub fn has_parameter(&self, key: &str) -> bool {
5591        self.parameters.contains_key(key)
5592    }
5593
5594    /// Returns the number of parameters.
5595    #[must_use]
5596    pub fn parameter_count(&self) -> usize {
5597        self.parameters.len()
5598    }
5599
5600    /// Removes a parameter by key.
5601    pub fn remove_parameter(&mut self, key: &str) -> Option<String> {
5602        self.parameters.remove(key)
5603    }
5604
5605    /// Creates a Grant effect.
5606    pub fn grant(description: impl Into<String>) -> Self {
5607        Self::new(EffectType::Grant, description)
5608    }
5609
5610    /// Creates a Revoke effect.
5611    pub fn revoke(description: impl Into<String>) -> Self {
5612        Self::new(EffectType::Revoke, description)
5613    }
5614
5615    /// Creates an Obligation effect.
5616    pub fn obligation(description: impl Into<String>) -> Self {
5617        Self::new(EffectType::Obligation, description)
5618    }
5619
5620    /// Creates a Prohibition effect.
5621    pub fn prohibition(description: impl Into<String>) -> Self {
5622        Self::new(EffectType::Prohibition, description)
5623    }
5624
5625    /// Composes multiple effects with priority ordering.
5626    ///
5627    /// Creates a `ComposedEffect` that represents the combination of multiple effects.
5628    /// Effects are applied in the order specified, with earlier effects having higher priority
5629    /// for conflict resolution.
5630    ///
5631    /// # Example
5632    /// ```
5633    /// # use legalis_core::{Effect, EffectType, CompositionStrategy};
5634    /// let grant = Effect::grant("access to resource");
5635    /// let obligation = Effect::obligation("must report annually");
5636    /// let revoke = Effect::revoke("temporary access");
5637    ///
5638    /// let composed = Effect::compose(vec![grant, obligation, revoke]);
5639    /// assert_eq!(composed.effects.len(), 3);
5640    /// assert_eq!(composed.resolution_strategy, CompositionStrategy::FirstWins);
5641    /// ```
5642    pub fn compose(effects: Vec<Effect>) -> ComposedEffect {
5643        ComposedEffect::new(effects)
5644    }
5645
5646    /// Computes the inverse effect for rollback operations.
5647    ///
5648    /// Returns the effect that would reverse this effect's action.
5649    /// For example, Grant ↔ Revoke, Obligation ↔ lifting of obligation.
5650    ///
5651    /// # Returns
5652    /// - `Some(Effect)` if an inverse exists
5653    /// - `None` if the effect cannot be inverted (e.g., Custom effects)
5654    ///
5655    /// # Example
5656    /// ```
5657    /// # use legalis_core::{Effect, EffectType};
5658    /// let grant = Effect::grant("access to resource");
5659    /// let inverse = grant.inverse().unwrap();
5660    /// assert_eq!(inverse.effect_type, EffectType::Revoke);
5661    /// assert_eq!(inverse.description, "access to resource");
5662    ///
5663    /// let obligation = Effect::obligation("must file taxes");
5664    /// let inverse_obligation = obligation.inverse().unwrap();
5665    /// assert_eq!(inverse_obligation.effect_type, EffectType::Grant);
5666    /// assert!(inverse_obligation.description.contains("relief from"));
5667    /// ```
5668    #[must_use]
5669    pub fn inverse(&self) -> Option<Effect> {
5670        let (inverse_type, inverse_description) = match self.effect_type {
5671            EffectType::Grant => (EffectType::Revoke, self.description.clone()),
5672            EffectType::Revoke => (EffectType::Grant, self.description.clone()),
5673            EffectType::Obligation => (
5674                EffectType::Grant,
5675                format!("relief from {}", self.description),
5676            ),
5677            EffectType::Prohibition => (
5678                EffectType::Grant,
5679                format!("permission for {}", self.description),
5680            ),
5681            EffectType::MonetaryTransfer => {
5682                // Reverse the monetary transfer (e.g., tax → refund)
5683                let desc = if self.description.contains("tax") {
5684                    self.description.replace("tax", "refund")
5685                } else if self.description.contains("fine") {
5686                    self.description.replace("fine", "reimbursement")
5687                } else {
5688                    format!("reverse {}", self.description)
5689                };
5690                (EffectType::MonetaryTransfer, desc)
5691            }
5692            EffectType::StatusChange => {
5693                // For status changes, we'd need to know the old status
5694                // Mark it as a reverse status change
5695                (
5696                    EffectType::StatusChange,
5697                    format!("reverse {}", self.description),
5698                )
5699            }
5700            EffectType::Custom => return None, // Cannot invert custom effects generically
5701        };
5702
5703        let mut inverse = Effect::new(inverse_type, inverse_description);
5704        // Copy parameters but mark as inverse
5705        inverse.parameters = self.parameters.clone();
5706        inverse
5707            .parameters
5708            .insert("_is_inverse".to_string(), "true".to_string());
5709        inverse.parameters.insert(
5710            "_original_type".to_string(),
5711            format!("{:?}", self.effect_type),
5712        );
5713
5714        Some(inverse)
5715    }
5716
5717    /// Checks if this effect is an inverse of another effect.
5718    #[must_use]
5719    pub fn is_inverse_of(&self, other: &Effect) -> bool {
5720        if let Some(inv) = other.inverse() {
5721            self.effect_type == inv.effect_type
5722                && (self.description == inv.description
5723                    || self.description.contains(&other.description))
5724        } else {
5725            false
5726        }
5727    }
5728
5729    /// Creates a temporal effect with start/end times and recurrence.
5730    ///
5731    /// Wraps this effect in a `TemporalEffect` that controls when the effect is active.
5732    ///
5733    /// # Example
5734    /// ```
5735    /// # use legalis_core::{Effect, RecurrencePattern};
5736    /// # use chrono::{NaiveDate, Utc};
5737    /// let grant = Effect::grant("seasonal parking permit");
5738    /// let start = NaiveDate::from_ymd_opt(2025, 6, 1).unwrap();
5739    /// let end = NaiveDate::from_ymd_opt(2025, 9, 1).unwrap();
5740    ///
5741    /// let temporal = grant.with_temporal_validity(start, Some(end), None);
5742    /// assert!(temporal.is_active_on(NaiveDate::from_ymd_opt(2025, 7, 15).unwrap()));
5743    /// assert!(!temporal.is_active_on(NaiveDate::from_ymd_opt(2025, 10, 1).unwrap()));
5744    /// ```
5745    #[must_use]
5746    pub fn with_temporal_validity(
5747        self,
5748        start: NaiveDate,
5749        end: Option<NaiveDate>,
5750        recurrence: Option<RecurrencePattern>,
5751    ) -> TemporalEffect {
5752        TemporalEffect::new(self, start, end, recurrence)
5753    }
5754
5755    /// Creates a conditional effect that depends on runtime conditions.
5756    ///
5757    /// The effect will only be applied if the condition evaluates to true
5758    /// at the time of application.
5759    ///
5760    /// # Example
5761    /// ```
5762    /// # use legalis_core::{Effect, Condition, ComparisonOp};
5763    /// let grant = Effect::grant("bonus payment");
5764    /// let condition = Condition::income(ComparisonOp::GreaterOrEqual, 50000);
5765    ///
5766    /// let conditional = grant.when(condition);
5767    /// assert_eq!(conditional.effect.description, "bonus payment");
5768    /// ```
5769    #[must_use]
5770    pub fn when(self, condition: Condition) -> ConditionalEffect {
5771        ConditionalEffect::new(self, condition)
5772    }
5773}
5774
5775impl fmt::Display for Effect {
5776    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5777        write!(f, "{}: {}", self.effect_type, self.description)
5778    }
5779}
5780
5781/// Composed effect combining multiple effects with priority ordering.
5782///
5783/// When multiple effects need to be applied together, a ComposedEffect
5784/// provides conflict resolution strategies and ordering guarantees.
5785///
5786/// # Example
5787/// ```
5788/// # use legalis_core::{Effect, ComposedEffect, CompositionStrategy};
5789/// let effects = vec![
5790///     Effect::grant("resource access"),
5791///     Effect::obligation("annual reporting"),
5792/// ];
5793/// let composed = ComposedEffect::new(effects)
5794///     .with_resolution_strategy(CompositionStrategy::MostSpecific);
5795///
5796/// assert_eq!(composed.effects.len(), 2);
5797/// ```
5798#[derive(Debug, Clone, PartialEq)]
5799#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5800#[cfg_attr(feature = "schema", derive(JsonSchema))]
5801pub struct ComposedEffect {
5802    /// The effects to be composed (applied in order).
5803    pub effects: Vec<Effect>,
5804    /// Strategy for resolving conflicts between effects.
5805    pub resolution_strategy: CompositionStrategy,
5806}
5807
5808impl ComposedEffect {
5809    /// Creates a new composed effect with default conflict resolution (FirstWins).
5810    #[must_use]
5811    pub fn new(effects: Vec<Effect>) -> Self {
5812        Self {
5813            effects,
5814            resolution_strategy: CompositionStrategy::FirstWins,
5815        }
5816    }
5817
5818    /// Sets the conflict resolution strategy.
5819    #[must_use]
5820    pub fn with_resolution_strategy(mut self, strategy: CompositionStrategy) -> Self {
5821        self.resolution_strategy = strategy;
5822        self
5823    }
5824
5825    /// Adds an effect to the composition.
5826    pub fn add_effect(&mut self, effect: Effect) {
5827        self.effects.push(effect);
5828    }
5829
5830    /// Returns the number of effects.
5831    #[must_use]
5832    pub fn len(&self) -> usize {
5833        self.effects.len()
5834    }
5835
5836    /// Checks if there are no effects.
5837    #[must_use]
5838    pub fn is_empty(&self) -> bool {
5839        self.effects.is_empty()
5840    }
5841
5842    /// Resolves the composition to a single effective result.
5843    ///
5844    /// Applies the conflict resolution strategy to determine which effects take precedence.
5845    #[must_use]
5846    pub fn resolve(&self) -> Vec<&Effect> {
5847        match self.resolution_strategy {
5848            CompositionStrategy::FirstWins => {
5849                // First effect of each type wins
5850                let mut seen_types = std::collections::HashSet::new();
5851                self.effects
5852                    .iter()
5853                    .filter(|e| seen_types.insert(e.effect_type.clone()))
5854                    .collect()
5855            }
5856            CompositionStrategy::LastWins => {
5857                // Last effect of each type wins
5858                let mut result = std::collections::HashMap::new();
5859                for effect in &self.effects {
5860                    result.insert(effect.effect_type.clone(), effect);
5861                }
5862                result.values().copied().collect()
5863            }
5864            CompositionStrategy::MostSpecific => {
5865                // Prefer effects with more parameters (more specific)
5866                let mut result = std::collections::HashMap::new();
5867                for effect in &self.effects {
5868                    result
5869                        .entry(effect.effect_type.clone())
5870                        .and_modify(|e: &mut &Effect| {
5871                            if effect.parameter_count() > e.parameter_count() {
5872                                *e = effect;
5873                            }
5874                        })
5875                        .or_insert(effect);
5876                }
5877                result.values().copied().collect()
5878            }
5879            CompositionStrategy::AllApply => {
5880                // All effects apply (no conflict resolution)
5881                self.effects.iter().collect()
5882            }
5883        }
5884    }
5885}
5886
5887impl fmt::Display for ComposedEffect {
5888    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5889        write!(f, "ComposedEffect[")?;
5890        for (i, effect) in self.effects.iter().enumerate() {
5891            if i > 0 {
5892                write!(f, ", ")?;
5893            }
5894            write!(f, "{}", effect)?;
5895        }
5896        write!(f, "]")
5897    }
5898}
5899
5900/// Strategies for resolving conflicts between composed effects.
5901#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5902#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5903#[cfg_attr(feature = "schema", derive(JsonSchema))]
5904pub enum CompositionStrategy {
5905    /// First effect of each type wins (default).
5906    FirstWins,
5907    /// Last effect of each type wins (later overrides earlier).
5908    LastWins,
5909    /// Most specific effect wins (most parameters).
5910    MostSpecific,
5911    /// All effects apply (no deduplication).
5912    AllApply,
5913}
5914
5915impl fmt::Display for CompositionStrategy {
5916    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5917        match self {
5918            Self::FirstWins => write!(f, "FirstWins"),
5919            Self::LastWins => write!(f, "LastWins"),
5920            Self::MostSpecific => write!(f, "MostSpecific"),
5921            Self::AllApply => write!(f, "AllApply"),
5922        }
5923    }
5924}
5925
5926/// Effect with temporal validity constraints.
5927///
5928/// Wraps an effect with start/end dates and optional recurrence pattern.
5929/// The effect is only active during specified time periods.
5930///
5931/// # Example
5932/// ```
5933/// # use legalis_core::{Effect, TemporalEffect, RecurrencePattern};
5934/// # use chrono::NaiveDate;
5935/// let effect = Effect::grant("summer internship");
5936/// let start = NaiveDate::from_ymd_opt(2025, 6, 1).unwrap();
5937/// let end = NaiveDate::from_ymd_opt(2025, 8, 31).unwrap();
5938///
5939/// let temporal = TemporalEffect::new(effect, start, Some(end), None);
5940/// assert!(temporal.is_active_on(NaiveDate::from_ymd_opt(2025, 7, 15).unwrap()));
5941/// assert!(!temporal.is_active_on(NaiveDate::from_ymd_opt(2025, 9, 1).unwrap()));
5942/// ```
5943#[derive(Debug, Clone, PartialEq)]
5944#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5945#[cfg_attr(feature = "schema", derive(JsonSchema))]
5946pub struct TemporalEffect {
5947    /// The underlying effect.
5948    pub effect: Effect,
5949    /// Start date (inclusive).
5950    pub start_date: NaiveDate,
5951    /// End date (inclusive), if any.
5952    pub end_date: Option<NaiveDate>,
5953    /// Recurrence pattern, if any.
5954    pub recurrence: Option<RecurrencePattern>,
5955}
5956
5957impl TemporalEffect {
5958    /// Creates a new temporal effect.
5959    #[must_use]
5960    pub fn new(
5961        effect: Effect,
5962        start_date: NaiveDate,
5963        end_date: Option<NaiveDate>,
5964        recurrence: Option<RecurrencePattern>,
5965    ) -> Self {
5966        Self {
5967            effect,
5968            start_date,
5969            end_date,
5970            recurrence,
5971        }
5972    }
5973
5974    /// Checks if the effect is active on a given date.
5975    #[must_use]
5976    pub fn is_active_on(&self, date: NaiveDate) -> bool {
5977        // Check basic date range
5978        if date < self.start_date {
5979            return false;
5980        }
5981        if let Some(end) = self.end_date
5982            && date > end
5983        {
5984            return false;
5985        }
5986
5987        // Check recurrence pattern if present
5988        if let Some(ref pattern) = self.recurrence {
5989            pattern.matches(date, self.start_date)
5990        } else {
5991            true
5992        }
5993    }
5994
5995    /// Returns the next activation date after the given date.
5996    #[must_use]
5997    pub fn next_activation(&self, after: NaiveDate) -> Option<NaiveDate> {
5998        if let Some(ref pattern) = self.recurrence {
5999            pattern.next_occurrence(after, self.start_date, self.end_date)
6000        } else if after < self.start_date {
6001            Some(self.start_date)
6002        } else {
6003            None
6004        }
6005    }
6006}
6007
6008impl fmt::Display for TemporalEffect {
6009    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6010        write!(f, "{} (active from {}", self.effect, self.start_date)?;
6011        if let Some(end) = self.end_date {
6012            write!(f, " to {}", end)?;
6013        }
6014        if let Some(ref rec) = self.recurrence {
6015            write!(f, ", {}", rec)?;
6016        }
6017        write!(f, ")")
6018    }
6019}
6020
6021/// Recurrence patterns for temporal effects.
6022#[derive(Debug, Clone, PartialEq, Eq)]
6023#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6024#[cfg_attr(feature = "schema", derive(JsonSchema))]
6025pub enum RecurrencePattern {
6026    /// Recurs daily.
6027    Daily,
6028    /// Recurs weekly (every N weeks).
6029    Weekly { interval: u32 },
6030    /// Recurs monthly (every N months, on same day).
6031    Monthly { interval: u32 },
6032    /// Recurs yearly (every N years, on same date).
6033    Yearly { interval: u32 },
6034    /// Recurs on specific days of week (0=Sunday, 6=Saturday).
6035    DaysOfWeek { days: Vec<u32> },
6036    /// Custom cron-like pattern (simplified).
6037    Custom { description: String },
6038}
6039
6040impl RecurrencePattern {
6041    /// Checks if the pattern matches a given date.
6042    #[must_use]
6043    pub fn matches(&self, date: NaiveDate, start: NaiveDate) -> bool {
6044        match self {
6045            Self::Daily => true,
6046            Self::Weekly { interval } => {
6047                let days_diff = (date - start).num_days();
6048                days_diff >= 0 && days_diff % ((*interval as i64) * 7) == 0
6049            }
6050            Self::Monthly { interval } => {
6051                let months_diff = (date.year() - start.year()) * 12
6052                    + (date.month() as i32 - start.month() as i32);
6053                months_diff >= 0
6054                    && months_diff % (*interval as i32) == 0
6055                    && date.day() == start.day()
6056            }
6057            Self::Yearly { interval } => {
6058                let years_diff = date.year() - start.year();
6059                years_diff >= 0
6060                    && years_diff % (*interval as i32) == 0
6061                    && date.month() == start.month()
6062                    && date.day() == start.day()
6063            }
6064            Self::DaysOfWeek { days } => {
6065                let weekday = date.weekday().num_days_from_sunday();
6066                days.contains(&weekday)
6067            }
6068            Self::Custom { .. } => true, // Custom patterns need external evaluation
6069        }
6070    }
6071
6072    /// Finds the next occurrence after a given date.
6073    #[must_use]
6074    pub fn next_occurrence(
6075        &self,
6076        after: NaiveDate,
6077        start: NaiveDate,
6078        end: Option<NaiveDate>,
6079    ) -> Option<NaiveDate> {
6080        let mut candidate = after.succ_opt()?;
6081
6082        // Search up to 1 year ahead
6083        for _ in 0..365 {
6084            if let Some(end_date) = end
6085                && candidate > end_date
6086            {
6087                return None;
6088            }
6089            if candidate >= start && self.matches(candidate, start) {
6090                return Some(candidate);
6091            }
6092            candidate = candidate.succ_opt()?;
6093        }
6094        None
6095    }
6096}
6097
6098impl fmt::Display for RecurrencePattern {
6099    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6100        match self {
6101            Self::Daily => write!(f, "daily"),
6102            Self::Weekly { interval } => write!(f, "every {} week(s)", interval),
6103            Self::Monthly { interval } => write!(f, "every {} month(s)", interval),
6104            Self::Yearly { interval } => write!(f, "every {} year(s)", interval),
6105            Self::DaysOfWeek { days } => {
6106                write!(f, "on days: ")?;
6107                for (i, day) in days.iter().enumerate() {
6108                    if i > 0 {
6109                        write!(f, ", ")?;
6110                    }
6111                    write!(f, "{}", day)?;
6112                }
6113                Ok(())
6114            }
6115            Self::Custom { description } => write!(f, "custom: {}", description),
6116        }
6117    }
6118}
6119
6120/// Effect that depends on runtime conditions.
6121///
6122/// The effect is only applied if the condition evaluates to true.
6123/// This allows for dynamic, context-dependent effects.
6124///
6125/// # Example
6126/// ```
6127/// # use legalis_core::{Effect, ConditionalEffect, Condition, ComparisonOp, AttributeBasedContext};
6128/// # use std::collections::HashMap;
6129/// let effect = Effect::grant("senior discount");
6130/// let condition = Condition::age(ComparisonOp::GreaterOrEqual, 65);
6131/// let conditional = ConditionalEffect::new(effect, condition);
6132///
6133/// let mut attributes = HashMap::new();
6134/// attributes.insert("age".to_string(), "70".to_string());
6135/// let ctx = AttributeBasedContext::new(attributes);
6136///
6137/// assert!(conditional.should_apply(&ctx).unwrap());
6138/// ```
6139#[derive(Debug, Clone, PartialEq)]
6140#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6141pub struct ConditionalEffect {
6142    /// The effect to apply conditionally.
6143    pub effect: Effect,
6144    /// The condition that must be satisfied.
6145    pub condition: Condition,
6146}
6147
6148impl ConditionalEffect {
6149    /// Creates a new conditional effect.
6150    #[must_use]
6151    pub fn new(effect: Effect, condition: Condition) -> Self {
6152        Self { effect, condition }
6153    }
6154
6155    /// Checks if the effect should be applied given an evaluation context.
6156    pub fn should_apply<C: EvaluationContext>(&self, context: &C) -> Result<bool, EvaluationError> {
6157        self.condition.evaluate(context)
6158    }
6159
6160    /// Applies the effect if the condition is met, returns the effect or None.
6161    pub fn apply_if<C: EvaluationContext>(
6162        &self,
6163        context: &C,
6164    ) -> Result<Option<&Effect>, EvaluationError> {
6165        if self.should_apply(context)? {
6166            Ok(Some(&self.effect))
6167        } else {
6168            Ok(None)
6169        }
6170    }
6171}
6172
6173impl fmt::Display for ConditionalEffect {
6174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6175        write!(f, "{} WHEN {}", self.effect, self.condition)
6176    }
6177}
6178
6179/// Effect dependency graph for tracking and detecting cycles.
6180///
6181/// Tracks dependencies between effects to ensure proper ordering
6182/// and detect circular dependencies.
6183///
6184/// # Example
6185/// ```
6186/// # use legalis_core::{Effect, EffectDependencyGraph};
6187/// let mut graph = EffectDependencyGraph::new();
6188/// let e1 = Effect::grant("base access");
6189/// let e2 = Effect::grant("extended access");
6190/// let e3 = Effect::obligation("reporting");
6191///
6192/// graph.add_effect("e1".to_string(), e1);
6193/// graph.add_effect("e2".to_string(), e2);
6194/// graph.add_effect("e3".to_string(), e3);
6195///
6196/// graph.add_dependency("e2", "e1"); // e2 depends on e1
6197/// graph.add_dependency("e3", "e2"); // e3 depends on e2
6198///
6199/// assert!(!graph.has_cycle());
6200/// assert_eq!(graph.topological_sort().unwrap(), vec!["e1", "e2", "e3"]);
6201/// ```
6202#[derive(Debug, Clone)]
6203pub struct EffectDependencyGraph {
6204    /// Effects indexed by ID.
6205    effects: std::collections::HashMap<String, Effect>,
6206    /// Dependencies: effect_id -> list of effect_ids it depends on.
6207    dependencies: std::collections::HashMap<String, Vec<String>>,
6208}
6209
6210impl EffectDependencyGraph {
6211    /// Creates a new empty dependency graph.
6212    #[must_use]
6213    pub fn new() -> Self {
6214        Self {
6215            effects: std::collections::HashMap::new(),
6216            dependencies: std::collections::HashMap::new(),
6217        }
6218    }
6219
6220    /// Adds an effect to the graph.
6221    pub fn add_effect(&mut self, id: String, effect: Effect) {
6222        self.effects.insert(id.clone(), effect);
6223        self.dependencies.entry(id).or_default();
6224    }
6225
6226    /// Adds a dependency: `from` depends on `to`.
6227    ///
6228    /// Returns an error if it would create a cycle.
6229    pub fn add_dependency(&mut self, from: &str, to: &str) -> Result<(), String> {
6230        if !self.effects.contains_key(from) {
6231            return Err(format!("Effect '{}' not found", from));
6232        }
6233        if !self.effects.contains_key(to) {
6234            return Err(format!("Effect '{}' not found", to));
6235        }
6236
6237        // Add the dependency
6238        self.dependencies
6239            .entry(from.to_string())
6240            .or_default()
6241            .push(to.to_string());
6242
6243        // Check for cycles
6244        if self.has_cycle() {
6245            // Remove the dependency we just added
6246            if let Some(deps) = self.dependencies.get_mut(from) {
6247                deps.retain(|d| d != to);
6248            }
6249            return Err(format!(
6250                "Adding dependency {} -> {} would create a cycle",
6251                from, to
6252            ));
6253        }
6254
6255        Ok(())
6256    }
6257
6258    /// Checks if the graph contains a cycle.
6259    #[must_use]
6260    pub fn has_cycle(&self) -> bool {
6261        let mut visited = std::collections::HashSet::new();
6262        let mut rec_stack = std::collections::HashSet::new();
6263
6264        for node in self.effects.keys() {
6265            if self.has_cycle_util(node, &mut visited, &mut rec_stack) {
6266                return true;
6267            }
6268        }
6269        false
6270    }
6271
6272    /// Helper function for cycle detection (DFS).
6273    fn has_cycle_util(
6274        &self,
6275        node: &str,
6276        visited: &mut std::collections::HashSet<String>,
6277        rec_stack: &mut std::collections::HashSet<String>,
6278    ) -> bool {
6279        if rec_stack.contains(node) {
6280            return true;
6281        }
6282        if visited.contains(node) {
6283            return false;
6284        }
6285
6286        visited.insert(node.to_string());
6287        rec_stack.insert(node.to_string());
6288
6289        if let Some(deps) = self.dependencies.get(node) {
6290            for dep in deps {
6291                if self.has_cycle_util(dep, visited, rec_stack) {
6292                    return true;
6293                }
6294            }
6295        }
6296
6297        rec_stack.remove(node);
6298        false
6299    }
6300
6301    /// Returns a topological sort of the effects (dependency order).
6302    ///
6303    /// Returns None if there's a cycle.
6304    #[must_use]
6305    pub fn topological_sort(&self) -> Option<Vec<String>> {
6306        if self.has_cycle() {
6307            return None;
6308        }
6309
6310        let mut visited = std::collections::HashSet::new();
6311        let mut stack = Vec::new();
6312
6313        for node in self.effects.keys() {
6314            if !visited.contains(node) {
6315                self.topological_sort_util(node, &mut visited, &mut stack);
6316            }
6317        }
6318
6319        // Stack is already in correct dependency order (dependencies first)
6320        Some(stack)
6321    }
6322
6323    /// Helper for topological sort (DFS).
6324    fn topological_sort_util(
6325        &self,
6326        node: &str,
6327        visited: &mut std::collections::HashSet<String>,
6328        stack: &mut Vec<String>,
6329    ) {
6330        visited.insert(node.to_string());
6331
6332        if let Some(deps) = self.dependencies.get(node) {
6333            for dep in deps {
6334                if !visited.contains(dep) {
6335                    self.topological_sort_util(dep, visited, stack);
6336                }
6337            }
6338        }
6339
6340        stack.push(node.to_string());
6341    }
6342
6343    /// Gets an effect by ID.
6344    #[must_use]
6345    pub fn get_effect(&self, id: &str) -> Option<&Effect> {
6346        self.effects.get(id)
6347    }
6348
6349    /// Returns the number of effects in the graph.
6350    #[must_use]
6351    pub fn len(&self) -> usize {
6352        self.effects.len()
6353    }
6354
6355    /// Checks if the graph is empty.
6356    #[must_use]
6357    pub fn is_empty(&self) -> bool {
6358        self.effects.is_empty()
6359    }
6360}
6361
6362impl Default for EffectDependencyGraph {
6363    fn default() -> Self {
6364        Self::new()
6365    }
6366}
6367
6368/// Types of legal effects.
6369#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6370#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6371#[cfg_attr(feature = "schema", derive(JsonSchema))]
6372pub enum EffectType {
6373    /// Grant a right or permission
6374    Grant,
6375    /// Revoke a right or permission
6376    Revoke,
6377    /// Impose an obligation
6378    Obligation,
6379    /// Impose a prohibition
6380    Prohibition,
6381    /// Monetary transfer (subsidy, tax, fine, etc.)
6382    MonetaryTransfer,
6383    /// Status change
6384    StatusChange,
6385    /// Custom effect
6386    Custom,
6387}
6388
6389impl fmt::Display for EffectType {
6390    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6391        match self {
6392            Self::Grant => write!(f, "GRANT"),
6393            Self::Revoke => write!(f, "REVOKE"),
6394            Self::Obligation => write!(f, "OBLIGATION"),
6395            Self::Prohibition => write!(f, "PROHIBITION"),
6396            Self::MonetaryTransfer => write!(f, "MONETARY_TRANSFER"),
6397            Self::StatusChange => write!(f, "STATUS_CHANGE"),
6398            Self::Custom => write!(f, "CUSTOM"),
6399        }
6400    }
6401}
6402
6403/// Temporal validity for statutes.
6404///
6405/// Defines when a statute is in force, including effective dates, expiry dates (sunset clauses),
6406/// and amendment history.
6407///
6408/// # Examples
6409///
6410/// ```
6411/// use legalis_core::TemporalValidity;
6412/// use chrono::NaiveDate;
6413///
6414/// let validity = TemporalValidity::new()
6415///     .with_effective_date(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap())
6416///     .with_expiry_date(NaiveDate::from_ymd_opt(2025, 12, 31).unwrap());
6417///
6418/// let today = NaiveDate::from_ymd_opt(2025, 6, 15).unwrap();
6419/// assert!(validity.is_active(today));
6420///
6421/// let before = NaiveDate::from_ymd_opt(2024, 12, 31).unwrap();
6422/// assert!(!validity.is_active(before));
6423///
6424/// let after = NaiveDate::from_ymd_opt(2026, 1, 1).unwrap();
6425/// assert!(!validity.is_active(after));
6426/// ```
6427#[derive(Debug, Clone, Default, PartialEq)]
6428#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6429#[cfg_attr(feature = "schema", derive(JsonSchema))]
6430pub struct TemporalValidity {
6431    /// Effective date (when the statute comes into force)
6432    pub effective_date: Option<NaiveDate>,
6433    /// Expiry date (sunset clause)
6434    pub expiry_date: Option<NaiveDate>,
6435    /// Enactment timestamp
6436    pub enacted_at: Option<DateTime<Utc>>,
6437    /// Last amended timestamp
6438    pub amended_at: Option<DateTime<Utc>>,
6439}
6440
6441impl TemporalValidity {
6442    /// Creates a new TemporalValidity with no dates set.
6443    pub fn new() -> Self {
6444        Self::default()
6445    }
6446
6447    /// Sets the effective date.
6448    pub fn with_effective_date(mut self, date: NaiveDate) -> Self {
6449        self.effective_date = Some(date);
6450        self
6451    }
6452
6453    /// Sets the expiry date.
6454    pub fn with_expiry_date(mut self, date: NaiveDate) -> Self {
6455        self.expiry_date = Some(date);
6456        self
6457    }
6458
6459    /// Sets the enacted timestamp.
6460    pub fn with_enacted_at(mut self, timestamp: DateTime<Utc>) -> Self {
6461        self.enacted_at = Some(timestamp);
6462        self
6463    }
6464
6465    /// Sets the amended timestamp.
6466    pub fn with_amended_at(mut self, timestamp: DateTime<Utc>) -> Self {
6467        self.amended_at = Some(timestamp);
6468        self
6469    }
6470
6471    /// Checks if the statute is currently active.
6472    pub fn is_active(&self, as_of: NaiveDate) -> bool {
6473        let after_effective = self.effective_date.is_none_or(|d| as_of >= d);
6474        let before_expiry = self.expiry_date.is_none_or(|d| as_of <= d);
6475        after_effective && before_expiry
6476    }
6477
6478    /// Returns whether this has an effective date set.
6479    #[must_use]
6480    pub fn has_effective_date(&self) -> bool {
6481        self.effective_date.is_some()
6482    }
6483
6484    /// Returns whether this has an expiry date set.
6485    #[must_use]
6486    pub fn has_expiry_date(&self) -> bool {
6487        self.expiry_date.is_some()
6488    }
6489
6490    /// Returns whether this has been enacted (has an enacted_at timestamp).
6491    #[must_use]
6492    pub fn is_enacted(&self) -> bool {
6493        self.enacted_at.is_some()
6494    }
6495
6496    /// Returns whether this has been amended.
6497    #[must_use]
6498    pub fn is_amended(&self) -> bool {
6499        self.amended_at.is_some()
6500    }
6501
6502    /// Returns whether the statute has expired as of the given date.
6503    #[must_use]
6504    pub fn has_expired(&self, as_of: NaiveDate) -> bool {
6505        self.expiry_date.is_some_and(|exp| as_of > exp)
6506    }
6507
6508    /// Returns whether the statute is not yet effective as of the given date.
6509    #[must_use]
6510    pub fn is_pending(&self, as_of: NaiveDate) -> bool {
6511        self.effective_date.is_some_and(|eff| as_of < eff)
6512    }
6513}
6514
6515impl fmt::Display for TemporalValidity {
6516    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6517        match (&self.effective_date, &self.expiry_date) {
6518            (Some(eff), Some(exp)) => write!(f, "valid {} to {}", eff, exp),
6519            (Some(eff), None) => write!(f, "effective from {}", eff),
6520            (None, Some(exp)) => write!(f, "expires {}", exp),
6521            (None, None) => write!(f, "no temporal constraints"),
6522        }
6523    }
6524}
6525
6526/// Statute (legal article) definition.
6527///
6528/// A statute represents a legal rule with preconditions, effects, and optional discretionary logic.
6529/// Statutes follow an "If-Then-Else If Maybe" pattern:
6530/// - **If**: Preconditions must be met
6531/// - **Then**: Legal effect occurs
6532/// - **Else If Maybe**: Discretionary logic for edge cases
6533///
6534/// # Examples
6535///
6536/// ## Simple Statute
6537///
6538/// ```
6539/// use legalis_core::{Statute, Effect, EffectType, Condition, ComparisonOp};
6540///
6541/// let voting_rights = Statute::new(
6542///     "voting-rights-act",
6543///     "Right to Vote",
6544///     Effect::new(EffectType::Grant, "Right to participate in elections"),
6545/// )
6546/// .with_precondition(Condition::Age {
6547///     operator: ComparisonOp::GreaterOrEqual,
6548///     value: 18,
6549/// })
6550/// .with_jurisdiction("US");
6551///
6552/// assert_eq!(voting_rights.id, "voting-rights-act");
6553/// assert!(voting_rights.is_valid());
6554/// ```
6555///
6556/// ## Statute with Temporal Validity
6557///
6558/// ```
6559/// use legalis_core::{Statute, Effect, EffectType, TemporalValidity};
6560/// use chrono::NaiveDate;
6561///
6562/// let temporary_law = Statute::new(
6563///     "covid-relief-2025",
6564///     "COVID-19 Relief Act",
6565///     Effect::new(EffectType::Grant, "Emergency assistance"),
6566/// )
6567/// .with_temporal_validity(
6568///     TemporalValidity::new()
6569///         .with_effective_date(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap())
6570///         .with_expiry_date(NaiveDate::from_ymd_opt(2025, 12, 31).unwrap())
6571/// );
6572///
6573/// assert!(temporary_law.is_active(NaiveDate::from_ymd_opt(2025, 6, 1).unwrap()));
6574/// assert!(!temporary_law.is_active(NaiveDate::from_ymd_opt(2026, 1, 1).unwrap()));
6575/// ```
6576///
6577/// ## Statute with Discretion
6578///
6579/// ```
6580/// use legalis_core::{Statute, Effect, EffectType, Condition, ComparisonOp};
6581///
6582/// let employment_termination = Statute::new(
6583///     "just-cause-termination",
6584///     "Employment Termination for Just Cause",
6585///     Effect::new(EffectType::Grant, "Right to terminate employment"),
6586/// )
6587/// .with_discretion("Determine if just cause exists based on circumstances");
6588///
6589/// assert!(employment_termination.discretion_logic.is_some());
6590/// ```
6591/// A structured exception to a statute's application.
6592///
6593/// Exceptions represent specific circumstances where a statute does not apply,
6594/// even when its preconditions would otherwise be satisfied.
6595///
6596/// # Examples
6597///
6598/// ```
6599/// use legalis_core::{StatuteException, Condition, ComparisonOp};
6600///
6601/// let exception = StatuteException::new(
6602///     "minor-exception",
6603///     "Exception for minors",
6604///     Condition::age(ComparisonOp::LessThan, 18)
6605/// );
6606///
6607/// assert_eq!(exception.id, "minor-exception");
6608/// ```
6609#[derive(Debug, Clone, PartialEq)]
6610#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6611#[cfg_attr(feature = "schema", derive(JsonSchema))]
6612pub struct StatuteException {
6613    /// Unique identifier for this exception
6614    pub id: String,
6615    /// Description of the exception
6616    pub description: String,
6617    /// Condition under which the exception applies
6618    pub condition: Condition,
6619}
6620
6621impl StatuteException {
6622    /// Creates a new statute exception.
6623    ///
6624    /// # Examples
6625    ///
6626    /// ```
6627    /// use legalis_core::{StatuteException, Condition, ComparisonOp};
6628    ///
6629    /// let exception = StatuteException::new(
6630    ///     "medical-exception",
6631    ///     "Exception for medical emergencies",
6632    ///     Condition::has_attribute("medical_emergency")
6633    /// );
6634    /// ```
6635    #[must_use]
6636    pub fn new(
6637        id: impl Into<String>,
6638        description: impl Into<String>,
6639        condition: Condition,
6640    ) -> Self {
6641        Self {
6642            id: id.into(),
6643            description: description.into(),
6644            condition,
6645        }
6646    }
6647}
6648
6649impl std::fmt::Display for StatuteException {
6650    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6651        write!(
6652            f,
6653            "Exception '{}': {} when {}",
6654            self.id, self.description, self.condition
6655        )
6656    }
6657}
6658
6659#[derive(Debug, Clone)]
6660#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6661#[cfg_attr(feature = "schema", derive(JsonSchema))]
6662pub struct Statute {
6663    /// Unique identifier (e.g., "civil-code-article-1")
6664    pub id: String,
6665    /// Title of the statute
6666    pub title: String,
6667    /// Preconditions (If)
6668    pub preconditions: Vec<Condition>,
6669    /// Legal effect (Then)
6670    pub effect: Effect,
6671    /// Discretion logic description (Else If Maybe)
6672    pub discretion_logic: Option<String>,
6673    /// Temporal validity (effective dates, sunset clauses)
6674    pub temporal_validity: TemporalValidity,
6675    /// Version number
6676    pub version: u32,
6677    /// Jurisdiction identifier
6678    pub jurisdiction: Option<String>,
6679    /// Derivation source - the statute(s) this one is derived from
6680    pub derives_from: Vec<String>,
6681    /// Applicable entity types - what types of entities this statute applies to
6682    pub applies_to: Vec<String>,
6683    /// Structured exceptions - conditions under which this statute does not apply
6684    pub exceptions: Vec<StatuteException>,
6685}
6686
6687impl Statute {
6688    /// Creates a new Statute.
6689    pub fn new(id: impl Into<String>, title: impl Into<String>, effect: Effect) -> Self {
6690        Self {
6691            id: id.into(),
6692            title: title.into(),
6693            preconditions: Vec::new(),
6694            effect,
6695            discretion_logic: None,
6696            temporal_validity: TemporalValidity::default(),
6697            version: 1,
6698            jurisdiction: None,
6699            derives_from: Vec::new(),
6700            applies_to: Vec::new(),
6701            exceptions: Vec::new(),
6702        }
6703    }
6704
6705    /// Adds a precondition.
6706    pub fn with_precondition(mut self, condition: Condition) -> Self {
6707        self.preconditions.push(condition);
6708        self
6709    }
6710
6711    /// Sets the discretion logic.
6712    pub fn with_discretion(mut self, logic: impl Into<String>) -> Self {
6713        self.discretion_logic = Some(logic.into());
6714        self
6715    }
6716
6717    /// Sets temporal validity.
6718    pub fn with_temporal_validity(mut self, validity: TemporalValidity) -> Self {
6719        self.temporal_validity = validity;
6720        self
6721    }
6722
6723    /// Sets the version.
6724    pub fn with_version(mut self, version: u32) -> Self {
6725        self.version = version;
6726        self
6727    }
6728
6729    /// Sets the jurisdiction.
6730    pub fn with_jurisdiction(mut self, jurisdiction: impl Into<String>) -> Self {
6731        self.jurisdiction = Some(jurisdiction.into());
6732        self
6733    }
6734
6735    /// Adds a statute ID that this statute is derived from.
6736    ///
6737    /// # Examples
6738    ///
6739    /// ```
6740    /// use legalis_core::{Statute, Effect, EffectType};
6741    ///
6742    /// let derived = Statute::new("state-law-1", "State Law", Effect::grant("Benefit"))
6743    ///     .with_derives_from("federal-law-1");
6744    ///
6745    /// assert_eq!(derived.derives_from, vec!["federal-law-1"]);
6746    /// ```
6747    pub fn with_derives_from(mut self, source_id: impl Into<String>) -> Self {
6748        self.derives_from.push(source_id.into());
6749        self
6750    }
6751
6752    /// Adds an entity type that this statute applies to.
6753    ///
6754    /// # Examples
6755    ///
6756    /// ```
6757    /// use legalis_core::{Statute, Effect, EffectType};
6758    ///
6759    /// let law = Statute::new("business-law-1", "Business Regulation", Effect::grant("License"))
6760    ///     .with_applies_to("Corporation")
6761    ///     .with_applies_to("LLC");
6762    ///
6763    /// assert!(law.applies_to.contains(&"Corporation".to_string()));
6764    /// ```
6765    pub fn with_applies_to(mut self, entity_type: impl Into<String>) -> Self {
6766        self.applies_to.push(entity_type.into());
6767        self
6768    }
6769
6770    /// Adds an exception to this statute.
6771    ///
6772    /// # Examples
6773    ///
6774    /// ```
6775    /// use legalis_core::{Statute, Effect, EffectType, StatuteException, Condition, ComparisonOp};
6776    ///
6777    /// let law = Statute::new("tax-law-1", "Income Tax", Effect::grant("Tax liability"))
6778    ///     .with_exception(StatuteException::new(
6779    ///         "minor-exception",
6780    ///         "Minors are exempt",
6781    ///         Condition::age(ComparisonOp::LessThan, 18)
6782    ///     ));
6783    ///
6784    /// assert_eq!(law.exceptions.len(), 1);
6785    /// ```
6786    pub fn with_exception(mut self, exception: StatuteException) -> Self {
6787        self.exceptions.push(exception);
6788        self
6789    }
6790
6791    /// Checks if the statute is currently active.
6792    pub fn is_active(&self, as_of: NaiveDate) -> bool {
6793        self.temporal_validity.is_active(as_of)
6794    }
6795
6796    /// Returns the number of preconditions.
6797    #[must_use]
6798    pub fn precondition_count(&self) -> usize {
6799        self.preconditions.len()
6800    }
6801
6802    /// Returns whether this statute has any preconditions.
6803    #[must_use]
6804    pub fn has_preconditions(&self) -> bool {
6805        !self.preconditions.is_empty()
6806    }
6807
6808    /// Returns whether this statute has discretion logic.
6809    #[must_use]
6810    pub fn has_discretion(&self) -> bool {
6811        self.discretion_logic.is_some()
6812    }
6813
6814    /// Returns whether this statute has a jurisdiction set.
6815    #[must_use]
6816    pub fn has_jurisdiction(&self) -> bool {
6817        self.jurisdiction.is_some()
6818    }
6819
6820    /// Returns a reference to the preconditions.
6821    pub fn preconditions(&self) -> &[Condition] {
6822        &self.preconditions
6823    }
6824
6825    /// Returns whether this statute is derived from other statutes.
6826    ///
6827    /// # Examples
6828    ///
6829    /// ```
6830    /// use legalis_core::{Statute, Effect};
6831    ///
6832    /// let derived = Statute::new("derived", "Derived Law", Effect::grant("Benefit"))
6833    ///     .with_derives_from("source-law");
6834    ///
6835    /// assert!(derived.is_derived());
6836    /// ```
6837    #[must_use]
6838    pub fn is_derived(&self) -> bool {
6839        !self.derives_from.is_empty()
6840    }
6841
6842    /// Returns the IDs of statutes this statute is derived from.
6843    ///
6844    /// # Examples
6845    ///
6846    /// ```
6847    /// use legalis_core::{Statute, Effect};
6848    ///
6849    /// let derived = Statute::new("derived", "Derived Law", Effect::grant("Benefit"))
6850    ///     .with_derives_from("source-1")
6851    ///     .with_derives_from("source-2");
6852    ///
6853    /// assert_eq!(derived.derivation_sources(), &["source-1", "source-2"]);
6854    /// ```
6855    pub fn derivation_sources(&self) -> &[String] {
6856        &self.derives_from
6857    }
6858
6859    /// Returns whether this statute applies to a specific entity type.
6860    ///
6861    /// # Examples
6862    ///
6863    /// ```
6864    /// use legalis_core::{Statute, Effect};
6865    ///
6866    /// let law = Statute::new("law-1", "Law", Effect::grant("License"))
6867    ///     .with_applies_to("Corporation");
6868    ///
6869    /// assert!(law.applies_to_entity_type("Corporation"));
6870    /// assert!(!law.applies_to_entity_type("Individual"));
6871    /// ```
6872    #[must_use]
6873    pub fn applies_to_entity_type(&self, entity_type: &str) -> bool {
6874        self.applies_to.iter().any(|t| t == entity_type)
6875    }
6876
6877    /// Returns whether this statute has any entity type restrictions.
6878    ///
6879    /// If this returns `false`, the statute applies to all entity types.
6880    ///
6881    /// # Examples
6882    ///
6883    /// ```
6884    /// use legalis_core::{Statute, Effect};
6885    ///
6886    /// let general_law = Statute::new("law-1", "General Law", Effect::grant("Benefit"));
6887    /// assert!(!general_law.has_entity_restrictions());
6888    ///
6889    /// let specific_law = Statute::new("law-2", "Specific Law", Effect::grant("Benefit"))
6890    ///     .with_applies_to("Corporation");
6891    /// assert!(specific_law.has_entity_restrictions());
6892    /// ```
6893    #[must_use]
6894    pub fn has_entity_restrictions(&self) -> bool {
6895        !self.applies_to.is_empty()
6896    }
6897
6898    /// Returns the entity types this statute applies to.
6899    pub fn applicable_entity_types(&self) -> &[String] {
6900        &self.applies_to
6901    }
6902
6903    /// Returns whether this statute has exceptions.
6904    ///
6905    /// # Examples
6906    ///
6907    /// ```
6908    /// use legalis_core::{Statute, Effect, StatuteException, Condition, ComparisonOp};
6909    ///
6910    /// let law = Statute::new("law-1", "Law", Effect::grant("Benefit"))
6911    ///     .with_exception(StatuteException::new(
6912    ///         "exc-1",
6913    ///         "Exception",
6914    ///         Condition::age(ComparisonOp::LessThan, 18)
6915    ///     ));
6916    ///
6917    /// assert!(law.has_exceptions());
6918    /// ```
6919    #[must_use]
6920    pub fn has_exceptions(&self) -> bool {
6921        !self.exceptions.is_empty()
6922    }
6923
6924    /// Returns a reference to the exceptions.
6925    pub fn exception_list(&self) -> &[StatuteException] {
6926        &self.exceptions
6927    }
6928
6929    /// Returns the number of exceptions.
6930    #[must_use]
6931    pub fn exception_count(&self) -> usize {
6932        self.exceptions.len()
6933    }
6934
6935    /// Validates the statute and returns a list of validation errors.
6936    pub fn validate(&self) -> Vec<ValidationError> {
6937        let mut errors = Vec::new();
6938
6939        // Validate ID
6940        if self.id.is_empty() {
6941            errors.push(ValidationError::EmptyId);
6942        } else if !self.is_valid_id(&self.id) {
6943            errors.push(ValidationError::InvalidId(self.id.clone()));
6944        }
6945
6946        // Validate title
6947        if self.title.is_empty() {
6948            errors.push(ValidationError::EmptyTitle);
6949        }
6950
6951        // Validate temporal consistency
6952        if let (Some(eff), Some(exp)) = (
6953            self.temporal_validity.effective_date,
6954            self.temporal_validity.expiry_date,
6955        ) && exp < eff
6956        {
6957            errors.push(ValidationError::ExpiryBeforeEffective {
6958                effective: eff,
6959                expiry: exp,
6960            });
6961        }
6962
6963        // Validate preconditions
6964        for (i, cond) in self.preconditions.iter().enumerate() {
6965            if let Some(err) = Self::validate_condition(cond) {
6966                errors.push(ValidationError::InvalidCondition {
6967                    index: i,
6968                    message: err,
6969                });
6970            }
6971        }
6972
6973        // Validate effect
6974        if self.effect.description.is_empty() {
6975            errors.push(ValidationError::EmptyEffectDescription);
6976        }
6977
6978        // Validate version
6979        if self.version == 0 {
6980            errors.push(ValidationError::InvalidVersion);
6981        }
6982
6983        errors
6984    }
6985
6986    /// Returns true if the statute is valid (has no validation errors).
6987    pub fn is_valid(&self) -> bool {
6988        self.validate().is_empty()
6989    }
6990
6991    /// Validates the statute and returns an error if invalid.
6992    pub fn validated(self) -> Result<Self, Vec<ValidationError>> {
6993        let errors = self.validate();
6994        if errors.is_empty() {
6995            Ok(self)
6996        } else {
6997            Err(errors)
6998        }
6999    }
7000
7001    /// Checks if an ID is valid (alphanumeric with dashes/underscores).
7002    fn is_valid_id(&self, id: &str) -> bool {
7003        !id.is_empty()
7004            && id
7005                .chars()
7006                .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
7007            && id.chars().next().is_some_and(|c| c.is_alphabetic())
7008    }
7009
7010    /// Validates a condition recursively.
7011    fn validate_condition(condition: &Condition) -> Option<String> {
7012        match condition {
7013            Condition::Age { value, .. } => {
7014                if *value > 150 {
7015                    Some(format!("Unrealistic age value: {}", value))
7016                } else {
7017                    None
7018                }
7019            }
7020            Condition::And(left, right) | Condition::Or(left, right) => {
7021                Self::validate_condition(left).or_else(|| Self::validate_condition(right))
7022            }
7023            Condition::Not(inner) => Self::validate_condition(inner),
7024            Condition::ResidencyDuration { months, .. } => {
7025                if *months > 1200 {
7026                    Some(format!("Unrealistic residency duration: {} months", months))
7027                } else {
7028                    None
7029                }
7030            }
7031            _ => None,
7032        }
7033    }
7034
7035    /// Checks if this statute subsumes another statute.
7036    ///
7037    /// Statute A subsumes statute B if:
7038    /// - A's preconditions are more general than (or equal to) B's preconditions
7039    /// - A's effect is the same or broader than B's effect
7040    /// - Whenever B applies, A also applies
7041    ///
7042    /// This is useful for detecting redundancy and logical relationships between statutes.
7043    ///
7044    /// **Note**: This is a simplified heuristic-based implementation.
7045    /// Full subsumption checking would require logical analysis of condition relationships
7046    /// (e.g., recognizing that age >= 18 subsumes age >= 21).
7047    ///
7048    /// # Examples
7049    ///
7050    /// ```
7051    /// use legalis_core::{Statute, Effect, EffectType, Condition, ComparisonOp};
7052    ///
7053    /// // Statute with no preconditions subsumes one with preconditions (same effect)
7054    /// let general = Statute::new("general", "Voting Rights", Effect::grant("Vote"));
7055    ///
7056    /// let specific = Statute::new("specific", "Voting Rights (21+)", Effect::grant("Vote"))
7057    ///     .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 21));
7058    ///
7059    /// // General (no conditions) subsumes specific (has conditions)
7060    /// assert_eq!(general.subsumes(&specific), true);
7061    /// assert_eq!(specific.subsumes(&general), false);
7062    /// ```
7063    #[must_use]
7064    pub fn subsumes(&self, other: &Self) -> bool {
7065        // For a basic implementation, check if:
7066        // 1. Effects are compatible (same type and description)
7067        // 2. This statute's preconditions are more general
7068
7069        // Check effect compatibility
7070        if self.effect.effect_type != other.effect.effect_type
7071            || self.effect.description != other.effect.description
7072        {
7073            return false;
7074        }
7075
7076        // Check jurisdiction compatibility
7077        if self.jurisdiction != other.jurisdiction && self.jurisdiction.is_some() {
7078            return false;
7079        }
7080
7081        // For preconditions, we do a simplified check:
7082        // If this statute has fewer or equally general preconditions, it subsumes the other
7083        // This is a simplified implementation - full subsumption would require
7084        // logical analysis of condition relationships
7085
7086        // If this has no preconditions, it subsumes any statute with the same effect
7087        if self.preconditions.is_empty() {
7088            return true;
7089        }
7090
7091        // If other has no preconditions but this does, this doesn't subsume
7092        if other.preconditions.is_empty() {
7093            return false;
7094        }
7095
7096        // Simplified heuristic: if precondition count is less, more general
7097        // For a proper implementation, use logical subsumption checking
7098        self.preconditions.len() <= other.preconditions.len()
7099    }
7100
7101    /// Checks if this statute is subsumed by another statute.
7102    ///
7103    /// This is the inverse of [`Self::subsumes`].
7104    ///
7105    /// # Examples
7106    ///
7107    /// ```
7108    /// use legalis_core::{Statute, Effect, EffectType, Condition, ComparisonOp};
7109    ///
7110    /// let general = Statute::new("general", "General Rule", Effect::grant("Benefit"))
7111    ///     .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18));
7112    ///
7113    /// let specific = Statute::new("specific", "Specific Rule", Effect::grant("Benefit"))
7114    ///     .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 21));
7115    ///
7116    /// assert_eq!(specific.is_subsumed_by(&general), true);
7117    /// ```
7118    #[must_use]
7119    pub fn is_subsumed_by(&self, other: &Self) -> bool {
7120        other.subsumes(self)
7121    }
7122
7123    /// Computes the differences between this statute and another version.
7124    ///
7125    /// This is useful for tracking amendments, understanding changes over time,
7126    /// and generating change logs for legal documents.
7127    ///
7128    /// # Examples
7129    ///
7130    /// ```
7131    /// use legalis_core::{Statute, Effect, EffectType, Condition, ComparisonOp};
7132    ///
7133    /// let version1 = Statute::new("tax-1", "Tax Law", Effect::grant("Tax Credit"))
7134    ///     .with_precondition(Condition::income(ComparisonOp::LessThan, 50000))
7135    ///     .with_version(1);
7136    ///
7137    /// let version2 = Statute::new("tax-1", "Tax Law (Amended)", Effect::grant("Tax Credit"))
7138    ///     .with_precondition(Condition::income(ComparisonOp::LessThan, 60000))
7139    ///     .with_version(2);
7140    ///
7141    /// let diff = version1.diff(&version2);
7142    /// assert!(!diff.changes.is_empty());
7143    /// ```
7144    #[must_use]
7145    pub fn diff(&self, other: &Self) -> StatuteDiff {
7146        let mut changes = Vec::new();
7147
7148        // Check ID change
7149        if self.id != other.id {
7150            changes.push(StatuteChange::IdChanged {
7151                old: self.id.clone(),
7152                new: other.id.clone(),
7153            });
7154        }
7155
7156        // Check title change
7157        if self.title != other.title {
7158            changes.push(StatuteChange::TitleChanged {
7159                old: self.title.clone(),
7160                new: other.title.clone(),
7161            });
7162        }
7163
7164        // Check effect change
7165        if self.effect != other.effect {
7166            changes.push(StatuteChange::EffectChanged {
7167                old: format!("{}", self.effect),
7168                new: format!("{}", other.effect),
7169            });
7170        }
7171
7172        // Check preconditions change
7173        if self.preconditions != other.preconditions {
7174            changes.push(StatuteChange::PreconditionsChanged {
7175                added: other
7176                    .preconditions
7177                    .len()
7178                    .saturating_sub(self.preconditions.len()),
7179                removed: self
7180                    .preconditions
7181                    .len()
7182                    .saturating_sub(other.preconditions.len()),
7183            });
7184        }
7185
7186        // Check temporal validity change
7187        if self.temporal_validity != other.temporal_validity {
7188            changes.push(StatuteChange::TemporalValidityChanged);
7189        }
7190
7191        // Check version change
7192        if self.version != other.version {
7193            changes.push(StatuteChange::VersionChanged {
7194                old: self.version,
7195                new: other.version,
7196            });
7197        }
7198
7199        // Check jurisdiction change
7200        if self.jurisdiction != other.jurisdiction {
7201            changes.push(StatuteChange::JurisdictionChanged {
7202                old: self.jurisdiction.clone(),
7203                new: other.jurisdiction.clone(),
7204            });
7205        }
7206
7207        StatuteDiff {
7208            statute_id: self.id.clone(),
7209            changes,
7210        }
7211    }
7212}
7213
7214/// Represents differences between two versions of a statute.
7215#[derive(Debug, Clone, PartialEq)]
7216#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7217#[cfg_attr(feature = "schema", derive(JsonSchema))]
7218pub struct StatuteDiff {
7219    /// ID of the statute being compared
7220    pub statute_id: String,
7221    /// List of changes detected
7222    pub changes: Vec<StatuteChange>,
7223}
7224
7225impl StatuteDiff {
7226    /// Returns true if there are no changes.
7227    #[must_use]
7228    pub fn is_empty(&self) -> bool {
7229        self.changes.is_empty()
7230    }
7231
7232    /// Returns the number of changes.
7233    #[must_use]
7234    pub fn change_count(&self) -> usize {
7235        self.changes.len()
7236    }
7237}
7238
7239/// Types of changes that can occur in a statute.
7240#[derive(Debug, Clone, PartialEq)]
7241#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7242#[cfg_attr(feature = "schema", derive(JsonSchema))]
7243pub enum StatuteChange {
7244    /// The statute ID was changed
7245    IdChanged { old: String, new: String },
7246    /// The statute title was changed
7247    TitleChanged { old: String, new: String },
7248    /// The effect was changed
7249    EffectChanged { old: String, new: String },
7250    /// Preconditions were modified
7251    PreconditionsChanged { added: usize, removed: usize },
7252    /// Temporal validity was changed
7253    TemporalValidityChanged,
7254    /// Version number was changed
7255    VersionChanged { old: u32, new: u32 },
7256    /// Jurisdiction was changed
7257    JurisdictionChanged {
7258        old: Option<String>,
7259        new: Option<String>,
7260    },
7261}
7262
7263impl fmt::Display for StatuteDiff {
7264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7265        if self.is_empty() {
7266            return write!(f, "No changes for statute '{}'", self.statute_id);
7267        }
7268
7269        writeln!(f, "Changes for statute '{}':", self.statute_id)?;
7270        for (i, change) in self.changes.iter().enumerate() {
7271            writeln!(f, "  {}. {}", i + 1, change)?;
7272        }
7273        Ok(())
7274    }
7275}
7276
7277impl fmt::Display for StatuteChange {
7278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7279        match self {
7280            Self::IdChanged { old, new } => write!(f, "ID: '{}' → '{}'", old, new),
7281            Self::TitleChanged { old, new } => write!(f, "Title: '{}' → '{}'", old, new),
7282            Self::EffectChanged { old, new } => write!(f, "Effect: {} → {}", old, new),
7283            Self::PreconditionsChanged { added, removed } => {
7284                write!(f, "Preconditions: +{} -{}", added, removed)
7285            }
7286            Self::TemporalValidityChanged => write!(f, "Temporal validity changed"),
7287            Self::VersionChanged { old, new } => write!(f, "Version: {} → {}", old, new),
7288            Self::JurisdictionChanged { old, new } => {
7289                write!(
7290                    f,
7291                    "Jurisdiction: {} → {}",
7292                    old.as_deref().unwrap_or("None"),
7293                    new.as_deref().unwrap_or("None")
7294                )
7295            }
7296        }
7297    }
7298}
7299
7300/// Error severity levels.
7301#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7302#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7303#[cfg_attr(feature = "schema", derive(JsonSchema))]
7304pub enum ErrorSeverity {
7305    /// Warning - potential issue but not critical.
7306    Warning,
7307    /// Error - significant problem that should be addressed.
7308    Error,
7309    /// Critical - fundamental issue that prevents operation.
7310    Critical,
7311}
7312
7313impl fmt::Display for ErrorSeverity {
7314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7315        match self {
7316            Self::Warning => write!(f, "WARNING"),
7317            Self::Error => write!(f, "ERROR"),
7318            Self::Critical => write!(f, "CRITICAL"),
7319        }
7320    }
7321}
7322
7323/// Validation errors for statutes with error codes and severity.
7324#[derive(Debug, Clone, PartialEq)]
7325#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7326#[cfg_attr(feature = "schema", derive(JsonSchema))]
7327pub enum ValidationError {
7328    /// Statute ID is empty.
7329    EmptyId,
7330    /// Statute ID contains invalid characters.
7331    InvalidId(String),
7332    /// Statute title is empty.
7333    EmptyTitle,
7334    /// Expiry date is before effective date.
7335    ExpiryBeforeEffective {
7336        effective: NaiveDate,
7337        expiry: NaiveDate,
7338    },
7339    /// A precondition is invalid.
7340    InvalidCondition { index: usize, message: String },
7341    /// Effect description is empty.
7342    EmptyEffectDescription,
7343    /// Version must be > 0.
7344    InvalidVersion,
7345}
7346
7347impl ValidationError {
7348    /// Returns the error code for this validation error.
7349    #[must_use]
7350    pub const fn error_code(&self) -> &'static str {
7351        match self {
7352            Self::EmptyId => "E001",
7353            Self::InvalidId(_) => "E002",
7354            Self::EmptyTitle => "E003",
7355            Self::ExpiryBeforeEffective { .. } => "E004",
7356            Self::InvalidCondition { .. } => "E005",
7357            Self::EmptyEffectDescription => "E006",
7358            Self::InvalidVersion => "E007",
7359        }
7360    }
7361
7362    /// Returns the severity level of this error.
7363    #[must_use]
7364    pub const fn severity(&self) -> ErrorSeverity {
7365        match self {
7366            Self::EmptyId | Self::EmptyTitle | Self::EmptyEffectDescription => {
7367                ErrorSeverity::Critical
7368            }
7369            Self::InvalidId(_) | Self::InvalidVersion => ErrorSeverity::Error,
7370            Self::ExpiryBeforeEffective { .. } | Self::InvalidCondition { .. } => {
7371                ErrorSeverity::Warning
7372            }
7373        }
7374    }
7375
7376    /// Returns a suggestion for how to fix this error.
7377    #[must_use]
7378    pub fn suggestion(&self) -> Option<&str> {
7379        match self {
7380            Self::EmptyId => Some("Provide a non-empty ID for the statute"),
7381            Self::InvalidId(_) => {
7382                Some("Use only alphanumeric characters, hyphens, and underscores in IDs")
7383            }
7384            Self::EmptyTitle => Some("Provide a descriptive title for the statute"),
7385            Self::ExpiryBeforeEffective { .. } => {
7386                Some("Ensure the expiry date is after the effective date")
7387            }
7388            Self::InvalidCondition { .. } => {
7389                Some("Review and fix the condition, or remove it if not needed")
7390            }
7391            Self::EmptyEffectDescription => Some("Provide a description for the effect"),
7392            Self::InvalidVersion => Some("Version must be greater than 0"),
7393        }
7394    }
7395
7396    /// Returns multiple recovery options for this error.
7397    ///
7398    /// # Examples
7399    ///
7400    /// ```
7401    /// use legalis_core::ValidationError;
7402    ///
7403    /// let err = ValidationError::EmptyId;
7404    /// let options = err.recovery_options();
7405    /// assert!(!options.is_empty());
7406    /// ```
7407    #[must_use]
7408    pub fn recovery_options(&self) -> Vec<String> {
7409        match self {
7410            Self::EmptyId => vec![
7411                "Generate a unique ID based on title".to_string(),
7412                "Use a UUID as the ID".to_string(),
7413                "Derive ID from jurisdiction and statute number".to_string(),
7414            ],
7415            Self::InvalidId(id) => vec![
7416                format!("Remove invalid characters from '{}'", id),
7417                "Replace spaces with hyphens or underscores".to_string(),
7418                "Start ID with a letter if it begins with a number".to_string(),
7419            ],
7420            Self::EmptyTitle => vec![
7421                "Add a descriptive title summarizing the statute".to_string(),
7422                "Use the statute ID as a temporary title".to_string(),
7423            ],
7424            Self::ExpiryBeforeEffective { effective, expiry } => vec![
7425                format!("Change expiry date to be after {}", effective),
7426                format!("Change effective date to be before {}", expiry),
7427                "Remove the expiry date if statute doesn't expire".to_string(),
7428            ],
7429            Self::InvalidCondition { index, message } => vec![
7430                format!("Fix condition at index {}: {}", index, message),
7431                format!("Remove condition at index {}", index),
7432                "Simplify the condition to avoid validation issues".to_string(),
7433            ],
7434            Self::EmptyEffectDescription => vec![
7435                "Add a description explaining what the effect does".to_string(),
7436                "Use the effect type as a default description".to_string(),
7437            ],
7438            Self::InvalidVersion => vec![
7439                "Set version to 1 for new statutes".to_string(),
7440                "Increment version number from previous version".to_string(),
7441            ],
7442        }
7443    }
7444
7445    /// Attempts to automatically fix this error if possible.
7446    ///
7447    /// Returns a description of the fix applied, or None if auto-fix is not available.
7448    ///
7449    /// # Examples
7450    ///
7451    /// ```
7452    /// use legalis_core::ValidationError;
7453    ///
7454    /// let err = ValidationError::InvalidId("my statute!".to_string());
7455    /// let fixed = err.try_auto_fix();
7456    /// assert!(fixed.is_some());
7457    /// ```
7458    #[must_use]
7459    pub fn try_auto_fix(&self) -> Option<(String, String)> {
7460        match self {
7461            Self::InvalidId(id) => {
7462                let fixed = id
7463                    .chars()
7464                    .map(|c| {
7465                        if c.is_alphanumeric() || c == '-' || c == '_' {
7466                            c
7467                        } else if c.is_whitespace() {
7468                            '-'
7469                        } else {
7470                            '_'
7471                        }
7472                    })
7473                    .collect::<String>();
7474                Some((
7475                    fixed,
7476                    "Replaced invalid characters with hyphens/underscores".to_string(),
7477                ))
7478            }
7479            Self::InvalidVersion => Some((
7480                "1".to_string(),
7481                "Set version to 1 (default for new statutes)".to_string(),
7482            )),
7483            _ => None,
7484        }
7485    }
7486}
7487
7488/// Condition evaluation errors.
7489#[derive(Debug, Clone, PartialEq)]
7490#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7491#[cfg_attr(feature = "schema", derive(JsonSchema))]
7492pub enum ConditionError {
7493    /// Missing attribute in entity.
7494    MissingAttribute { key: String },
7495    /// Type mismatch when evaluating condition.
7496    TypeMismatch { expected: String, actual: String },
7497    /// Invalid calculation formula.
7498    InvalidFormula { formula: String, error: String },
7499    /// Pattern matching error.
7500    PatternError { pattern: String, error: String },
7501    /// Evaluation exceeded maximum depth (possible infinite recursion).
7502    MaxDepthExceeded { max_depth: usize },
7503    /// Custom evaluation error.
7504    Custom { message: String },
7505}
7506
7507impl ConditionError {
7508    /// Returns the error code for this condition error.
7509    #[must_use]
7510    pub const fn error_code(&self) -> &'static str {
7511        match self {
7512            Self::MissingAttribute { .. } => "C001",
7513            Self::TypeMismatch { .. } => "C002",
7514            Self::InvalidFormula { .. } => "C003",
7515            Self::PatternError { .. } => "C004",
7516            Self::MaxDepthExceeded { .. } => "C005",
7517            Self::Custom { .. } => "C999",
7518        }
7519    }
7520
7521    /// Returns the severity level of this error.
7522    #[must_use]
7523    pub const fn severity(&self) -> ErrorSeverity {
7524        match self {
7525            Self::MissingAttribute { .. } | Self::TypeMismatch { .. } => ErrorSeverity::Error,
7526            Self::InvalidFormula { .. } | Self::PatternError { .. } => ErrorSeverity::Critical,
7527            Self::MaxDepthExceeded { .. } => ErrorSeverity::Critical,
7528            Self::Custom { .. } => ErrorSeverity::Error,
7529        }
7530    }
7531
7532    /// Returns a suggestion for how to fix this error.
7533    ///
7534    /// # Examples
7535    ///
7536    /// ```
7537    /// use legalis_core::ConditionError;
7538    ///
7539    /// let err = ConditionError::MissingAttribute {
7540    ///     key: "age".to_string(),
7541    /// };
7542    /// assert!(err.suggestion().is_some());
7543    /// ```
7544    #[must_use]
7545    pub fn suggestion(&self) -> Option<String> {
7546        match self {
7547            Self::MissingAttribute { key } => Some(format!(
7548                "Add the '{}' attribute to the entity before evaluation",
7549                key
7550            )),
7551            Self::TypeMismatch { expected, actual } => Some(format!(
7552                "Convert the value from {} to {} or adjust the condition type",
7553                actual, expected
7554            )),
7555            Self::InvalidFormula { formula, error } => Some(format!(
7556                "Fix the formula '{}': {}. Check syntax and ensure all variables are defined.",
7557                formula, error
7558            )),
7559            Self::PatternError { pattern, error } => Some(format!(
7560                "Fix the regex pattern '{}': {}. Ensure the pattern is valid regex syntax.",
7561                pattern, error
7562            )),
7563            Self::MaxDepthExceeded { max_depth } => Some(format!(
7564                "Simplify the condition structure to reduce nesting below {} levels, or check for circular references",
7565                max_depth
7566            )),
7567            Self::Custom { .. } => None,
7568        }
7569    }
7570
7571    /// Returns multiple recovery options for this error.
7572    ///
7573    /// # Examples
7574    ///
7575    /// ```
7576    /// use legalis_core::ConditionError;
7577    ///
7578    /// let err = ConditionError::TypeMismatch {
7579    ///     expected: "u32".to_string(),
7580    ///     actual: "String".to_string(),
7581    /// };
7582    /// let options = err.recovery_options();
7583    /// assert!(!options.is_empty());
7584    /// ```
7585    #[must_use]
7586    pub fn recovery_options(&self) -> Vec<String> {
7587        match self {
7588            Self::MissingAttribute { key } => vec![
7589                format!("Add '{}' to entity attributes", key),
7590                "Use default value for missing attribute".to_string(),
7591                "Make this condition optional".to_string(),
7592            ],
7593            Self::TypeMismatch { expected, actual } => vec![
7594                format!("Convert {} to {}", actual, expected),
7595                "Change condition to accept current type".to_string(),
7596                "Add type conversion in evaluation context".to_string(),
7597            ],
7598            Self::InvalidFormula { .. } => vec![
7599                "Fix formula syntax".to_string(),
7600                "Use simpler condition type instead of calculation".to_string(),
7601                "Define missing variables in context".to_string(),
7602            ],
7603            Self::PatternError { .. } => vec![
7604                "Fix regex syntax".to_string(),
7605                "Escape special regex characters".to_string(),
7606                "Use simpler string comparison instead".to_string(),
7607            ],
7608            Self::MaxDepthExceeded { .. } => vec![
7609                "Flatten nested conditions using normalization".to_string(),
7610                "Break complex condition into multiple simpler ones".to_string(),
7611                "Check for and remove circular condition references".to_string(),
7612            ],
7613            Self::Custom { .. } => vec![],
7614        }
7615    }
7616}
7617
7618impl fmt::Display for ConditionError {
7619    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7620        match self {
7621            Self::MissingAttribute { key } => write!(f, "Missing attribute: {}", key),
7622            Self::TypeMismatch { expected, actual } => {
7623                write!(f, "Type mismatch: expected {}, got {}", expected, actual)
7624            }
7625            Self::InvalidFormula { formula, error } => {
7626                write!(f, "Invalid formula '{}': {}", formula, error)
7627            }
7628            Self::PatternError { pattern, error } => {
7629                write!(f, "Pattern error '{}': {}", pattern, error)
7630            }
7631            Self::MaxDepthExceeded { max_depth } => {
7632                write!(f, "Maximum evaluation depth ({}) exceeded", max_depth)
7633            }
7634            Self::Custom { message } => write!(f, "{}", message),
7635        }
7636    }
7637}
7638
7639impl std::error::Error for ConditionError {}
7640
7641impl fmt::Display for ValidationError {
7642    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7643        match self {
7644            Self::EmptyId => write!(f, "Statute ID cannot be empty"),
7645            Self::InvalidId(id) => write!(
7646                f,
7647                "Invalid statute ID: '{}' (must start with letter, contain only alphanumeric/dash/underscore)",
7648                id
7649            ),
7650            Self::EmptyTitle => write!(f, "Statute title cannot be empty"),
7651            Self::ExpiryBeforeEffective { effective, expiry } => {
7652                write!(
7653                    f,
7654                    "Expiry date ({}) cannot be before effective date ({})",
7655                    expiry, effective
7656                )
7657            }
7658            Self::InvalidCondition { index, message } => {
7659                write!(f, "Invalid condition at index {}: {}", index, message)
7660            }
7661            Self::EmptyEffectDescription => write!(f, "Effect description cannot be empty"),
7662            Self::InvalidVersion => write!(f, "Version must be greater than 0"),
7663        }
7664    }
7665}
7666
7667impl std::error::Error for ValidationError {}
7668
7669// ==================================================
7670// Enhanced Diagnostic Context for Validation Errors
7671// ==================================================
7672
7673/// Source location information for error diagnostics.
7674#[derive(Debug, Clone, PartialEq, Eq)]
7675#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7676#[cfg_attr(feature = "schema", derive(JsonSchema))]
7677pub struct SourceLocation {
7678    /// File path or source identifier
7679    pub file: Option<String>,
7680    /// Line number (1-indexed)
7681    pub line: Option<usize>,
7682    /// Column number (1-indexed)
7683    pub column: Option<usize>,
7684    /// Source snippet for context
7685    pub snippet: Option<String>,
7686}
7687
7688impl SourceLocation {
7689    /// Creates a new source location.
7690    #[must_use]
7691    pub fn new() -> Self {
7692        Self {
7693            file: None,
7694            line: None,
7695            column: None,
7696            snippet: None,
7697        }
7698    }
7699
7700    /// Sets the file path.
7701    #[must_use]
7702    pub fn with_file(mut self, file: impl Into<String>) -> Self {
7703        self.file = Some(file.into());
7704        self
7705    }
7706
7707    /// Sets the line number.
7708    #[must_use]
7709    pub const fn with_line(mut self, line: usize) -> Self {
7710        self.line = Some(line);
7711        self
7712    }
7713
7714    /// Sets the column number.
7715    #[must_use]
7716    pub const fn with_column(mut self, column: usize) -> Self {
7717        self.column = Some(column);
7718        self
7719    }
7720
7721    /// Sets the source snippet.
7722    #[must_use]
7723    pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
7724        self.snippet = Some(snippet.into());
7725        self
7726    }
7727}
7728
7729impl Default for SourceLocation {
7730    fn default() -> Self {
7731        Self::new()
7732    }
7733}
7734
7735impl fmt::Display for SourceLocation {
7736    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7737        if let Some(file) = &self.file {
7738            write!(f, "{}", file)?;
7739            if let Some(line) = self.line {
7740                write!(f, ":{}", line)?;
7741                if let Some(column) = self.column {
7742                    write!(f, ":{}", column)?;
7743                }
7744            }
7745        } else if let Some(line) = self.line {
7746            write!(f, "line {}", line)?;
7747            if let Some(column) = self.column {
7748                write!(f, ":{}", column)?;
7749            }
7750        } else {
7751            write!(f, "unknown location")?;
7752        }
7753        Ok(())
7754    }
7755}
7756
7757/// Diagnostic context for detailed error reporting.
7758#[derive(Debug, Clone, PartialEq, Eq)]
7759#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7760#[cfg_attr(feature = "schema", derive(JsonSchema))]
7761pub struct DiagnosticContext {
7762    /// Source location where the error occurred
7763    pub location: Option<SourceLocation>,
7764    /// Related statute ID
7765    pub statute_id: Option<String>,
7766    /// Related condition description
7767    pub condition: Option<String>,
7768    /// Stack trace or call chain
7769    pub stack: Vec<String>,
7770    /// Additional contextual notes
7771    pub notes: Vec<String>,
7772    /// Suggested fixes
7773    pub suggestions: Vec<String>,
7774}
7775
7776impl DiagnosticContext {
7777    /// Creates a new empty diagnostic context.
7778    #[must_use]
7779    pub fn new() -> Self {
7780        Self {
7781            location: None,
7782            statute_id: None,
7783            condition: None,
7784            stack: Vec::new(),
7785            notes: Vec::new(),
7786            suggestions: Vec::new(),
7787        }
7788    }
7789
7790    /// Sets the source location.
7791    #[must_use]
7792    pub fn with_location(mut self, location: SourceLocation) -> Self {
7793        self.location = Some(location);
7794        self
7795    }
7796
7797    /// Sets the statute ID.
7798    #[must_use]
7799    pub fn with_statute_id(mut self, id: impl Into<String>) -> Self {
7800        self.statute_id = Some(id.into());
7801        self
7802    }
7803
7804    /// Sets the condition description.
7805    #[must_use]
7806    pub fn with_condition(mut self, condition: impl Into<String>) -> Self {
7807        self.condition = Some(condition.into());
7808        self
7809    }
7810
7811    /// Adds a stack frame.
7812    pub fn add_stack_frame(&mut self, frame: impl Into<String>) {
7813        self.stack.push(frame.into());
7814    }
7815
7816    /// Adds a note.
7817    pub fn add_note(&mut self, note: impl Into<String>) {
7818        self.notes.push(note.into());
7819    }
7820
7821    /// Adds a suggestion.
7822    pub fn add_suggestion(&mut self, suggestion: impl Into<String>) {
7823        self.suggestions.push(suggestion.into());
7824    }
7825
7826    /// Builder method to add a stack frame.
7827    #[must_use]
7828    pub fn with_stack_frame(mut self, frame: impl Into<String>) -> Self {
7829        self.stack.push(frame.into());
7830        self
7831    }
7832
7833    /// Builder method to add a note.
7834    #[must_use]
7835    pub fn with_note(mut self, note: impl Into<String>) -> Self {
7836        self.notes.push(note.into());
7837        self
7838    }
7839
7840    /// Builder method to add a suggestion.
7841    #[must_use]
7842    pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
7843        self.suggestions.push(suggestion.into());
7844        self
7845    }
7846}
7847
7848impl Default for DiagnosticContext {
7849    fn default() -> Self {
7850        Self::new()
7851    }
7852}
7853
7854impl fmt::Display for DiagnosticContext {
7855    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7856        if let Some(location) = &self.location {
7857            writeln!(f, "at {}", location)?;
7858            if let Some(snippet) = &location.snippet {
7859                writeln!(f, "  {}", snippet)?;
7860            }
7861        }
7862
7863        if let Some(statute_id) = &self.statute_id {
7864            writeln!(f, "in statute: {}", statute_id)?;
7865        }
7866
7867        if let Some(condition) = &self.condition {
7868            writeln!(f, "condition: {}", condition)?;
7869        }
7870
7871        if !self.stack.is_empty() {
7872            writeln!(f, "\nStack trace:")?;
7873            for (i, frame) in self.stack.iter().enumerate() {
7874                writeln!(f, "  {}: {}", i, frame)?;
7875            }
7876        }
7877
7878        if !self.notes.is_empty() {
7879            writeln!(f, "\nNotes:")?;
7880            for note in &self.notes {
7881                writeln!(f, "  - {}", note)?;
7882            }
7883        }
7884
7885        if !self.suggestions.is_empty() {
7886            writeln!(f, "\nSuggestions:")?;
7887            for suggestion in &self.suggestions {
7888                writeln!(f, "  - {}", suggestion)?;
7889            }
7890        }
7891
7892        Ok(())
7893    }
7894}
7895
7896/// Enhanced validation error with diagnostic context.
7897#[derive(Debug, Clone, PartialEq)]
7898#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7899pub struct DiagnosticValidationError {
7900    /// The base validation error
7901    pub error: ValidationError,
7902    /// Diagnostic context
7903    pub context: DiagnosticContext,
7904}
7905
7906impl DiagnosticValidationError {
7907    /// Creates a new diagnostic validation error.
7908    #[must_use]
7909    pub fn new(error: ValidationError) -> Self {
7910        Self {
7911            error,
7912            context: DiagnosticContext::new(),
7913        }
7914    }
7915
7916    /// Adds diagnostic context.
7917    #[must_use]
7918    pub fn with_context(mut self, context: DiagnosticContext) -> Self {
7919        self.context = context;
7920        self
7921    }
7922
7923    /// Gets the error code.
7924    #[must_use]
7925    pub fn error_code(&self) -> &str {
7926        self.error.error_code()
7927    }
7928
7929    /// Gets the error severity.
7930    #[must_use]
7931    pub fn severity(&self) -> ErrorSeverity {
7932        self.error.severity()
7933    }
7934
7935    /// Gets a suggestion for fixing the error.
7936    #[must_use]
7937    pub fn suggestion(&self) -> Option<&str> {
7938        self.error.suggestion()
7939    }
7940}
7941
7942impl fmt::Display for DiagnosticValidationError {
7943    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7944        writeln!(f, "Error [{}]: {}", self.error_code(), self.error)?;
7945        write!(f, "{}", self.context)?;
7946        Ok(())
7947    }
7948}
7949
7950impl std::error::Error for DiagnosticValidationError {}
7951
7952/// Diagnostic error reporter for collecting and formatting errors.
7953///
7954/// # Examples
7955///
7956/// ```
7957/// use legalis_core::{DiagnosticReporter, ValidationError, SourceLocation, DiagnosticContext};
7958///
7959/// let mut reporter = DiagnosticReporter::new();
7960///
7961/// reporter.add_error(
7962///     ValidationError::EmptyTitle,
7963///     DiagnosticContext::new()
7964///         .with_statute_id("law-123")
7965///         .with_location(SourceLocation::new().with_file("statutes.json").with_line(45))
7966///         .with_suggestion("Add a 'title' field to the statute definition")
7967/// );
7968///
7969/// reporter.add_error(
7970///     ValidationError::InvalidVersion,
7971///     DiagnosticContext::new()
7972///         .with_statute_id("law-456")
7973///         .with_note("Version must be greater than 0")
7974/// );
7975///
7976/// // Print all errors with diagnostic context
7977/// println!("{}", reporter.report());
7978/// assert_eq!(reporter.error_count(), 2);
7979/// ```
7980#[derive(Debug, Default)]
7981pub struct DiagnosticReporter {
7982    errors: Vec<DiagnosticValidationError>,
7983}
7984
7985impl DiagnosticReporter {
7986    /// Creates a new diagnostic reporter.
7987    #[must_use]
7988    pub fn new() -> Self {
7989        Self::default()
7990    }
7991
7992    /// Adds an error with diagnostic context.
7993    pub fn add_error(&mut self, error: ValidationError, context: DiagnosticContext) {
7994        self.errors
7995            .push(DiagnosticValidationError { error, context });
7996    }
7997
7998    /// Adds an error without context.
7999    pub fn add_simple_error(&mut self, error: ValidationError) {
8000        self.errors.push(DiagnosticValidationError::new(error));
8001    }
8002
8003    /// Returns the number of errors.
8004    #[must_use]
8005    pub fn error_count(&self) -> usize {
8006        self.errors.len()
8007    }
8008
8009    /// Returns `true` if there are no errors.
8010    #[must_use]
8011    pub fn is_empty(&self) -> bool {
8012        self.errors.is_empty()
8013    }
8014
8015    /// Returns `true` if there are errors.
8016    #[must_use]
8017    pub fn has_errors(&self) -> bool {
8018        !self.errors.is_empty()
8019    }
8020
8021    /// Gets all errors.
8022    #[must_use]
8023    pub fn errors(&self) -> &[DiagnosticValidationError] {
8024        &self.errors
8025    }
8026
8027    /// Filters errors by severity.
8028    #[must_use]
8029    pub fn errors_with_severity(&self, severity: ErrorSeverity) -> Vec<&DiagnosticValidationError> {
8030        self.errors
8031            .iter()
8032            .filter(|e| e.severity() == severity)
8033            .collect()
8034    }
8035
8036    /// Returns only critical errors.
8037    #[must_use]
8038    pub fn critical_errors(&self) -> Vec<&DiagnosticValidationError> {
8039        self.errors_with_severity(ErrorSeverity::Critical)
8040    }
8041
8042    /// Clears all errors.
8043    pub fn clear(&mut self) {
8044        self.errors.clear();
8045    }
8046
8047    /// Generates a formatted error report.
8048    #[must_use]
8049    pub fn report(&self) -> String {
8050        if self.errors.is_empty() {
8051            return "No errors".to_string();
8052        }
8053
8054        let mut output = String::new();
8055        output.push_str(&format!("\n{} error(s) found:\n\n", self.errors.len()));
8056
8057        for (i, error) in self.errors.iter().enumerate() {
8058            output.push_str(&format!("{}. {}\n", i + 1, error));
8059        }
8060
8061        output
8062    }
8063
8064    /// Generates a summary of errors by type.
8065    #[must_use]
8066    pub fn summary(&self) -> String {
8067        let critical = self.critical_errors().len();
8068        let errors = self.errors_with_severity(ErrorSeverity::Error).len();
8069        let warnings = self.errors_with_severity(ErrorSeverity::Warning).len();
8070
8071        format!(
8072            "{} total ({} critical, {} errors, {} warnings)",
8073            self.error_count(),
8074            critical,
8075            errors,
8076            warnings
8077        )
8078    }
8079}
8080
8081impl fmt::Display for DiagnosticReporter {
8082    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8083        write!(f, "{}", self.report())
8084    }
8085}
8086
8087impl fmt::Display for Statute {
8088    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8089        writeln!(f, "STATUTE {}: \"{}\"", self.id, self.title)?;
8090        if let Some(ref jur) = self.jurisdiction {
8091            writeln!(f, "  JURISDICTION: {}", jur)?;
8092        }
8093        writeln!(f, "  VERSION: {}", self.version)?;
8094        writeln!(f, "  {}", self.temporal_validity)?;
8095        if !self.preconditions.is_empty() {
8096            writeln!(f, "  WHEN:")?;
8097            for cond in &self.preconditions {
8098                writeln!(f, "    {}", cond)?;
8099            }
8100        }
8101        writeln!(f, "  THEN: {}", self.effect)?;
8102        if let Some(ref disc) = self.discretion_logic {
8103            writeln!(f, "  DISCRETION: {}", disc)?;
8104        }
8105        Ok(())
8106    }
8107}
8108
8109/// Represents the outcome of a conflict resolution between two statutes.
8110#[derive(Debug, Clone, PartialEq, Eq)]
8111#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8112#[cfg_attr(feature = "schema", derive(JsonSchema))]
8113pub enum ConflictResolution {
8114    /// First statute prevails
8115    FirstPrevails(ConflictReason),
8116    /// Second statute prevails
8117    SecondPrevails(ConflictReason),
8118    /// No conflict - statutes are compatible
8119    NoConflict,
8120    /// Statutes conflict but cannot be automatically resolved
8121    Unresolvable(String),
8122}
8123
8124/// Reason why one statute prevails over another.
8125#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
8126#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8127#[cfg_attr(feature = "schema", derive(JsonSchema))]
8128pub enum ConflictReason {
8129    /// Later law prevails (lex posterior derogat legi priori)
8130    TemporalPrecedence,
8131    /// More specific law prevails (lex specialis derogat legi generali)
8132    Specificity,
8133    /// Higher authority prevails (lex superior derogat legi inferiori)
8134    Hierarchy,
8135    /// Explicit amendment/repeal relationship
8136    ExplicitAmendment,
8137}
8138
8139impl fmt::Display for ConflictReason {
8140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8141        match self {
8142            Self::TemporalPrecedence => write!(f, "lex posterior (later law prevails)"),
8143            Self::Specificity => write!(f, "lex specialis (more specific law prevails)"),
8144            Self::Hierarchy => write!(f, "lex superior (higher authority prevails)"),
8145            Self::ExplicitAmendment => write!(f, "explicit amendment/repeal"),
8146        }
8147    }
8148}
8149
8150impl fmt::Display for ConflictResolution {
8151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8152        match self {
8153            Self::FirstPrevails(reason) => write!(f, "First statute prevails: {}", reason),
8154            Self::SecondPrevails(reason) => write!(f, "Second statute prevails: {}", reason),
8155            Self::NoConflict => write!(f, "No conflict - statutes are compatible"),
8156            Self::Unresolvable(msg) => write!(f, "Unresolvable conflict: {}", msg),
8157        }
8158    }
8159}
8160
8161/// Statute conflict analyzer.
8162///
8163/// Provides methods to detect and resolve conflicts between statutes
8164/// using established legal principles.
8165pub struct StatuteConflictAnalyzer;
8166
8167impl StatuteConflictAnalyzer {
8168    /// Analyzes two statutes for conflicts and determines which should prevail.
8169    ///
8170    /// Uses the following hierarchy of resolution principles:
8171    /// 1. Explicit amendment relationships
8172    /// 2. Temporal precedence (newer laws)
8173    /// 3. Specificity (more specific laws)
8174    /// 4. Hierarchy (jurisdictional authority)
8175    ///
8176    /// # Examples
8177    ///
8178    /// ```
8179    /// use legalis_core::{Statute, Effect, EffectType, StatuteConflictAnalyzer, TemporalValidity};
8180    /// use chrono::NaiveDate;
8181    ///
8182    /// let old_law = Statute::new("old-1", "Old Law", Effect::new(EffectType::Grant, "Old grant"))
8183    ///     .with_temporal_validity(
8184    ///         TemporalValidity::new()
8185    ///             .with_effective_date(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap())
8186    ///     )
8187    ///     .with_version(1);
8188    ///
8189    /// let new_law = Statute::new("new-1", "New Law", Effect::new(EffectType::Grant, "New grant"))
8190    ///     .with_temporal_validity(
8191    ///         TemporalValidity::new()
8192    ///             .with_effective_date(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap())
8193    ///     )
8194    ///     .with_version(1);
8195    ///
8196    /// let resolution = StatuteConflictAnalyzer::resolve(&old_law, &new_law);
8197    /// // New law prevails by temporal precedence
8198    /// ```
8199    pub fn resolve(first: &Statute, second: &Statute) -> ConflictResolution {
8200        // Check if statutes actually conflict
8201        if !Self::has_conflict(first, second) {
8202            return ConflictResolution::NoConflict;
8203        }
8204
8205        // 1. Check for explicit amendments (would require hierarchy module integration)
8206        // For now, we'll implement temporal and specificity checks
8207
8208        // 2. Check temporal precedence
8209        if let Some(resolution) = Self::check_temporal_precedence(first, second) {
8210            return resolution;
8211        }
8212
8213        // 3. Check specificity
8214        if let Some(resolution) = Self::check_specificity(first, second) {
8215            return resolution;
8216        }
8217
8218        // 4. Check hierarchy (jurisdiction-based)
8219        if let Some(resolution) = Self::check_hierarchy(first, second) {
8220            return resolution;
8221        }
8222
8223        // Cannot automatically resolve
8224        ConflictResolution::Unresolvable(
8225            "Statutes conflict but resolution requires human judgment".to_string(),
8226        )
8227    }
8228
8229    /// Checks if two statutes have conflicting effects.
8230    fn has_conflict(first: &Statute, second: &Statute) -> bool {
8231        // Statutes conflict if they have incompatible effects
8232        // For simplicity, we consider Grant vs Prohibition/Revoke as conflicts
8233        use EffectType::*;
8234
8235        matches!(
8236            (&first.effect.effect_type, &second.effect.effect_type),
8237            (Grant, Prohibition)
8238                | (Grant, Revoke)
8239                | (Prohibition, Grant)
8240                | (Revoke, Grant)
8241                | (Obligation, Prohibition)
8242                | (Prohibition, Obligation)
8243        )
8244    }
8245
8246    /// Applies lex posterior (later law prevails).
8247    fn check_temporal_precedence(first: &Statute, second: &Statute) -> Option<ConflictResolution> {
8248        let first_date = first.temporal_validity.effective_date?;
8249        let second_date = second.temporal_validity.effective_date?;
8250
8251        if first_date > second_date {
8252            Some(ConflictResolution::FirstPrevails(
8253                ConflictReason::TemporalPrecedence,
8254            ))
8255        } else if second_date > first_date {
8256            Some(ConflictResolution::SecondPrevails(
8257                ConflictReason::TemporalPrecedence,
8258            ))
8259        } else {
8260            None // Same date, cannot resolve by temporal precedence
8261        }
8262    }
8263
8264    /// Applies lex specialis (more specific law prevails).
8265    ///
8266    /// A statute is considered more specific if it has more preconditions.
8267    fn check_specificity(first: &Statute, second: &Statute) -> Option<ConflictResolution> {
8268        let first_specificity = Self::calculate_specificity(first);
8269        let second_specificity = Self::calculate_specificity(second);
8270
8271        if first_specificity > second_specificity {
8272            Some(ConflictResolution::FirstPrevails(
8273                ConflictReason::Specificity,
8274            ))
8275        } else if second_specificity > first_specificity {
8276            Some(ConflictResolution::SecondPrevails(
8277                ConflictReason::Specificity,
8278            ))
8279        } else {
8280            None // Same specificity
8281        }
8282    }
8283
8284    /// Calculates specificity score based on number and complexity of conditions.
8285    fn calculate_specificity(statute: &Statute) -> usize {
8286        statute
8287            .preconditions
8288            .iter()
8289            .map(|c| c.count_conditions())
8290            .sum()
8291    }
8292
8293    /// Applies lex superior (higher authority prevails).
8294    ///
8295    /// Uses jurisdiction hierarchy: federal > state > local
8296    fn check_hierarchy(first: &Statute, second: &Statute) -> Option<ConflictResolution> {
8297        let first_level = Self::jurisdiction_level(&first.jurisdiction);
8298        let second_level = Self::jurisdiction_level(&second.jurisdiction);
8299
8300        if first_level > second_level {
8301            Some(ConflictResolution::FirstPrevails(ConflictReason::Hierarchy))
8302        } else if second_level > first_level {
8303            Some(ConflictResolution::SecondPrevails(
8304                ConflictReason::Hierarchy,
8305            ))
8306        } else {
8307            None // Same level
8308        }
8309    }
8310
8311    /// Determines jurisdiction hierarchy level.
8312    ///
8313    /// Higher number = higher authority
8314    fn jurisdiction_level(jurisdiction: &Option<String>) -> u32 {
8315        jurisdiction.as_ref().map_or(0, |j| {
8316            if j.to_lowercase().contains("federal") || j.to_lowercase().contains("national") {
8317                3
8318            } else if j.to_lowercase().contains("state") || j.to_lowercase().contains("provincial")
8319            {
8320                2
8321            } else if j.to_lowercase().contains("local") || j.to_lowercase().contains("municipal") {
8322                1
8323            } else {
8324                // Try to infer from format (e.g., "US" = federal, "US-NY" = state)
8325                if j.len() <= 3 && j.chars().all(|c| c.is_ascii_uppercase()) {
8326                    3 // Likely national/federal
8327                } else if j.contains('-') {
8328                    2 // Likely state/provincial
8329                } else {
8330                    0 // Unknown
8331                }
8332            }
8333        })
8334    }
8335
8336    /// Checks if a statute is still in effect on a given date.
8337    pub fn is_in_effect(statute: &Statute, date: NaiveDate) -> bool {
8338        statute.temporal_validity.is_active(date)
8339    }
8340
8341    /// Finds which statutes from a set apply to a given date and resolves conflicts.
8342    ///
8343    /// Returns statutes in order of precedence (highest priority first).
8344    pub fn resolve_conflicts_at_date(statutes: &[Statute], date: NaiveDate) -> Vec<&Statute> {
8345        // Filter to only in-effect statutes
8346        let mut active: Vec<&Statute> = statutes
8347            .iter()
8348            .filter(|s| Self::is_in_effect(s, date))
8349            .collect();
8350
8351        // Sort by precedence (most specific and recent first)
8352        active.sort_by(|a, b| {
8353            // First by effective date (newer first)
8354            let date_cmp = b
8355                .temporal_validity
8356                .effective_date
8357                .cmp(&a.temporal_validity.effective_date);
8358            if date_cmp != std::cmp::Ordering::Equal {
8359                return date_cmp;
8360            }
8361
8362            // Then by specificity (more specific first)
8363            let spec_cmp = Self::calculate_specificity(b).cmp(&Self::calculate_specificity(a));
8364            if spec_cmp != std::cmp::Ordering::Equal {
8365                return spec_cmp;
8366            }
8367
8368            // Then by hierarchy
8369            Self::jurisdiction_level(&b.jurisdiction)
8370                .cmp(&Self::jurisdiction_level(&a.jurisdiction))
8371        });
8372
8373        active
8374    }
8375
8376    /// Detects contradictions across a set of statutes.
8377    ///
8378    /// A contradiction occurs when:
8379    /// - Two statutes have conflicting effects (Grant vs Revoke) for the same thing
8380    /// - Two statutes have mutually exclusive preconditions but same effects
8381    /// - Statutes create logical inconsistencies in the legal system
8382    ///
8383    /// # Examples
8384    ///
8385    /// ```
8386    /// use legalis_core::{Statute, Effect, EffectType, StatuteConflictAnalyzer, Condition, ComparisonOp};
8387    ///
8388    /// let grant = Statute::new("grant-1", "Grant Right", Effect::new(EffectType::Grant, "Voting"))
8389    ///     .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18));
8390    ///
8391    /// let revoke = Statute::new("revoke-1", "Revoke Right", Effect::new(EffectType::Revoke, "Voting"))
8392    ///     .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18));
8393    ///
8394    /// let statutes = vec![grant, revoke];
8395    /// let contradictions = StatuteConflictAnalyzer::detect_contradictions(&statutes);
8396    ///
8397    /// assert!(!contradictions.is_empty());
8398    /// ```
8399    #[must_use]
8400    pub fn detect_contradictions(statutes: &[Statute]) -> Vec<Contradiction> {
8401        let mut contradictions = Vec::new();
8402
8403        // Check all pairs of statutes
8404        for (i, statute_a) in statutes.iter().enumerate() {
8405            for statute_b in statutes.iter().skip(i + 1) {
8406                // Check for effect contradictions
8407                if Self::effects_contradict(&statute_a.effect, &statute_b.effect) {
8408                    // Check if conditions overlap (could both apply)
8409                    if Self::conditions_may_overlap(
8410                        &statute_a.preconditions,
8411                        &statute_b.preconditions,
8412                    ) {
8413                        contradictions.push(Contradiction {
8414                            statute_a_id: statute_a.id.clone(),
8415                            statute_b_id: statute_b.id.clone(),
8416                            contradiction_type: ContradictionType::ConflictingEffects,
8417                            description: format!(
8418                                "Statute '{}' grants while '{}' revokes the same right",
8419                                statute_a.id, statute_b.id
8420                            ),
8421                            severity: ErrorSeverity::Critical,
8422                        });
8423                    }
8424                }
8425
8426                // Check for identical preconditions with conflicting effects
8427                if statute_a.preconditions == statute_b.preconditions
8428                    && statute_a.effect.effect_type != statute_b.effect.effect_type
8429                {
8430                    contradictions.push(Contradiction {
8431                        statute_a_id: statute_a.id.clone(),
8432                        statute_b_id: statute_b.id.clone(),
8433                        contradiction_type: ContradictionType::IdenticalConditionsConflictingEffects,
8434                        description: format!(
8435                            "Statutes '{}' and '{}' have identical conditions but conflicting effects",
8436                            statute_a.id, statute_b.id
8437                        ),
8438                        severity: ErrorSeverity::Critical,
8439                    });
8440                }
8441            }
8442        }
8443
8444        contradictions
8445    }
8446
8447    /// Checks if two effects contradict each other.
8448    fn effects_contradict(effect_a: &Effect, effect_b: &Effect) -> bool {
8449        // Grant vs Revoke is a contradiction
8450        matches!(
8451            (&effect_a.effect_type, &effect_b.effect_type),
8452            (EffectType::Grant, EffectType::Revoke) | (EffectType::Revoke, EffectType::Grant)
8453        ) && effect_a.description == effect_b.description
8454    }
8455
8456    /// Checks if two sets of conditions may overlap (both could be true).
8457    /// This is a simplified heuristic - full overlap detection requires SAT solving.
8458    #[allow(dead_code)]
8459    fn conditions_may_overlap(conds_a: &[Condition], conds_b: &[Condition]) -> bool {
8460        // Simplified: if either is empty or they're identical, they overlap
8461        conds_a.is_empty() || conds_b.is_empty() || conds_a == conds_b
8462    }
8463}
8464
8465/// Represents a logical contradiction between statutes.
8466#[derive(Debug, Clone, PartialEq)]
8467#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8468#[cfg_attr(feature = "schema", derive(JsonSchema))]
8469pub struct Contradiction {
8470    /// ID of the first statute involved
8471    pub statute_a_id: String,
8472    /// ID of the second statute involved
8473    pub statute_b_id: String,
8474    /// Type of contradiction
8475    pub contradiction_type: ContradictionType,
8476    /// Human-readable description
8477    pub description: String,
8478    /// Severity of the contradiction
8479    pub severity: ErrorSeverity,
8480}
8481
8482/// Types of contradictions that can occur between statutes.
8483#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8484#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8485#[cfg_attr(feature = "schema", derive(JsonSchema))]
8486pub enum ContradictionType {
8487    /// Statutes have conflicting effects (grant vs revoke)
8488    ConflictingEffects,
8489    /// Identical conditions but conflicting effects
8490    IdenticalConditionsConflictingEffects,
8491    /// Circular dependency between statutes
8492    CircularDependency,
8493    /// Logical inconsistency in rule set
8494    LogicalInconsistency,
8495}
8496
8497impl fmt::Display for Contradiction {
8498    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8499        write!(
8500            f,
8501            "[{}] {} <-> {}: {}",
8502            self.severity, self.statute_a_id, self.statute_b_id, self.description
8503        )
8504    }
8505}
8506
8507impl fmt::Display for ContradictionType {
8508    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8509        match self {
8510            Self::ConflictingEffects => write!(f, "Conflicting Effects"),
8511            Self::IdenticalConditionsConflictingEffects => {
8512                write!(f, "Identical Conditions, Conflicting Effects")
8513            }
8514            Self::CircularDependency => write!(f, "Circular Dependency"),
8515            Self::LogicalInconsistency => write!(f, "Logical Inconsistency"),
8516        }
8517    }
8518}
8519
8520// ============================================================================
8521// Fluent Builders for Enhanced Developer Experience
8522// ============================================================================
8523
8524/// Builder for constructing `Condition` objects with a fluent API.
8525///
8526/// Provides a convenient way to construct complex conditions with chaining.
8527///
8528/// # Examples
8529///
8530/// ```
8531/// use legalis_core::{ConditionBuilder, ComparisonOp};
8532///
8533/// let condition = ConditionBuilder::new()
8534///     .age(ComparisonOp::GreaterOrEqual, 18)
8535///     .and()
8536///     .income(ComparisonOp::LessThan, 50000)
8537///     .build();
8538///
8539/// assert!(!condition.to_string().is_empty());
8540/// ```
8541#[derive(Debug, Clone)]
8542pub struct ConditionBuilder {
8543    conditions: Vec<Condition>,
8544    operation: ConditionOperation,
8545}
8546
8547#[derive(Debug, Clone)]
8548enum ConditionOperation {
8549    None,
8550    And,
8551    Or,
8552}
8553
8554impl ConditionBuilder {
8555    /// Creates a new condition builder.
8556    #[must_use]
8557    pub fn new() -> Self {
8558        Self {
8559            conditions: Vec::new(),
8560            operation: ConditionOperation::None,
8561        }
8562    }
8563
8564    /// Adds an age condition.
8565    ///
8566    /// # Examples
8567    ///
8568    /// ```
8569    /// use legalis_core::{ConditionBuilder, ComparisonOp};
8570    ///
8571    /// let cond = ConditionBuilder::new()
8572    ///     .age(ComparisonOp::GreaterOrEqual, 21)
8573    ///     .build();
8574    /// ```
8575    #[must_use]
8576    pub fn age(mut self, operator: ComparisonOp, value: u32) -> Self {
8577        self.conditions.push(Condition::Age { operator, value });
8578        self
8579    }
8580
8581    /// Adds an income condition.
8582    #[must_use]
8583    pub fn income(mut self, operator: ComparisonOp, value: u64) -> Self {
8584        self.conditions.push(Condition::Income { operator, value });
8585        self
8586    }
8587
8588    /// Adds a has-attribute condition.
8589    #[must_use]
8590    pub fn has_attribute(mut self, attr: impl Into<String>) -> Self {
8591        self.conditions
8592            .push(Condition::HasAttribute { key: attr.into() });
8593        self
8594    }
8595
8596    /// Adds an attribute-equals condition.
8597    #[must_use]
8598    pub fn attribute_equals(mut self, attr: impl Into<String>, value: impl Into<String>) -> Self {
8599        self.conditions.push(Condition::AttributeEquals {
8600            key: attr.into(),
8601            value: value.into(),
8602        });
8603        self
8604    }
8605
8606    /// Adds a custom condition.
8607    #[must_use]
8608    pub fn custom(mut self, description: impl Into<String>) -> Self {
8609        self.conditions.push(Condition::Custom {
8610            description: description.into(),
8611        });
8612        self
8613    }
8614
8615    /// Combines the next condition with AND logic.
8616    ///
8617    /// # Examples
8618    ///
8619    /// ```
8620    /// use legalis_core::{ConditionBuilder, ComparisonOp};
8621    ///
8622    /// let cond = ConditionBuilder::new()
8623    ///     .age(ComparisonOp::GreaterOrEqual, 18)
8624    ///     .and()
8625    ///     .income(ComparisonOp::LessThan, 50000)
8626    ///     .build();
8627    /// ```
8628    #[must_use]
8629    pub fn and(mut self) -> Self {
8630        self.operation = ConditionOperation::And;
8631        self
8632    }
8633
8634    /// Combines the next condition with OR logic.
8635    #[must_use]
8636    pub fn or(mut self) -> Self {
8637        self.operation = ConditionOperation::Or;
8638        self
8639    }
8640
8641    /// Builds the final condition.
8642    ///
8643    /// If multiple conditions were added, they are combined according to the
8644    /// specified operations (AND/OR).
8645    #[must_use]
8646    pub fn build(self) -> Condition {
8647        if self.conditions.is_empty() {
8648            Condition::Custom {
8649                description: "true".to_string(),
8650            }
8651        } else if self.conditions.len() == 1 {
8652            self.conditions.into_iter().next().unwrap()
8653        } else {
8654            // Combine all conditions with the operation
8655            let mut result = self.conditions[0].clone();
8656            for cond in self.conditions.into_iter().skip(1) {
8657                result = match self.operation {
8658                    ConditionOperation::And => Condition::And(Box::new(result), Box::new(cond)),
8659                    ConditionOperation::Or => Condition::Or(Box::new(result), Box::new(cond)),
8660                    ConditionOperation::None => Condition::And(Box::new(result), Box::new(cond)),
8661                };
8662            }
8663            result
8664        }
8665    }
8666}
8667
8668impl Default for ConditionBuilder {
8669    fn default() -> Self {
8670        Self::new()
8671    }
8672}
8673
8674/// Builder for constructing `Effect` objects with a fluent API.
8675///
8676/// Provides a convenient way to construct effects with parameters.
8677///
8678/// # Examples
8679///
8680/// ```
8681/// use legalis_core::{EffectBuilder, EffectType};
8682///
8683/// let effect = EffectBuilder::new()
8684///     .effect_type(EffectType::Grant)
8685///     .description("Tax credit")
8686///     .parameter("amount", "1000")
8687///     .parameter("currency", "USD")
8688///     .build();
8689///
8690/// assert_eq!(effect.effect_type, EffectType::Grant);
8691/// assert_eq!(effect.parameters.get("amount"), Some(&"1000".to_string()));
8692/// ```
8693#[derive(Debug, Clone)]
8694pub struct EffectBuilder {
8695    effect_type: Option<EffectType>,
8696    description: Option<String>,
8697    parameters: std::collections::HashMap<String, String>,
8698}
8699
8700impl EffectBuilder {
8701    /// Creates a new effect builder.
8702    #[must_use]
8703    pub fn new() -> Self {
8704        Self {
8705            effect_type: None,
8706            description: None,
8707            parameters: std::collections::HashMap::new(),
8708        }
8709    }
8710
8711    /// Creates a builder initialized with an effect type and description.
8712    ///
8713    /// # Examples
8714    ///
8715    /// ```
8716    /// use legalis_core::{EffectBuilder, EffectType};
8717    ///
8718    /// let effect = EffectBuilder::grant("Tax credit")
8719    ///     .parameter("amount", "1000")
8720    ///     .build();
8721    ///
8722    /// assert_eq!(effect.effect_type, EffectType::Grant);
8723    /// ```
8724    #[must_use]
8725    pub fn grant(description: impl Into<String>) -> Self {
8726        Self {
8727            effect_type: Some(EffectType::Grant),
8728            description: Some(description.into()),
8729            parameters: std::collections::HashMap::new(),
8730        }
8731    }
8732
8733    /// Creates a builder for a revoke effect.
8734    #[must_use]
8735    pub fn revoke(description: impl Into<String>) -> Self {
8736        Self {
8737            effect_type: Some(EffectType::Revoke),
8738            description: Some(description.into()),
8739            parameters: std::collections::HashMap::new(),
8740        }
8741    }
8742
8743    /// Creates a builder for an obligation effect.
8744    #[must_use]
8745    pub fn obligation(description: impl Into<String>) -> Self {
8746        Self {
8747            effect_type: Some(EffectType::Obligation),
8748            description: Some(description.into()),
8749            parameters: std::collections::HashMap::new(),
8750        }
8751    }
8752
8753    /// Sets the effect type.
8754    #[must_use]
8755    pub fn effect_type(mut self, effect_type: EffectType) -> Self {
8756        self.effect_type = Some(effect_type);
8757        self
8758    }
8759
8760    /// Sets the description.
8761    #[must_use]
8762    pub fn description(mut self, desc: impl Into<String>) -> Self {
8763        self.description = Some(desc.into());
8764        self
8765    }
8766
8767    /// Adds a parameter to the effect.
8768    ///
8769    /// # Examples
8770    ///
8771    /// ```
8772    /// use legalis_core::{EffectBuilder, EffectType};
8773    ///
8774    /// let effect = EffectBuilder::new()
8775    ///     .effect_type(EffectType::MonetaryTransfer)
8776    ///     .description("Tax payment")
8777    ///     .parameter("amount", "5000")
8778    ///     .parameter("currency", "USD")
8779    ///     .build();
8780    ///
8781    /// assert_eq!(effect.parameters.len(), 2);
8782    /// ```
8783    #[must_use]
8784    pub fn parameter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
8785        self.parameters.insert(key.into(), value.into());
8786        self
8787    }
8788
8789    /// Builds the final effect.
8790    ///
8791    /// # Panics
8792    ///
8793    /// Panics if effect_type or description is not set.
8794    #[must_use]
8795    pub fn build(self) -> Effect {
8796        Effect {
8797            effect_type: self.effect_type.expect("Effect type must be set"),
8798            description: self.description.expect("Description must be set"),
8799            parameters: self.parameters,
8800        }
8801    }
8802
8803    /// Builds the effect, returning an error if required fields are missing.
8804    pub fn try_build(self) -> Result<Effect, String> {
8805        let effect_type = self.effect_type.ok_or("Effect type not set")?;
8806        let description = self.description.ok_or("Description not set")?;
8807        Ok(Effect {
8808            effect_type,
8809            description,
8810            parameters: self.parameters,
8811        })
8812    }
8813}
8814
8815impl Default for EffectBuilder {
8816    fn default() -> Self {
8817        Self::new()
8818    }
8819}
8820
8821/// Simple builder for constructing `Statute` objects with template support
8822/// and progressive validation.
8823///
8824/// Unlike `TypedStatuteBuilder`, this builder is runtime-validated and provides
8825/// convenience methods like `from_template()` and progressive validation.
8826///
8827/// # Examples
8828///
8829/// ```
8830/// use legalis_core::{StatuteBuilder, Effect, EffectType, Condition, ComparisonOp};
8831///
8832/// let statute = StatuteBuilder::new()
8833///     .id("tax-law-1")
8834///     .title("Tax Credit Law")
8835///     .effect(Effect::new(EffectType::Grant, "Tax credit"))
8836///     .precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18))
8837///     .validate_progressive(true)
8838///     .build()
8839///     .expect("Failed to build statute");
8840///
8841/// assert_eq!(statute.id, "tax-law-1");
8842/// ```
8843#[derive(Debug, Clone)]
8844pub struct StatuteBuilder {
8845    id: Option<String>,
8846    title: Option<String>,
8847    effect: Option<Effect>,
8848    preconditions: Vec<Condition>,
8849    discretion_logic: Option<String>,
8850    temporal_validity: TemporalValidity,
8851    version: u32,
8852    jurisdiction: Option<String>,
8853    derives_from: Vec<String>,
8854    applies_to: Vec<String>,
8855    exceptions: Vec<StatuteException>,
8856    progressive_validation: bool,
8857    validation_errors: Vec<ValidationError>,
8858}
8859
8860impl StatuteBuilder {
8861    /// Creates a new statute builder.
8862    #[must_use]
8863    pub fn new() -> Self {
8864        Self {
8865            id: None,
8866            title: None,
8867            effect: None,
8868            preconditions: Vec::new(),
8869            discretion_logic: None,
8870            temporal_validity: TemporalValidity::default(),
8871            version: 1,
8872            jurisdiction: None,
8873            derives_from: Vec::new(),
8874            applies_to: Vec::new(),
8875            exceptions: Vec::new(),
8876            progressive_validation: false,
8877            validation_errors: Vec::new(),
8878        }
8879    }
8880
8881    /// Creates a builder from an existing statute template.
8882    ///
8883    /// This copies all fields from the template statute, allowing you to modify
8884    /// specific fields while keeping others the same.
8885    ///
8886    /// # Examples
8887    ///
8888    /// ```
8889    /// use legalis_core::{StatuteBuilder, Statute, Effect, EffectType};
8890    ///
8891    /// let template = Statute::new("template-1", "Template Law", Effect::grant("Benefit"))
8892    ///     .with_version(1)
8893    ///     .with_jurisdiction("US");
8894    ///
8895    /// let derived = StatuteBuilder::from_template(&template)
8896    ///     .id("derived-1")
8897    ///     .title("Derived Law")
8898    ///     .build()
8899    ///     .expect("Failed to build");
8900    ///
8901    /// assert_eq!(derived.jurisdiction, Some("US".to_string()));
8902    /// assert_eq!(derived.version, 1);
8903    /// ```
8904    #[must_use]
8905    pub fn from_template(template: &Statute) -> Self {
8906        Self {
8907            id: Some(template.id.clone()),
8908            title: Some(template.title.clone()),
8909            effect: Some(template.effect.clone()),
8910            preconditions: template.preconditions.clone(),
8911            discretion_logic: template.discretion_logic.clone(),
8912            temporal_validity: template.temporal_validity.clone(),
8913            version: template.version,
8914            jurisdiction: template.jurisdiction.clone(),
8915            derives_from: template.derives_from.clone(),
8916            applies_to: template.applies_to.clone(),
8917            exceptions: template.exceptions.clone(),
8918            progressive_validation: false,
8919            validation_errors: Vec::new(),
8920        }
8921    }
8922
8923    /// Enables or disables progressive validation.
8924    ///
8925    /// When enabled, the builder validates each field as it's set and accumulates
8926    /// validation errors.
8927    ///
8928    /// # Examples
8929    ///
8930    /// ```
8931    /// use legalis_core::{StatuteBuilder, Effect, EffectType};
8932    ///
8933    /// let result = StatuteBuilder::new()
8934    ///     .validate_progressive(true)
8935    ///     .id("") // Invalid ID - empty
8936    ///     .title("Test")
8937    ///     .effect(Effect::grant("Benefit"))
8938    ///     .build();
8939    ///
8940    /// assert!(result.is_err());
8941    /// ```
8942    #[must_use]
8943    pub fn validate_progressive(mut self, enabled: bool) -> Self {
8944        self.progressive_validation = enabled;
8945        self
8946    }
8947
8948    /// Sets the statute ID.
8949    #[must_use]
8950    pub fn id(mut self, id: impl Into<String>) -> Self {
8951        let id = id.into();
8952        if self.progressive_validation {
8953            if id.is_empty() {
8954                self.validation_errors.push(ValidationError::EmptyId);
8955            } else if !self.is_valid_id(&id) {
8956                self.validation_errors
8957                    .push(ValidationError::InvalidId(id.clone()));
8958            }
8959        }
8960        self.id = Some(id);
8961        self
8962    }
8963
8964    /// Sets the statute title.
8965    #[must_use]
8966    pub fn title(mut self, title: impl Into<String>) -> Self {
8967        let title = title.into();
8968        if self.progressive_validation && title.is_empty() {
8969            self.validation_errors.push(ValidationError::EmptyTitle);
8970        }
8971        self.title = Some(title);
8972        self
8973    }
8974
8975    /// Sets the effect.
8976    #[must_use]
8977    pub fn effect(mut self, effect: Effect) -> Self {
8978        if self.progressive_validation && effect.description.is_empty() {
8979            self.validation_errors
8980                .push(ValidationError::EmptyEffectDescription);
8981        }
8982        self.effect = Some(effect);
8983        self
8984    }
8985
8986    /// Adds a precondition.
8987    #[must_use]
8988    pub fn precondition(mut self, condition: Condition) -> Self {
8989        self.preconditions.push(condition);
8990        self
8991    }
8992
8993    /// Sets the discretion logic.
8994    #[must_use]
8995    pub fn discretion(mut self, logic: impl Into<String>) -> Self {
8996        self.discretion_logic = Some(logic.into());
8997        self
8998    }
8999
9000    /// Sets temporal validity.
9001    #[must_use]
9002    pub fn temporal_validity(mut self, validity: TemporalValidity) -> Self {
9003        self.temporal_validity = validity;
9004        self
9005    }
9006
9007    /// Sets the version.
9008    #[must_use]
9009    pub fn version(mut self, version: u32) -> Self {
9010        if self.progressive_validation && version == 0 {
9011            self.validation_errors.push(ValidationError::InvalidVersion);
9012        }
9013        self.version = version;
9014        self
9015    }
9016
9017    /// Sets the jurisdiction.
9018    #[must_use]
9019    pub fn jurisdiction(mut self, jurisdiction: impl Into<String>) -> Self {
9020        self.jurisdiction = Some(jurisdiction.into());
9021        self
9022    }
9023
9024    /// Adds a derivation source.
9025    #[must_use]
9026    pub fn derives_from(mut self, source: impl Into<String>) -> Self {
9027        self.derives_from.push(source.into());
9028        self
9029    }
9030
9031    /// Adds an applicable entity type.
9032    #[must_use]
9033    pub fn applies_to(mut self, entity_type: impl Into<String>) -> Self {
9034        self.applies_to.push(entity_type.into());
9035        self
9036    }
9037
9038    /// Adds an exception.
9039    #[must_use]
9040    pub fn exception(mut self, exception: StatuteException) -> Self {
9041        self.exceptions.push(exception);
9042        self
9043    }
9044
9045    /// Returns accumulated validation errors (when progressive validation is enabled).
9046    #[must_use]
9047    pub fn validation_errors(&self) -> &[ValidationError] {
9048        &self.validation_errors
9049    }
9050
9051    /// Checks if an ID is valid.
9052    fn is_valid_id(&self, id: &str) -> bool {
9053        !id.is_empty()
9054            && id
9055                .chars()
9056                .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
9057            && id.chars().next().is_some_and(|c| c.is_alphabetic())
9058    }
9059
9060    /// Builds the statute, returning an error if required fields are missing or validation fails.
9061    pub fn build(self) -> Result<Statute, Vec<ValidationError>> {
9062        let mut errors = self.validation_errors;
9063
9064        // Check required fields
9065        if self.id.is_none() {
9066            errors.push(ValidationError::EmptyId);
9067        }
9068        if self.title.is_none() {
9069            errors.push(ValidationError::EmptyTitle);
9070        }
9071        if self.effect.is_none() {
9072            errors.push(ValidationError::EmptyEffectDescription);
9073        }
9074
9075        if !errors.is_empty() {
9076            return Err(errors);
9077        }
9078
9079        let statute = Statute {
9080            id: self.id.unwrap(),
9081            title: self.title.unwrap(),
9082            effect: self.effect.unwrap(),
9083            preconditions: self.preconditions,
9084            discretion_logic: self.discretion_logic,
9085            temporal_validity: self.temporal_validity,
9086            version: self.version,
9087            jurisdiction: self.jurisdiction,
9088            derives_from: self.derives_from,
9089            applies_to: self.applies_to,
9090            exceptions: self.exceptions,
9091        };
9092
9093        // Final validation
9094        let validation_errors = statute.validate();
9095        if !validation_errors.is_empty() {
9096            Err(validation_errors)
9097        } else {
9098            Ok(statute)
9099        }
9100    }
9101}
9102
9103impl Default for StatuteBuilder {
9104    fn default() -> Self {
9105        Self::new()
9106    }
9107}
9108
9109// ============================================================================
9110// Typestate Builder Pattern for Compile-Time Verification
9111// ============================================================================
9112
9113/// Marker types for typestate builder pattern.
9114///
9115/// These types are used to track the builder state at compile time,
9116/// ensuring that required fields are set before building a `Statute`.
9117pub mod builder_states {
9118    /// Marker indicating ID is not set.
9119    #[derive(Debug, Clone, Copy)]
9120    pub struct NoId;
9121    /// Marker indicating ID is set.
9122    #[derive(Debug, Clone, Copy)]
9123    pub struct HasId;
9124    /// Marker indicating title is not set.
9125    #[derive(Debug, Clone, Copy)]
9126    pub struct NoTitle;
9127    /// Marker indicating title is set.
9128    #[derive(Debug, Clone, Copy)]
9129    pub struct HasTitle;
9130    /// Marker indicating effect is not set.
9131    #[derive(Debug, Clone, Copy)]
9132    pub struct NoEffect;
9133    /// Marker indicating effect is set.
9134    #[derive(Debug, Clone, Copy)]
9135    pub struct HasEffect;
9136}
9137
9138use builder_states::*;
9139
9140/// Type-safe builder for `Statute` using the typestate pattern.
9141///
9142/// This builder ensures at compile time that all required fields (id, title, effect)
9143/// are set before building a statute. The type parameters track which fields have been set.
9144///
9145/// # Type Parameters
9146///
9147/// - `I`: ID state (`NoId` or `HasId`)
9148/// - `T`: Title state (`NoTitle` or `HasTitle`)
9149/// - `E`: Effect state (`NoEffect` or `HasEffect`)
9150///
9151/// # Examples
9152///
9153/// ```
9154/// use legalis_core::{TypedStatuteBuilder, Effect, EffectType, Condition, ComparisonOp};
9155///
9156/// // This compiles - all required fields are set
9157/// let statute = TypedStatuteBuilder::new()
9158///     .id("tax-law-2025")
9159///     .title("Income Tax Credit")
9160///     .effect(Effect::new(EffectType::Grant, "Tax credit of $1000"))
9161///     .with_precondition(Condition::Income {
9162///         operator: ComparisonOp::LessThan,
9163///         value: 50000,
9164///     })
9165///     .build();
9166///
9167/// assert_eq!(statute.id, "tax-law-2025");
9168/// ```
9169///
9170/// ```compile_fail
9171/// use legalis_core::TypedStatuteBuilder;
9172///
9173/// // This won't compile - missing title and effect
9174/// let statute = TypedStatuteBuilder::new()
9175///     .id("tax-law-2025")
9176///     .build(); // ERROR: build() not available
9177/// ```
9178#[derive(Debug, Clone)]
9179pub struct TypedStatuteBuilder<I, T, E> {
9180    id: Option<String>,
9181    title: Option<String>,
9182    effect: Option<Effect>,
9183    preconditions: Vec<Condition>,
9184    discretion_logic: Option<String>,
9185    temporal_validity: TemporalValidity,
9186    version: u32,
9187    jurisdiction: Option<String>,
9188    _phantom: std::marker::PhantomData<(I, T, E)>,
9189}
9190
9191impl TypedStatuteBuilder<NoId, NoTitle, NoEffect> {
9192    /// Creates a new builder with no fields set.
9193    #[must_use]
9194    pub fn new() -> Self {
9195        Self {
9196            id: None,
9197            title: None,
9198            effect: None,
9199            preconditions: Vec::new(),
9200            discretion_logic: None,
9201            temporal_validity: TemporalValidity::default(),
9202            version: 1,
9203            jurisdiction: None,
9204            _phantom: std::marker::PhantomData,
9205        }
9206    }
9207}
9208
9209impl<T, E> TypedStatuteBuilder<NoId, T, E> {
9210    /// Sets the statute ID (required field).
9211    ///
9212    /// Transitions from `NoId` to `HasId` state.
9213    #[must_use]
9214    pub fn id(self, id: impl Into<String>) -> TypedStatuteBuilder<HasId, T, E> {
9215        TypedStatuteBuilder {
9216            id: Some(id.into()),
9217            title: self.title,
9218            effect: self.effect,
9219            preconditions: self.preconditions,
9220            discretion_logic: self.discretion_logic,
9221            temporal_validity: self.temporal_validity,
9222            version: self.version,
9223            jurisdiction: self.jurisdiction,
9224            _phantom: std::marker::PhantomData,
9225        }
9226    }
9227}
9228
9229impl<I, E> TypedStatuteBuilder<I, NoTitle, E> {
9230    /// Sets the statute title (required field).
9231    ///
9232    /// Transitions from `NoTitle` to `HasTitle` state.
9233    #[must_use]
9234    pub fn title(self, title: impl Into<String>) -> TypedStatuteBuilder<I, HasTitle, E> {
9235        TypedStatuteBuilder {
9236            id: self.id,
9237            title: Some(title.into()),
9238            effect: self.effect,
9239            preconditions: self.preconditions,
9240            discretion_logic: self.discretion_logic,
9241            temporal_validity: self.temporal_validity,
9242            version: self.version,
9243            jurisdiction: self.jurisdiction,
9244            _phantom: std::marker::PhantomData,
9245        }
9246    }
9247}
9248
9249impl<I, T> TypedStatuteBuilder<I, T, NoEffect> {
9250    /// Sets the statute effect (required field).
9251    ///
9252    /// Transitions from `NoEffect` to `HasEffect` state.
9253    #[must_use]
9254    pub fn effect(self, effect: Effect) -> TypedStatuteBuilder<I, T, HasEffect> {
9255        TypedStatuteBuilder {
9256            id: self.id,
9257            title: self.title,
9258            effect: Some(effect),
9259            preconditions: self.preconditions,
9260            discretion_logic: self.discretion_logic,
9261            temporal_validity: self.temporal_validity,
9262            version: self.version,
9263            jurisdiction: self.jurisdiction,
9264            _phantom: std::marker::PhantomData,
9265        }
9266    }
9267}
9268
9269// Methods available in all states
9270impl<I, T, E> TypedStatuteBuilder<I, T, E> {
9271    /// Adds a precondition (optional field).
9272    #[must_use]
9273    pub fn with_precondition(mut self, condition: Condition) -> Self {
9274        self.preconditions.push(condition);
9275        self
9276    }
9277
9278    /// Sets the discretion logic (optional field).
9279    #[must_use]
9280    pub fn with_discretion(mut self, logic: impl Into<String>) -> Self {
9281        self.discretion_logic = Some(logic.into());
9282        self
9283    }
9284
9285    /// Sets temporal validity (optional field).
9286    #[must_use]
9287    pub fn with_temporal_validity(mut self, validity: TemporalValidity) -> Self {
9288        self.temporal_validity = validity;
9289        self
9290    }
9291
9292    /// Sets the version (optional field, defaults to 1).
9293    #[must_use]
9294    pub fn with_version(mut self, version: u32) -> Self {
9295        self.version = version;
9296        self
9297    }
9298
9299    /// Sets the jurisdiction (optional field).
9300    #[must_use]
9301    pub fn with_jurisdiction(mut self, jurisdiction: impl Into<String>) -> Self {
9302        self.jurisdiction = Some(jurisdiction.into());
9303        self
9304    }
9305}
9306
9307// build() only available when all required fields are set
9308impl TypedStatuteBuilder<HasId, HasTitle, HasEffect> {
9309    /// Builds the `Statute` (only available when all required fields are set).
9310    ///
9311    /// This method is only callable when the builder has transitioned through
9312    /// all required states (HasId, HasTitle, HasEffect).
9313    #[must_use]
9314    pub fn build(self) -> Statute {
9315        Statute {
9316            id: self.id.expect("ID must be set"),
9317            title: self.title.expect("Title must be set"),
9318            effect: self.effect.expect("Effect must be set"),
9319            preconditions: self.preconditions,
9320            discretion_logic: self.discretion_logic,
9321            temporal_validity: self.temporal_validity,
9322            version: self.version,
9323            jurisdiction: self.jurisdiction,
9324            derives_from: Vec::new(),
9325            applies_to: Vec::new(),
9326            exceptions: Vec::new(),
9327        }
9328    }
9329}
9330
9331impl Default for TypedStatuteBuilder<NoId, NoTitle, NoEffect> {
9332    fn default() -> Self {
9333        Self::new()
9334    }
9335}
9336
9337// ============================================================================
9338// Phantom Types for Jurisdiction-Specific Statutes
9339// ============================================================================
9340
9341/// Marker trait for jurisdictions.
9342///
9343/// This trait enables compile-time verification that statutes are used
9344/// in the correct jurisdiction context.
9345pub trait Jurisdiction: std::fmt::Debug + Clone {
9346    /// Returns the jurisdiction code (e.g., "US", "UK", "US-CA").
9347    fn code() -> &'static str;
9348}
9349
9350/// United States jurisdiction marker.
9351#[derive(Debug, Clone, Copy)]
9352pub struct US;
9353
9354impl Jurisdiction for US {
9355    fn code() -> &'static str {
9356        "US"
9357    }
9358}
9359
9360/// United Kingdom jurisdiction marker.
9361#[derive(Debug, Clone, Copy)]
9362pub struct UK;
9363
9364impl Jurisdiction for UK {
9365    fn code() -> &'static str {
9366        "UK"
9367    }
9368}
9369
9370/// European Union jurisdiction marker.
9371#[derive(Debug, Clone, Copy)]
9372pub struct EU;
9373
9374impl Jurisdiction for EU {
9375    fn code() -> &'static str {
9376        "EU"
9377    }
9378}
9379
9380/// California (US-CA) jurisdiction marker.
9381#[derive(Debug, Clone, Copy)]
9382pub struct California;
9383
9384impl Jurisdiction for California {
9385    fn code() -> &'static str {
9386        "US-CA"
9387    }
9388}
9389
9390/// New York (US-NY) jurisdiction marker.
9391#[derive(Debug, Clone, Copy)]
9392pub struct NewYork;
9393
9394impl Jurisdiction for NewYork {
9395    fn code() -> &'static str {
9396        "US-NY"
9397    }
9398}
9399
9400/// Generic marker for any jurisdiction.
9401#[derive(Debug, Clone, Copy)]
9402pub struct AnyJurisdiction;
9403
9404impl Jurisdiction for AnyJurisdiction {
9405    fn code() -> &'static str {
9406        ""
9407    }
9408}
9409
9410/// Jurisdiction-specific statute wrapper using phantom types.
9411///
9412/// This type enforces at compile time that statutes are used in the correct
9413/// jurisdiction context. The type parameter `J` ensures that you can't mix
9414/// statutes from different jurisdictions without explicit conversion.
9415///
9416/// # Type Parameters
9417///
9418/// - `J`: Jurisdiction marker type implementing the `Jurisdiction` trait
9419///
9420/// # Examples
9421///
9422/// ```
9423/// use legalis_core::{JurisdictionStatute, US, UK, Statute, Effect, EffectType};
9424///
9425/// // Create a US-specific statute
9426/// let us_statute = Statute::new("tax-law", "Tax Law", Effect::new(EffectType::Grant, "Tax credit"));
9427/// let us_law = JurisdictionStatute::<US>::new(us_statute);
9428///
9429/// // Create a UK-specific statute
9430/// let uk_statute = Statute::new("uk-law", "UK Law", Effect::new(EffectType::Grant, "Benefit"));
9431/// let uk_law = JurisdictionStatute::<UK>::new(uk_statute);
9432///
9433/// // These types are different and can't be mixed
9434/// assert_eq!(us_law.jurisdiction_code(), "US");
9435/// assert_eq!(uk_law.jurisdiction_code(), "UK");
9436/// ```
9437#[derive(Debug, Clone)]
9438#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9439pub struct JurisdictionStatute<J: Jurisdiction> {
9440    statute: Statute,
9441    _phantom: std::marker::PhantomData<J>,
9442}
9443
9444impl<J: Jurisdiction> JurisdictionStatute<J> {
9445    /// Creates a new jurisdiction-specific statute.
9446    ///
9447    /// # Examples
9448    ///
9449    /// ```
9450    /// use legalis_core::{JurisdictionStatute, US, Statute, Effect, EffectType};
9451    ///
9452    /// let statute = Statute::new("law-1", "Law", Effect::new(EffectType::Grant, "Benefit"));
9453    /// let us_law = JurisdictionStatute::<US>::new(statute);
9454    /// ```
9455    #[must_use]
9456    pub fn new(mut statute: Statute) -> Self {
9457        // Automatically set the jurisdiction if not already set
9458        if statute.jurisdiction.is_none() {
9459            statute.jurisdiction = Some(J::code().to_string());
9460        }
9461        Self {
9462            statute,
9463            _phantom: std::marker::PhantomData,
9464        }
9465    }
9466
9467    /// Returns the jurisdiction code for this statute.
9468    #[must_use]
9469    pub fn jurisdiction_code(&self) -> &'static str {
9470        J::code()
9471    }
9472
9473    /// Returns a reference to the underlying statute.
9474    #[must_use]
9475    pub fn statute(&self) -> &Statute {
9476        &self.statute
9477    }
9478
9479    /// Consumes self and returns the underlying statute.
9480    #[must_use]
9481    pub fn into_statute(self) -> Statute {
9482        self.statute
9483    }
9484
9485    /// Converts this statute to a different jurisdiction.
9486    ///
9487    /// This is an explicit operation that requires the caller to acknowledge
9488    /// they are changing jurisdictions.
9489    ///
9490    /// # Examples
9491    ///
9492    /// ```
9493    /// use legalis_core::{JurisdictionStatute, US, UK, Statute, Effect, EffectType};
9494    ///
9495    /// let statute = Statute::new("law", "Law", Effect::new(EffectType::Grant, "Benefit"));
9496    /// let us_law = JurisdictionStatute::<US>::new(statute);
9497    /// let uk_law = us_law.convert_to::<UK>();
9498    /// assert_eq!(uk_law.jurisdiction_code(), "UK");
9499    /// ```
9500    #[must_use]
9501    pub fn convert_to<K: Jurisdiction>(mut self) -> JurisdictionStatute<K> {
9502        self.statute.jurisdiction = Some(K::code().to_string());
9503        JurisdictionStatute {
9504            statute: self.statute,
9505            _phantom: std::marker::PhantomData,
9506        }
9507    }
9508}
9509
9510/// Collection of jurisdiction-specific statutes.
9511///
9512/// This type ensures all statutes in the collection belong to the same jurisdiction.
9513///
9514/// # Examples
9515///
9516/// ```
9517/// use legalis_core::{JurisdictionStatuteRegistry, US, JurisdictionStatute, Statute, Effect, EffectType};
9518///
9519/// let mut registry = JurisdictionStatuteRegistry::<US>::new();
9520///
9521/// let statute1 = Statute::new("law-1", "Law 1", Effect::new(EffectType::Grant, "Benefit 1"));
9522/// let statute2 = Statute::new("law-2", "Law 2", Effect::new(EffectType::Grant, "Benefit 2"));
9523///
9524/// registry.add(JurisdictionStatute::new(statute1));
9525/// registry.add(JurisdictionStatute::new(statute2));
9526///
9527/// assert_eq!(registry.len(), 2);
9528/// assert_eq!(registry.jurisdiction_code(), "US");
9529/// ```
9530#[derive(Debug, Clone)]
9531#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9532pub struct JurisdictionStatuteRegistry<J: Jurisdiction> {
9533    statutes: Vec<JurisdictionStatute<J>>,
9534    _phantom: std::marker::PhantomData<J>,
9535}
9536
9537impl<J: Jurisdiction> JurisdictionStatuteRegistry<J> {
9538    /// Creates a new empty registry for a specific jurisdiction.
9539    #[must_use]
9540    pub fn new() -> Self {
9541        Self {
9542            statutes: Vec::new(),
9543            _phantom: std::marker::PhantomData,
9544        }
9545    }
9546
9547    /// Adds a statute to the registry.
9548    pub fn add(&mut self, statute: JurisdictionStatute<J>) {
9549        self.statutes.push(statute);
9550    }
9551
9552    /// Returns the number of statutes in the registry.
9553    #[must_use]
9554    pub fn len(&self) -> usize {
9555        self.statutes.len()
9556    }
9557
9558    /// Returns true if the registry is empty.
9559    #[must_use]
9560    pub fn is_empty(&self) -> bool {
9561        self.statutes.is_empty()
9562    }
9563
9564    /// Returns the jurisdiction code for this registry.
9565    #[must_use]
9566    pub fn jurisdiction_code(&self) -> &'static str {
9567        J::code()
9568    }
9569
9570    /// Returns an iterator over the statutes.
9571    pub fn iter(&self) -> impl Iterator<Item = &JurisdictionStatute<J>> {
9572        self.statutes.iter()
9573    }
9574
9575    /// Finds a statute by ID.
9576    #[must_use]
9577    pub fn find(&self, id: &str) -> Option<&JurisdictionStatute<J>> {
9578        self.statutes.iter().find(|s| s.statute().id == id)
9579    }
9580}
9581
9582impl<J: Jurisdiction> Default for JurisdictionStatuteRegistry<J> {
9583    fn default() -> Self {
9584        Self::new()
9585    }
9586}
9587
9588/// Macro for defining custom jurisdiction types with automatic boilerplate.
9589///
9590/// # Examples
9591///
9592/// ```
9593/// use legalis_core::{define_jurisdiction, Jurisdiction};
9594///
9595/// define_jurisdiction! {
9596///     /// Texas jurisdiction
9597///     Texas => "US-TX"
9598/// }
9599///
9600/// // Now Texas can be used as a jurisdiction marker
9601/// let code = Texas::code();
9602/// assert_eq!(code, "US-TX");
9603/// ```
9604#[macro_export]
9605macro_rules! define_jurisdiction {
9606    (
9607        $(#[$meta:meta])*
9608        $name:ident => $code:expr
9609    ) => {
9610        $(#[$meta])*
9611        #[derive(Debug, Clone, Copy)]
9612        pub struct $name;
9613
9614        impl $crate::Jurisdiction for $name {
9615            fn code() -> &'static str {
9616                $code
9617            }
9618        }
9619    };
9620}
9621
9622/// Macro for defining custom condition types with automatic boilerplate.
9623///
9624/// This macro generates a custom condition wrapper type with:
9625/// - A struct to hold the condition data
9626/// - Constructor methods
9627/// - Display trait implementation
9628/// - Conversion to `Condition::Custom` variant
9629/// - Evaluation helper method
9630///
9631/// # Examples
9632///
9633/// ```
9634/// use legalis_core::{define_custom_condition, Condition, EvaluationContext, EvaluationError};
9635///
9636/// // Define a custom "Employment Status" condition
9637/// define_custom_condition! {
9638///     /// Checks if entity has specific employment status
9639///     EmploymentStatus {
9640///         status: String,
9641///         requires_full_time: bool,
9642///     }
9643/// }
9644///
9645/// // Use the custom condition
9646/// let cond = EmploymentStatus::new("engineer".to_string(), true);
9647/// let custom_cond: Condition = cond.into();
9648/// assert!(matches!(custom_cond, Condition::Custom { .. }));
9649/// ```
9650///
9651/// The macro generates:
9652///
9653/// ```text
9654/// pub struct EmploymentStatus {
9655///     pub status: String,
9656///     pub requires_full_time: bool,
9657/// }
9658///
9659/// impl EmploymentStatus {
9660///     pub fn new(status: String, requires_full_time: bool) -> Self { ... }
9661///     pub fn to_condition(&self) -> Condition { ... }
9662/// }
9663///
9664/// impl From<EmploymentStatus> for Condition { ... }
9665/// impl std::fmt::Display for EmploymentStatus { ... }
9666/// ```
9667#[macro_export]
9668macro_rules! define_custom_condition {
9669    (
9670        $(#[$meta:meta])*
9671        $name:ident {
9672            $($field:ident: $field_type:ty),* $(,)?
9673        }
9674    ) => {
9675        $(#[$meta])*
9676        #[derive(Debug, Clone, PartialEq)]
9677        pub struct $name {
9678            $(pub $field: $field_type,)*
9679        }
9680
9681        impl $name {
9682            /// Creates a new instance of this custom condition.
9683            #[must_use]
9684            pub fn new($($field: $field_type),*) -> Self {
9685                Self {
9686                    $($field,)*
9687                }
9688            }
9689
9690            /// Converts this custom condition to a `Condition::Custom`.
9691            #[must_use]
9692            pub fn to_condition(&self) -> $crate::Condition {
9693                $crate::Condition::Custom {
9694                    description: self.to_string(),
9695                }
9696            }
9697
9698            /// Evaluates this condition against a context.
9699            ///
9700            /// Override this method in your implementation to provide custom evaluation logic.
9701            #[allow(dead_code)]
9702            pub fn evaluate<C: $crate::EvaluationContext>(
9703                &self,
9704                _context: &C,
9705            ) -> Result<bool, $crate::EvaluationError> {
9706                // Default implementation always returns an error
9707                // Users should implement their own evaluation logic
9708                Err($crate::EvaluationError::Custom {
9709                    message: format!(
9710                        "Evaluation not implemented for custom condition type '{}'",
9711                        stringify!($name)
9712                    ),
9713                })
9714            }
9715        }
9716
9717        impl From<$name> for $crate::Condition {
9718            fn from(custom: $name) -> Self {
9719                custom.to_condition()
9720            }
9721        }
9722
9723        impl std::fmt::Display for $name {
9724            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9725                write!(f, "{}(", stringify!($name))?;
9726                let mut first = true;
9727                $(
9728                    if !first {
9729                        write!(f, ", ")?;
9730                    }
9731                    write!(f, "{}: {:?}", stringify!($field), self.$field)?;
9732                    first = false;
9733                )*
9734                write!(f, ")")
9735            }
9736        }
9737    };
9738}
9739
9740// Helper macro for adding conditions
9741#[doc(hidden)]
9742#[macro_export]
9743macro_rules! statute_impl_add_conditions {
9744    ($statute:ident, age >= $age:expr, $($rest:tt)*) => {
9745        $statute = $statute.with_precondition(
9746            $crate::Condition::age($crate::ComparisonOp::GreaterOrEqual, $age)
9747        );
9748        statute_impl_add_conditions!($statute, $($rest)*);
9749    };
9750    ($statute:ident, age > $age:expr, $($rest:tt)*) => {
9751        $statute = $statute.with_precondition(
9752            $crate::Condition::age($crate::ComparisonOp::GreaterThan, $age)
9753        );
9754        statute_impl_add_conditions!($statute, $($rest)*);
9755    };
9756    ($statute:ident, age < $age:expr, $($rest:tt)*) => {
9757        $statute = $statute.with_precondition(
9758            $crate::Condition::age($crate::ComparisonOp::LessThan, $age)
9759        );
9760        statute_impl_add_conditions!($statute, $($rest)*);
9761    };
9762    ($statute:ident, age <= $age:expr, $($rest:tt)*) => {
9763        $statute = $statute.with_precondition(
9764            $crate::Condition::age($crate::ComparisonOp::LessOrEqual, $age)
9765        );
9766        statute_impl_add_conditions!($statute, $($rest)*);
9767    };
9768    ($statute:ident, income >= $income:expr, $($rest:tt)*) => {
9769        $statute = $statute.with_precondition(
9770            $crate::Condition::income($crate::ComparisonOp::GreaterOrEqual, $income)
9771        );
9772        statute_impl_add_conditions!($statute, $($rest)*);
9773    };
9774    ($statute:ident, income < $income:expr, $($rest:tt)*) => {
9775        $statute = $statute.with_precondition(
9776            $crate::Condition::income($crate::ComparisonOp::LessThan, $income)
9777        );
9778        statute_impl_add_conditions!($statute, $($rest)*);
9779    };
9780    ($statute:ident, has_attribute $attr:expr, $($rest:tt)*) => {
9781        $statute = $statute.with_precondition(
9782            $crate::Condition::has_attribute($attr)
9783        );
9784        statute_impl_add_conditions!($statute, $($rest)*);
9785    };
9786    ($statute:ident,) => {};
9787    ($statute:ident) => {};
9788}
9789
9790// Helper macro for adding exceptions
9791#[doc(hidden)]
9792#[macro_export]
9793macro_rules! statute_impl_add_exceptions {
9794    // We'll just skip exceptions for now - users can add them manually
9795    // This is a simplified version for the macro
9796    ($statute:ident, $($rest:tt)*) => {};
9797}
9798
9799/// Declarative macro for defining statutes with a clean, readable syntax.
9800///
9801/// This macro provides a convenient way to define statutes using a declarative syntax
9802/// that resembles natural language and legal documents.
9803///
9804/// # Examples
9805///
9806/// ```
9807/// use legalis_core::{statute, EffectType};
9808///
9809/// let voting_law = statute! {
9810///     id: "voting-rights-2025",
9811///     title: "Voting Rights Act",
9812///     effect: Grant("Right to vote in federal elections"),
9813///     jurisdiction: "US",
9814///     version: 1,
9815/// };
9816///
9817/// assert_eq!(voting_law.id, "voting-rights-2025");
9818/// assert_eq!(voting_law.jurisdiction, Some("US".to_string()));
9819/// ```
9820///
9821/// Simple syntax:
9822///
9823/// ```
9824/// use legalis_core::{statute, EffectType};
9825///
9826/// let tax_law = statute! {
9827///     id: "income-tax-2025",
9828///     title: "Income Tax Law",
9829///     effect: Obligation("Pay income tax"),
9830///     jurisdiction: "US",
9831/// };
9832///
9833/// assert_eq!(tax_law.id, "income-tax-2025");
9834/// ```
9835#[macro_export]
9836macro_rules! statute {
9837    // Syntax with all optional fields
9838    (
9839        id: $id:expr,
9840        title: $title:expr,
9841        effect: $effect_type:ident($effect_desc:expr)
9842        $(, jurisdiction: $jurisdiction:expr)?
9843        $(, version: $version:expr)?
9844        $(, discretion: $discretion:expr)?
9845        $(,)?
9846    ) => {{
9847        let mut statute = $crate::Statute::new(
9848            $id,
9849            $title,
9850            $crate::Effect::new(
9851                $crate::EffectType::$effect_type,
9852                $effect_desc
9853            )
9854        );
9855
9856        $(
9857            statute = statute.with_jurisdiction($jurisdiction);
9858        )?
9859        $(
9860            statute = statute.with_version($version);
9861        )?
9862        $(
9863            statute = statute.with_discretion($discretion);
9864        )?
9865
9866        statute
9867    }};
9868}
9869
9870#[cfg(test)]
9871mod tests {
9872    use super::*;
9873
9874    // Property-based testing with proptest
9875    #[cfg(test)]
9876    mod proptests {
9877        use super::*;
9878        use proptest::prelude::*;
9879
9880        // Strategy for generating valid statute IDs
9881        fn statute_id_strategy() -> impl Strategy<Value = String> {
9882            "[a-z][a-z0-9_-]{0,30}".prop_map(|s| s.to_string())
9883        }
9884
9885        // Strategy for generating comparison operators
9886        fn comparison_op_strategy() -> impl Strategy<Value = ComparisonOp> {
9887            prop_oneof![
9888                Just(ComparisonOp::Equal),
9889                Just(ComparisonOp::NotEqual),
9890                Just(ComparisonOp::GreaterThan),
9891                Just(ComparisonOp::GreaterOrEqual),
9892                Just(ComparisonOp::LessThan),
9893                Just(ComparisonOp::LessOrEqual),
9894            ]
9895        }
9896
9897        // Strategy for generating reasonable ages
9898        fn age_strategy() -> impl Strategy<Value = u32> {
9899            0u32..150u32
9900        }
9901
9902        // Strategy for generating conditions
9903        fn condition_strategy() -> impl Strategy<Value = Condition> {
9904            let leaf = prop_oneof![
9905                (comparison_op_strategy(), age_strategy()).prop_map(|(op, age)| Condition::Age {
9906                    operator: op,
9907                    value: age
9908                }),
9909                (comparison_op_strategy(), any::<u64>()).prop_map(|(op, income)| {
9910                    Condition::Income {
9911                        operator: op,
9912                        value: income,
9913                    }
9914                }),
9915                any::<String>().prop_map(|key| Condition::HasAttribute { key }),
9916                (any::<String>(), any::<String>())
9917                    .prop_map(|(key, value)| Condition::AttributeEquals { key, value }),
9918                any::<String>().prop_map(|desc| Condition::Custom { description: desc }),
9919            ];
9920            leaf.prop_recursive(
9921                3,  // max depth
9922                16, // max nodes
9923                5,  // items per collection
9924                |inner| {
9925                    prop_oneof![
9926                        (inner.clone(), inner.clone())
9927                            .prop_map(|(a, b)| Condition::And(Box::new(a), Box::new(b))),
9928                        (inner.clone(), inner.clone())
9929                            .prop_map(|(a, b)| Condition::Or(Box::new(a), Box::new(b))),
9930                        inner.clone().prop_map(|c| Condition::Not(Box::new(c))),
9931                    ]
9932                },
9933            )
9934        }
9935
9936        proptest! {
9937            #[test]
9938            fn test_legal_result_map_preserves_deterministic(value in any::<i32>()) {
9939                let result = LegalResult::Deterministic(value);
9940                let mapped = result.map(|x| x + 1);
9941                prop_assert!(mapped.is_deterministic());
9942                if let LegalResult::Deterministic(v) = mapped {
9943                    prop_assert_eq!(v, value + 1);
9944                }
9945            }
9946
9947            #[test]
9948            fn test_legal_result_discretion_stays_discretion(issue in "\\PC+", hint in proptest::option::of("\\PC+")) {
9949                let result: LegalResult<i32> = LegalResult::JudicialDiscretion {
9950                    issue: issue.clone(),
9951                    context_id: Uuid::new_v4(),
9952                    narrative_hint: hint.clone(),
9953                };
9954                let mapped = result.map(|x: i32| x + 1);
9955                prop_assert!(mapped.requires_discretion());
9956            }
9957
9958            #[test]
9959            fn test_comparison_op_display_roundtrip(op in comparison_op_strategy()) {
9960                let display = format!("{}", op);
9961                prop_assert!(!display.is_empty());
9962                prop_assert!(display.len() <= 2);
9963            }
9964
9965            #[test]
9966            fn test_condition_display_not_empty(cond in condition_strategy()) {
9967                let display = format!("{}", cond);
9968                prop_assert!(!display.is_empty());
9969            }
9970
9971            #[test]
9972            fn test_statute_id_validation(id in statute_id_strategy()) {
9973                let statute = Statute::new(
9974                    id.clone(),
9975                    "Test Statute",
9976                    Effect::new(EffectType::Grant, "Test effect"),
9977                );
9978                // Valid IDs should not produce InvalidId error
9979                let errors = statute.validate();
9980                prop_assert!(!errors.iter().any(|e| matches!(e, ValidationError::InvalidId(_))));
9981            }
9982
9983            #[test]
9984            fn test_temporal_validity_consistency(
9985                eff_days in 0i64..1000i64,
9986                exp_days in 0i64..1000i64
9987            ) {
9988                let base_date = NaiveDate::from_ymd_opt(2025, 1, 1).unwrap();
9989                let effective = base_date + chrono::Duration::days(eff_days);
9990                let expiry = base_date + chrono::Duration::days(exp_days);
9991
9992                let validity = TemporalValidity::new()
9993                    .with_effective_date(effective)
9994                    .with_expiry_date(expiry);
9995
9996                // The statute should be active between effective and expiry dates
9997                if effective <= expiry {
9998                    prop_assert!(validity.is_active(effective));
9999                    prop_assert!(validity.is_active(expiry));
10000                    if eff_days < exp_days {
10001                        let mid_date = base_date + chrono::Duration::days((eff_days + exp_days) / 2);
10002                        prop_assert!(validity.is_active(mid_date));
10003                    }
10004                }
10005            }
10006
10007            #[test]
10008            fn test_typed_entity_u32_roundtrip(key in "[a-z_]{1,20}", value in any::<u32>()) {
10009                let mut entity = TypedEntity::new();
10010                entity.set_u32(key.clone(), value);
10011                let retrieved = entity.get_u32(&key);
10012                prop_assert_eq!(retrieved, Ok(value));
10013            }
10014
10015            #[test]
10016            fn test_typed_entity_bool_roundtrip(key in "[a-z_]{1,20}", value in any::<bool>()) {
10017                let mut entity = TypedEntity::new();
10018                entity.set_bool(key.clone(), value);
10019                let retrieved = entity.get_bool(&key);
10020                prop_assert_eq!(retrieved, Ok(value));
10021            }
10022
10023            #[test]
10024            fn test_typed_entity_string_roundtrip(
10025                key in "[a-z_]{1,20}",
10026                value in "\\PC*"
10027            ) {
10028                let mut entity = TypedEntity::new();
10029                entity.set_string(key.clone(), value.clone());
10030                let retrieved = entity.get_string(&key);
10031                prop_assert_eq!(retrieved, Ok(value.as_str()));
10032            }
10033
10034            #[test]
10035            fn test_effect_parameters_preservation(
10036                key in "[a-z_]{1,10}",
10037                value in "\\PC{0,20}"
10038            ) {
10039                let effect = Effect::new(EffectType::Grant, "Test effect")
10040                    .with_parameter(key.clone(), value.clone());
10041
10042                prop_assert_eq!(effect.parameters.get(&key).map(String::as_str), Some(value.as_str()));
10043                prop_assert_eq!(effect.parameters.len(), 1);
10044            }
10045
10046            #[test]
10047            fn test_statute_version_validation(version in 1u32..1000u32) {
10048                let statute = Statute::new(
10049                    "test-statute",
10050                    "Test Statute",
10051                    Effect::new(EffectType::Grant, "Test"),
10052                ).with_version(version);
10053
10054                let errors = statute.validate();
10055                // Versions >= 1 should not produce InvalidVersion error
10056                prop_assert!(!errors.iter().any(|e| matches!(e, ValidationError::InvalidVersion)));
10057            }
10058        }
10059
10060        // Additional property tests for edge cases
10061        proptest! {
10062            #[test]
10063            fn test_basic_entity_attribute_storage(
10064                key in "[a-z_]{1,20}",
10065                value in "\\PC{0,50}"
10066            ) {
10067                let mut entity = BasicEntity::new();
10068                entity.set_attribute(&key, value.clone());
10069                let retrieved = entity.get_attribute(&key);
10070                prop_assert_eq!(retrieved, Some(value));
10071            }
10072
10073            #[test]
10074            fn test_statute_builder_preserves_properties(
10075                id in statute_id_strategy(),
10076                title in "\\PC{1,100}",
10077                jurisdiction in proptest::option::of("[A-Z]{2,3}")
10078            ) {
10079                let statute = Statute::new(
10080                    id.clone(),
10081                    title.clone(),
10082                    Effect::new(EffectType::Grant, "Test"),
10083                ).with_jurisdiction(jurisdiction.clone().unwrap_or_default());
10084
10085                prop_assert_eq!(statute.id, id);
10086                prop_assert_eq!(statute.title, title);
10087            }
10088
10089            #[test]
10090            fn test_legal_result_void_stays_void(reason in "\\PC+") {
10091                let result: LegalResult<i32> = LegalResult::Void { reason: reason.clone() };
10092                let mapped = result.map(|x| x * 2);
10093                prop_assert!(mapped.is_void());
10094            }
10095        }
10096    }
10097
10098    #[test]
10099    fn test_legal_result_deterministic() {
10100        let result: LegalResult<i32> = LegalResult::Deterministic(42);
10101        assert!(result.is_deterministic());
10102        assert!(!result.requires_discretion());
10103        assert!(!result.is_void());
10104    }
10105
10106    #[test]
10107    fn test_legal_result_discretion() {
10108        let result: LegalResult<i32> = LegalResult::JudicialDiscretion {
10109            issue: "test issue".to_string(),
10110            context_id: Uuid::new_v4(),
10111            narrative_hint: None,
10112        };
10113        assert!(!result.is_deterministic());
10114        assert!(result.requires_discretion());
10115    }
10116
10117    #[test]
10118    fn test_legal_result_map() {
10119        let result: LegalResult<i32> = LegalResult::Deterministic(21);
10120        let mapped = result.map(|x| x * 2);
10121        assert_eq!(mapped, LegalResult::Deterministic(42));
10122    }
10123
10124    #[test]
10125    fn test_legal_result_display() {
10126        let det: LegalResult<i32> = LegalResult::Deterministic(42);
10127        assert_eq!(format!("{}", det), "Deterministic(42)");
10128
10129        let disc: LegalResult<i32> = LegalResult::JudicialDiscretion {
10130            issue: "test issue".to_string(),
10131            context_id: Uuid::new_v4(),
10132            narrative_hint: Some("consider facts".to_string()),
10133        };
10134        assert!(format!("{}", disc).contains("test issue"));
10135        assert!(format!("{}", disc).contains("consider facts"));
10136    }
10137
10138    #[test]
10139    fn test_basic_entity() {
10140        let mut entity = BasicEntity::new();
10141        entity.set_attribute("age", "25".to_string());
10142        assert_eq!(entity.get_attribute("age"), Some("25".to_string()));
10143        assert_eq!(entity.get_attribute("nonexistent"), None);
10144    }
10145
10146    #[test]
10147    fn test_statute_builder() {
10148        let statute = Statute::new(
10149            "test-statute-1",
10150            "Test Statute",
10151            Effect::new(EffectType::Grant, "Grant test permission"),
10152        )
10153        .with_precondition(Condition::Age {
10154            operator: ComparisonOp::GreaterOrEqual,
10155            value: 18,
10156        })
10157        .with_discretion("Consider special circumstances");
10158
10159        assert_eq!(statute.id, "test-statute-1");
10160        assert_eq!(statute.preconditions.len(), 1);
10161        assert!(statute.discretion_logic.is_some());
10162    }
10163
10164    #[test]
10165    fn test_temporal_validity() {
10166        let today = NaiveDate::from_ymd_opt(2025, 6, 15).unwrap();
10167        let past = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
10168        let future = NaiveDate::from_ymd_opt(2026, 12, 31).unwrap();
10169
10170        let validity = TemporalValidity::new()
10171            .with_effective_date(past)
10172            .with_expiry_date(future);
10173
10174        assert!(validity.is_active(today));
10175        assert!(!validity.is_active(NaiveDate::from_ymd_opt(2023, 1, 1).unwrap()));
10176        assert!(!validity.is_active(NaiveDate::from_ymd_opt(2027, 1, 1).unwrap()));
10177    }
10178
10179    #[test]
10180    fn test_statute_with_temporal_validity() {
10181        let statute = Statute::new(
10182            "sunset-test",
10183            "Sunset Test Act",
10184            Effect::new(EffectType::Grant, "Temporary grant"),
10185        )
10186        .with_temporal_validity(
10187            TemporalValidity::new()
10188                .with_effective_date(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap())
10189                .with_expiry_date(NaiveDate::from_ymd_opt(2025, 12, 31).unwrap()),
10190        )
10191        .with_jurisdiction("US-CA");
10192
10193        assert!(statute.is_active(NaiveDate::from_ymd_opt(2025, 6, 1).unwrap()));
10194        assert!(!statute.is_active(NaiveDate::from_ymd_opt(2024, 12, 31).unwrap()));
10195        assert_eq!(statute.jurisdiction, Some("US-CA".to_string()));
10196    }
10197
10198    #[test]
10199    fn test_condition_display() {
10200        let age_cond = Condition::Age {
10201            operator: ComparisonOp::GreaterOrEqual,
10202            value: 18,
10203        };
10204        assert_eq!(format!("{}", age_cond), "age >= 18");
10205
10206        let and_cond = Condition::And(
10207            Box::new(Condition::Age {
10208                operator: ComparisonOp::GreaterOrEqual,
10209                value: 18,
10210            }),
10211            Box::new(Condition::Income {
10212                operator: ComparisonOp::LessThan,
10213                value: 50000,
10214            }),
10215        );
10216        assert!(format!("{}", and_cond).contains("AND"));
10217    }
10218
10219    #[test]
10220    fn test_geographic_condition() {
10221        let cond = Condition::Geographic {
10222            region_type: RegionType::State,
10223            region_id: "CA".to_string(),
10224        };
10225        assert!(format!("{}", cond).contains("State"));
10226        assert!(format!("{}", cond).contains("CA"));
10227    }
10228
10229    #[test]
10230    fn test_entity_relationship_condition() {
10231        let cond = Condition::EntityRelationship {
10232            relationship_type: RelationshipType::Employment,
10233            target_entity_id: Some("employer-123".to_string()),
10234        };
10235        assert!(format!("{}", cond).contains("Employment"));
10236    }
10237
10238    #[test]
10239    fn test_statute_display() {
10240        let statute = Statute::new(
10241            "display-test",
10242            "Display Test Act",
10243            Effect::new(EffectType::Grant, "Test grant"),
10244        )
10245        .with_precondition(Condition::Age {
10246            operator: ComparisonOp::GreaterOrEqual,
10247            value: 21,
10248        })
10249        .with_version(2)
10250        .with_jurisdiction("JP");
10251
10252        let display = format!("{}", statute);
10253        assert!(display.contains("display-test"));
10254        assert!(display.contains("Display Test Act"));
10255        assert!(display.contains("VERSION: 2"));
10256        assert!(display.contains("JP"));
10257    }
10258
10259    #[test]
10260    fn test_statute_validation_valid() {
10261        let statute = Statute::new(
10262            "valid-statute",
10263            "Valid Statute",
10264            Effect::new(EffectType::Grant, "Grant something"),
10265        );
10266
10267        assert!(statute.is_valid());
10268        assert!(statute.validate().is_empty());
10269    }
10270
10271    #[test]
10272    fn test_statute_validation_empty_id() {
10273        let mut statute = Statute::new("temp", "Test", Effect::new(EffectType::Grant, "Grant"));
10274        statute.id = String::new();
10275
10276        let errors = statute.validate();
10277        assert!(errors.iter().any(|e| matches!(e, ValidationError::EmptyId)));
10278    }
10279
10280    #[test]
10281    fn test_statute_validation_invalid_id() {
10282        let mut statute = Statute::new("temp", "Test", Effect::new(EffectType::Grant, "Grant"));
10283        statute.id = "123-invalid".to_string(); // Starts with number
10284
10285        let errors = statute.validate();
10286        assert!(
10287            errors
10288                .iter()
10289                .any(|e| matches!(e, ValidationError::InvalidId(_)))
10290        );
10291    }
10292
10293    #[test]
10294    fn test_statute_validation_empty_title() {
10295        let mut statute = Statute::new("test-id", "temp", Effect::new(EffectType::Grant, "Grant"));
10296        statute.title = String::new();
10297
10298        let errors = statute.validate();
10299        assert!(
10300            errors
10301                .iter()
10302                .any(|e| matches!(e, ValidationError::EmptyTitle))
10303        );
10304    }
10305
10306    #[test]
10307    fn test_statute_validation_expiry_before_effective() {
10308        let statute = Statute::new(
10309            "temporal-error",
10310            "Temporal Error Statute",
10311            Effect::new(EffectType::Grant, "Grant"),
10312        )
10313        .with_temporal_validity(TemporalValidity {
10314            effective_date: Some(NaiveDate::from_ymd_opt(2025, 12, 31).unwrap()),
10315            expiry_date: Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()), // Before effective!
10316            enacted_at: None,
10317            amended_at: None,
10318        });
10319
10320        let errors = statute.validate();
10321        assert!(
10322            errors
10323                .iter()
10324                .any(|e| matches!(e, ValidationError::ExpiryBeforeEffective { .. }))
10325        );
10326    }
10327
10328    #[test]
10329    fn test_statute_validation_invalid_condition() {
10330        let statute = Statute::new(
10331            "age-error",
10332            "Age Error Statute",
10333            Effect::new(EffectType::Grant, "Grant"),
10334        )
10335        .with_precondition(Condition::Age {
10336            operator: ComparisonOp::GreaterOrEqual,
10337            value: 200, // Unrealistic age
10338        });
10339
10340        let errors = statute.validate();
10341        assert!(
10342            errors
10343                .iter()
10344                .any(|e| matches!(e, ValidationError::InvalidCondition { .. }))
10345        );
10346    }
10347
10348    #[test]
10349    fn test_statute_validation_zero_version() {
10350        let mut statute = Statute::new(
10351            "zero-version",
10352            "Zero Version Statute",
10353            Effect::new(EffectType::Grant, "Grant"),
10354        );
10355        statute.version = 0;
10356
10357        let errors = statute.validate();
10358        assert!(
10359            errors
10360                .iter()
10361                .any(|e| matches!(e, ValidationError::InvalidVersion))
10362        );
10363    }
10364
10365    #[test]
10366    fn test_statute_validated_method() {
10367        let valid_statute = Statute::new(
10368            "valid",
10369            "Valid Statute",
10370            Effect::new(EffectType::Grant, "Grant"),
10371        );
10372        assert!(valid_statute.validated().is_ok());
10373
10374        let mut invalid_statute = Statute::new(
10375            "invalid",
10376            "Invalid",
10377            Effect::new(EffectType::Grant, "Grant"),
10378        );
10379        invalid_statute.id = String::new();
10380        assert!(invalid_statute.validated().is_err());
10381    }
10382
10383    #[test]
10384    fn test_validation_error_display() {
10385        assert!(ValidationError::EmptyId.to_string().contains("empty"));
10386        assert!(ValidationError::EmptyTitle.to_string().contains("title"));
10387        assert!(
10388            ValidationError::InvalidVersion
10389                .to_string()
10390                .contains("Version")
10391        );
10392    }
10393
10394    #[test]
10395    fn test_typed_entity_basic_operations() {
10396        let mut entity = TypedEntity::new();
10397
10398        // Set various typed attributes
10399        entity.set_u32("age", 25);
10400        entity.set_u64("income", 50000);
10401        entity.set_bool("is_citizen", true);
10402        entity.set_string("name", "Alice");
10403        entity.set_date("birth_date", NaiveDate::from_ymd_opt(1999, 1, 15).unwrap());
10404        entity.set_f64("tax_rate", 0.15);
10405
10406        // Get typed attributes
10407        assert_eq!(entity.get_u32("age").unwrap(), 25);
10408        assert_eq!(entity.get_u64("income").unwrap(), 50000);
10409        assert!(entity.get_bool("is_citizen").unwrap());
10410        assert_eq!(entity.get_string("name").unwrap(), "Alice");
10411        assert_eq!(
10412            entity.get_date("birth_date").unwrap(),
10413            NaiveDate::from_ymd_opt(1999, 1, 15).unwrap()
10414        );
10415        assert_eq!(entity.get_f64("tax_rate").unwrap(), 0.15);
10416
10417        // Test attribute existence
10418        assert!(entity.has_attribute("age"));
10419        assert!(!entity.has_attribute("nonexistent"));
10420    }
10421
10422    #[test]
10423    fn test_typed_entity_type_safety() {
10424        let mut entity = TypedEntity::new();
10425        entity.set_string("name", "Bob");
10426
10427        // Attempting to get string as u32 should fail
10428        assert!(entity.get_u32("name").is_err());
10429
10430        // Not found error
10431        assert!(entity.get_u32("missing").is_err());
10432    }
10433
10434    #[test]
10435    fn test_typed_entity_legal_entity_trait() {
10436        let mut entity = TypedEntity::new();
10437
10438        // Test LegalEntity trait implementation
10439        let _id = entity.id();
10440        assert!(entity.get_attribute("age").is_none());
10441
10442        // Set via LegalEntity trait (uses string parsing)
10443        entity.set_attribute("age", "30".to_string());
10444        assert_eq!(entity.get_attribute("age").unwrap(), "30");
10445
10446        // Verify it was parsed as u32
10447        assert_eq!(entity.get_u32("age").unwrap(), 30);
10448
10449        // Set boolean via trait
10450        entity.set_attribute("active", "true".to_string());
10451        assert!(entity.get_bool("active").unwrap());
10452    }
10453
10454    #[test]
10455    fn test_typed_entity_backward_compatibility() {
10456        let mut entity = TypedEntity::new();
10457
10458        // Simulate old string-based code
10459        entity.set_attribute("age", "25".to_string());
10460        entity.set_attribute("income", "50000".to_string());
10461        entity.set_attribute("is_citizen", "true".to_string());
10462
10463        // New typed code can read these
10464        assert_eq!(entity.get_u32("age").unwrap(), 25);
10465        assert_eq!(entity.get_u64("income").unwrap(), 50000);
10466        assert!(entity.get_bool("is_citizen").unwrap());
10467
10468        // Verify string retrieval still works
10469        assert_eq!(entity.get_attribute("age").unwrap(), "25");
10470        assert_eq!(entity.get_attribute("income").unwrap(), "50000");
10471        assert_eq!(entity.get_attribute("is_citizen").unwrap(), "true");
10472    }
10473
10474    #[test]
10475    fn test_typed_entity_attribute_value_conversions() {
10476        let mut entity = TypedEntity::new();
10477
10478        // Test AttributeValue directly
10479        entity.set_typed("count", AttributeValue::U32(42));
10480        let val = entity.get_typed("count").unwrap();
10481        assert_eq!(val.as_u32().unwrap(), 42);
10482        assert_eq!(val.as_u64().unwrap(), 42); // Upcasting works
10483
10484        // Test date parsing
10485        entity.set_attribute("registration_date", "2024-01-15".to_string());
10486        assert_eq!(
10487            entity.get_date("registration_date").unwrap(),
10488            NaiveDate::from_ymd_opt(2024, 1, 15).unwrap()
10489        );
10490    }
10491
10492    #[test]
10493    fn test_typed_entity_integration_with_condition() {
10494        let mut entity = TypedEntity::new();
10495        entity.set_u32("age", 25);
10496
10497        // TypedEntity implements LegalEntity, so it works with existing condition checking
10498        assert_eq!(entity.get_attribute("age").unwrap(), "25");
10499
10500        // This demonstrates backward compatibility with existing SimEngine code
10501        let age_from_trait = entity
10502            .get_attribute("age")
10503            .and_then(|v| v.parse::<u32>().ok());
10504        assert_eq!(age_from_trait, Some(25));
10505    }
10506
10507    #[test]
10508    fn test_condition_helpers() {
10509        let simple = Condition::age(ComparisonOp::GreaterOrEqual, 18);
10510        assert!(simple.is_simple());
10511        assert!(!simple.is_compound());
10512        assert_eq!(simple.count_conditions(), 1);
10513        assert_eq!(simple.depth(), 1);
10514
10515        let compound = simple
10516            .clone()
10517            .and(Condition::income(ComparisonOp::LessThan, 50000));
10518        assert!(compound.is_compound());
10519        assert!(!compound.is_simple());
10520        assert_eq!(compound.count_conditions(), 3); // AND + 2 leaves
10521        assert_eq!(compound.depth(), 2);
10522
10523        let negated = simple.clone().not();
10524        assert!(negated.is_negation());
10525        assert!(negated.is_compound());
10526        assert_eq!(negated.count_conditions(), 2); // NOT + 1 leaf
10527    }
10528
10529    #[test]
10530    fn test_condition_constructors() {
10531        let age_cond = Condition::age(ComparisonOp::GreaterOrEqual, 21);
10532        assert!(matches!(age_cond, Condition::Age { value: 21, .. }));
10533
10534        let income_cond = Condition::income(ComparisonOp::LessThan, 100000);
10535        assert!(matches!(
10536            income_cond,
10537            Condition::Income { value: 100000, .. }
10538        ));
10539
10540        let attr_cond = Condition::has_attribute("license");
10541        assert!(matches!(attr_cond, Condition::HasAttribute { .. }));
10542
10543        let eq_cond = Condition::attribute_equals("status", "active");
10544        assert!(matches!(eq_cond, Condition::AttributeEquals { .. }));
10545
10546        let custom = Condition::custom("Complex eligibility check");
10547        assert!(matches!(custom, Condition::Custom { .. }));
10548    }
10549
10550    #[test]
10551    fn test_condition_combinators() {
10552        let c1 = Condition::age(ComparisonOp::GreaterOrEqual, 18);
10553        let c2 = Condition::income(ComparisonOp::LessThan, 50000);
10554        let c3 = Condition::has_attribute("citizenship");
10555
10556        let combined = c1.and(c2).or(c3);
10557        assert_eq!(combined.count_conditions(), 5); // OR + (AND + 2 leaves) + 1 leaf
10558        assert_eq!(combined.depth(), 3);
10559    }
10560
10561    #[test]
10562    fn test_comparison_op_inverse() {
10563        assert_eq!(ComparisonOp::Equal.inverse(), ComparisonOp::NotEqual);
10564        assert_eq!(ComparisonOp::NotEqual.inverse(), ComparisonOp::Equal);
10565        assert_eq!(
10566            ComparisonOp::GreaterThan.inverse(),
10567            ComparisonOp::LessOrEqual
10568        );
10569        assert_eq!(
10570            ComparisonOp::GreaterOrEqual.inverse(),
10571            ComparisonOp::LessThan
10572        );
10573        assert_eq!(
10574            ComparisonOp::LessThan.inverse(),
10575            ComparisonOp::GreaterOrEqual
10576        );
10577        assert_eq!(
10578            ComparisonOp::LessOrEqual.inverse(),
10579            ComparisonOp::GreaterThan
10580        );
10581    }
10582
10583    #[test]
10584    fn test_comparison_op_classification() {
10585        assert!(ComparisonOp::Equal.is_equality());
10586        assert!(ComparisonOp::NotEqual.is_equality());
10587        assert!(!ComparisonOp::GreaterThan.is_equality());
10588
10589        assert!(ComparisonOp::GreaterThan.is_ordering());
10590        assert!(ComparisonOp::LessThan.is_ordering());
10591        assert!(!ComparisonOp::Equal.is_ordering());
10592    }
10593
10594    #[test]
10595    fn test_effect_helpers() {
10596        let mut effect = Effect::new(EffectType::Grant, "Test effect")
10597            .with_parameter("key1", "value1")
10598            .with_parameter("key2", "value2");
10599
10600        assert_eq!(effect.parameter_count(), 2);
10601        assert!(effect.has_parameter("key1"));
10602        assert!(!effect.has_parameter("key3"));
10603        assert_eq!(effect.get_parameter("key1"), Some(&"value1".to_string()));
10604        assert_eq!(effect.get_parameter("key3"), None);
10605
10606        let removed = effect.remove_parameter("key1");
10607        assert_eq!(removed, Some("value1".to_string()));
10608        assert_eq!(effect.parameter_count(), 1);
10609    }
10610
10611    #[test]
10612    fn test_effect_constructors() {
10613        let grant = Effect::grant("Right to vote");
10614        assert_eq!(grant.effect_type, EffectType::Grant);
10615
10616        let revoke = Effect::revoke("Driving privileges");
10617        assert_eq!(revoke.effect_type, EffectType::Revoke);
10618
10619        let obligation = Effect::obligation("Pay taxes");
10620        assert_eq!(obligation.effect_type, EffectType::Obligation);
10621
10622        let prohibition = Effect::prohibition("Smoking in public");
10623        assert_eq!(prohibition.effect_type, EffectType::Prohibition);
10624    }
10625
10626    #[test]
10627    fn test_statute_helper_methods() {
10628        let statute = Statute::new("test-id", "Test Statute", Effect::grant("Test permission"))
10629            .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18))
10630            .with_precondition(Condition::has_attribute("citizenship"))
10631            .with_discretion("Consider special circumstances");
10632
10633        assert_eq!(statute.precondition_count(), 2);
10634        assert!(statute.has_preconditions());
10635        assert!(statute.has_discretion());
10636        assert!(!statute.has_jurisdiction());
10637
10638        let with_jurisdiction = statute.clone().with_jurisdiction("US");
10639        assert!(with_jurisdiction.has_jurisdiction());
10640
10641        let conditions = statute.preconditions();
10642        assert_eq!(conditions.len(), 2);
10643    }
10644
10645    #[test]
10646    fn test_temporal_validity_helpers() {
10647        use chrono::Utc;
10648
10649        let validity = TemporalValidity::new()
10650            .with_effective_date(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap())
10651            .with_expiry_date(NaiveDate::from_ymd_opt(2030, 12, 31).unwrap())
10652            .with_enacted_at(Utc::now());
10653
10654        assert!(validity.has_effective_date());
10655        assert!(validity.has_expiry_date());
10656        assert!(validity.is_enacted());
10657        assert!(!validity.is_amended());
10658
10659        let test_date_active = NaiveDate::from_ymd_opt(2026, 6, 15).unwrap();
10660        let test_date_expired = NaiveDate::from_ymd_opt(2031, 1, 1).unwrap();
10661        let test_date_pending = NaiveDate::from_ymd_opt(2024, 12, 31).unwrap();
10662
10663        assert!(validity.is_active(test_date_active));
10664        assert!(validity.has_expired(test_date_expired));
10665        assert!(validity.is_pending(test_date_pending));
10666    }
10667
10668    #[test]
10669    fn test_duration_condition() {
10670        let employment = Condition::duration(ComparisonOp::GreaterOrEqual, 5, DurationUnit::Years);
10671        assert!(matches!(employment, Condition::Duration { .. }));
10672        assert_eq!(format!("{}", employment), "duration >= 5 years");
10673
10674        let probation = Condition::duration(ComparisonOp::LessThan, 90, DurationUnit::Days);
10675        assert_eq!(format!("{}", probation), "duration < 90 days");
10676    }
10677
10678    #[test]
10679    fn test_percentage_condition() {
10680        let ownership = Condition::percentage(ComparisonOp::GreaterOrEqual, 25, "ownership");
10681        assert!(matches!(ownership, Condition::Percentage { .. }));
10682        assert_eq!(format!("{}", ownership), "ownership >= 25%");
10683
10684        let threshold = Condition::percentage(ComparisonOp::LessThan, 50, "voting_power");
10685        assert_eq!(format!("{}", threshold), "voting_power < 50%");
10686    }
10687
10688    #[test]
10689    fn test_set_membership_condition() {
10690        let status_in = Condition::in_set(
10691            "status",
10692            vec![
10693                "active".to_string(),
10694                "pending".to_string(),
10695                "approved".to_string(),
10696            ],
10697        );
10698        assert!(matches!(status_in, Condition::SetMembership { .. }));
10699        assert_eq!(
10700            format!("{}", status_in),
10701            "status IN {active, pending, approved}"
10702        );
10703
10704        let status_not_in = Condition::not_in_set(
10705            "status",
10706            vec!["rejected".to_string(), "canceled".to_string()],
10707        );
10708        assert_eq!(
10709            format!("{}", status_not_in),
10710            "status NOT IN {rejected, canceled}"
10711        );
10712    }
10713
10714    #[test]
10715    fn test_pattern_condition() {
10716        let matches = Condition::matches_pattern("id", "^[A-Z]{2}[0-9]{6}$");
10717        assert!(matches!(matches, Condition::Pattern { .. }));
10718        assert_eq!(format!("{}", matches), "id =~ /^[A-Z]{2}[0-9]{6}$/");
10719
10720        let not_matches = Condition::not_matches_pattern("email", ".*@spam\\.com$");
10721        assert_eq!(format!("{}", not_matches), "email !~ /.*@spam\\.com$/");
10722    }
10723
10724    #[test]
10725    fn test_duration_unit_display() {
10726        assert_eq!(format!("{}", DurationUnit::Days), "days");
10727        assert_eq!(format!("{}", DurationUnit::Weeks), "weeks");
10728        assert_eq!(format!("{}", DurationUnit::Months), "months");
10729        assert_eq!(format!("{}", DurationUnit::Years), "years");
10730    }
10731
10732    #[test]
10733    fn test_duration_unit_ordering() {
10734        assert!(DurationUnit::Days < DurationUnit::Weeks);
10735        assert!(DurationUnit::Weeks < DurationUnit::Months);
10736        assert!(DurationUnit::Months < DurationUnit::Years);
10737    }
10738
10739    #[test]
10740    fn test_new_conditions_with_combinators() {
10741        let employment_eligible =
10742            Condition::duration(ComparisonOp::GreaterOrEqual, 1, DurationUnit::Years).and(
10743                Condition::percentage(ComparisonOp::GreaterOrEqual, 80, "attendance"),
10744            );
10745
10746        assert!(format!("{}", employment_eligible).contains("AND"));
10747        assert!(format!("{}", employment_eligible).contains("duration"));
10748        assert!(format!("{}", employment_eligible).contains("attendance"));
10749
10750        let status_check =
10751            Condition::in_set("status", vec!["active".to_string(), "verified".to_string()])
10752                .or(Condition::matches_pattern("id", "^VIP-"));
10753
10754        assert!(format!("{}", status_check).contains("OR"));
10755        assert!(format!("{}", status_check).contains("IN"));
10756        assert!(format!("{}", status_check).contains("=~"));
10757    }
10758
10759    #[test]
10760    fn test_new_conditions_count_and_depth() {
10761        let simple = Condition::duration(ComparisonOp::GreaterOrEqual, 5, DurationUnit::Years);
10762        assert_eq!(simple.count_conditions(), 1);
10763        assert_eq!(simple.depth(), 1);
10764        assert!(simple.is_simple());
10765        assert!(!simple.is_compound());
10766
10767        let compound = Condition::percentage(ComparisonOp::GreaterOrEqual, 25, "ownership").and(
10768            Condition::in_set("status", vec!["active".to_string(), "verified".to_string()]),
10769        );
10770        assert_eq!(compound.count_conditions(), 3);
10771        assert_eq!(compound.depth(), 2);
10772        assert!(!compound.is_simple());
10773        assert!(compound.is_compound());
10774    }
10775
10776    #[test]
10777    fn test_conflict_resolution_temporal_precedence() {
10778        let old_law = Statute::new(
10779            "old-1",
10780            "Old Law",
10781            Effect::new(EffectType::Grant, "Old grant"),
10782        )
10783        .with_temporal_validity(
10784            TemporalValidity::new()
10785                .with_effective_date(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()),
10786        );
10787
10788        let new_law = Statute::new(
10789            "new-1",
10790            "New Law",
10791            Effect::new(EffectType::Prohibition, "New prohibition"),
10792        )
10793        .with_temporal_validity(
10794            TemporalValidity::new()
10795                .with_effective_date(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap()),
10796        );
10797
10798        let resolution = StatuteConflictAnalyzer::resolve(&old_law, &new_law);
10799        assert_eq!(
10800            resolution,
10801            ConflictResolution::SecondPrevails(ConflictReason::TemporalPrecedence)
10802        );
10803    }
10804
10805    #[test]
10806    fn test_conflict_resolution_specificity() {
10807        let general = Statute::new(
10808            "general",
10809            "General Law",
10810            Effect::new(EffectType::Grant, "General grant"),
10811        )
10812        .with_temporal_validity(
10813            TemporalValidity::new()
10814                .with_effective_date(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()),
10815        );
10816
10817        let specific = Statute::new(
10818            "specific",
10819            "Specific Law",
10820            Effect::new(EffectType::Prohibition, "Specific prohibition"),
10821        )
10822        .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18))
10823        .with_precondition(Condition::income(ComparisonOp::LessThan, 50000))
10824        .with_temporal_validity(
10825            TemporalValidity::new()
10826                .with_effective_date(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()),
10827        );
10828
10829        let resolution = StatuteConflictAnalyzer::resolve(&general, &specific);
10830        assert_eq!(
10831            resolution,
10832            ConflictResolution::SecondPrevails(ConflictReason::Specificity)
10833        );
10834    }
10835
10836    #[test]
10837    fn test_conflict_resolution_hierarchy() {
10838        let state_law = Statute::new(
10839            "state-1",
10840            "State Law",
10841            Effect::new(EffectType::Grant, "State grant"),
10842        )
10843        .with_jurisdiction("US-NY")
10844        .with_temporal_validity(
10845            TemporalValidity::new()
10846                .with_effective_date(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()),
10847        );
10848
10849        let federal_law = Statute::new(
10850            "fed-1",
10851            "Federal Law",
10852            Effect::new(EffectType::Prohibition, "Federal prohibition"),
10853        )
10854        .with_jurisdiction("US")
10855        .with_temporal_validity(
10856            TemporalValidity::new()
10857                .with_effective_date(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()),
10858        );
10859
10860        let resolution = StatuteConflictAnalyzer::resolve(&state_law, &federal_law);
10861        assert_eq!(
10862            resolution,
10863            ConflictResolution::SecondPrevails(ConflictReason::Hierarchy)
10864        );
10865    }
10866
10867    #[test]
10868    fn test_conflict_resolution_no_conflict() {
10869        let law1 = Statute::new("law-1", "Law 1", Effect::new(EffectType::Grant, "Grant A"));
10870
10871        let law2 = Statute::new("law-2", "Law 2", Effect::new(EffectType::Grant, "Grant B"));
10872
10873        let resolution = StatuteConflictAnalyzer::resolve(&law1, &law2);
10874        assert_eq!(resolution, ConflictResolution::NoConflict);
10875    }
10876
10877    #[test]
10878    fn test_conflict_resolution_unresolvable() {
10879        // Two conflicting statutes with same date, specificity, and jurisdiction
10880        let law1 = Statute::new("law-1", "Law 1", Effect::new(EffectType::Grant, "Grant"))
10881            .with_temporal_validity(
10882                TemporalValidity::new()
10883                    .with_effective_date(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()),
10884            )
10885            .with_jurisdiction("US");
10886
10887        let law2 = Statute::new(
10888            "law-2",
10889            "Law 2",
10890            Effect::new(EffectType::Prohibition, "Prohibition"),
10891        )
10892        .with_temporal_validity(
10893            TemporalValidity::new()
10894                .with_effective_date(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()),
10895        )
10896        .with_jurisdiction("US");
10897
10898        let resolution = StatuteConflictAnalyzer::resolve(&law1, &law2);
10899        assert!(matches!(resolution, ConflictResolution::Unresolvable(_)));
10900    }
10901
10902    #[test]
10903    fn test_conflict_resolution_is_in_effect() {
10904        let statute = Statute::new("test", "Test", Effect::grant("Test")).with_temporal_validity(
10905            TemporalValidity::new()
10906                .with_effective_date(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap())
10907                .with_expiry_date(NaiveDate::from_ymd_opt(2030, 12, 31).unwrap()),
10908        );
10909
10910        assert!(StatuteConflictAnalyzer::is_in_effect(
10911            &statute,
10912            NaiveDate::from_ymd_opt(2025, 6, 15).unwrap()
10913        ));
10914        assert!(!StatuteConflictAnalyzer::is_in_effect(
10915            &statute,
10916            NaiveDate::from_ymd_opt(2019, 1, 1).unwrap()
10917        ));
10918        assert!(!StatuteConflictAnalyzer::is_in_effect(
10919            &statute,
10920            NaiveDate::from_ymd_opt(2031, 1, 1).unwrap()
10921        ));
10922    }
10923
10924    #[test]
10925    fn test_conflict_resolution_resolve_conflicts_at_date() {
10926        let old_general = Statute::new("old-gen", "Old General", Effect::grant("Grant"))
10927            .with_temporal_validity(
10928                TemporalValidity::new()
10929                    .with_effective_date(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap()),
10930            );
10931
10932        let new_specific = Statute::new("new-spec", "New Specific", Effect::grant("Grant"))
10933            .with_temporal_validity(
10934                TemporalValidity::new()
10935                    .with_effective_date(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()),
10936            )
10937            .with_precondition(Condition::age(ComparisonOp::GreaterOrEqual, 18));
10938
10939        let federal = Statute::new("federal", "Federal", Effect::grant("Grant"))
10940            .with_temporal_validity(
10941                TemporalValidity::new()
10942                    .with_effective_date(NaiveDate::from_ymd_opt(2018, 1, 1).unwrap()),
10943            )
10944            .with_jurisdiction("US");
10945
10946        let expired = Statute::new("expired", "Expired", Effect::grant("Grant"))
10947            .with_temporal_validity(
10948                TemporalValidity::new()
10949                    .with_effective_date(NaiveDate::from_ymd_opt(2010, 1, 1).unwrap())
10950                    .with_expiry_date(NaiveDate::from_ymd_opt(2015, 12, 31).unwrap()),
10951            );
10952
10953        let statutes = vec![old_general, new_specific, federal, expired];
10954        let active = StatuteConflictAnalyzer::resolve_conflicts_at_date(
10955            &statutes,
10956            NaiveDate::from_ymd_opt(2022, 1, 1).unwrap(),
10957        );
10958
10959        // Should have 3 active (expired should be filtered out)
10960        assert_eq!(active.len(), 3);
10961
10962        // Most recent and specific should be first
10963        assert_eq!(active[0].id, "new-spec");
10964    }
10965
10966    #[test]
10967    fn test_conflict_reason_display() {
10968        assert_eq!(
10969            format!("{}", ConflictReason::TemporalPrecedence),
10970            "lex posterior (later law prevails)"
10971        );
10972        assert_eq!(
10973            format!("{}", ConflictReason::Specificity),
10974            "lex specialis (more specific law prevails)"
10975        );
10976        assert_eq!(
10977            format!("{}", ConflictReason::Hierarchy),
10978            "lex superior (higher authority prevails)"
10979        );
10980    }
10981
10982    #[test]
10983    fn test_jurisdiction_level_detection() {
10984        // Federal/National
10985        assert_eq!(
10986            StatuteConflictAnalyzer::jurisdiction_level(&Some("US".to_string())),
10987            3
10988        );
10989        assert_eq!(
10990            StatuteConflictAnalyzer::jurisdiction_level(&Some("Federal".to_string())),
10991            3
10992        );
10993
10994        // State/Provincial
10995        assert_eq!(
10996            StatuteConflictAnalyzer::jurisdiction_level(&Some("US-NY".to_string())),
10997            2
10998        );
10999        assert_eq!(
11000            StatuteConflictAnalyzer::jurisdiction_level(&Some("State-CA".to_string())),
11001            2
11002        );
11003
11004        // Local/Municipal
11005        assert_eq!(
11006            StatuteConflictAnalyzer::jurisdiction_level(&Some("Local-NYC".to_string())),
11007            1
11008        );
11009
11010        // Unknown/None
11011        assert_eq!(StatuteConflictAnalyzer::jurisdiction_level(&None), 0);
11012    }
11013}