Skip to main content

observable_property/
lib.rs

1//! # Observable Property
2//!
3//! A thread-safe observable property implementation for Rust that allows you to
4//! observe changes to values across multiple threads.
5//!
6//! ## Features
7//!
8//! - **Thread-safe**: Uses `Arc<RwLock<>>` for safe concurrent access
9//! - **Observer pattern**: Subscribe to property changes with callbacks
10//! - **RAII subscriptions**: Automatic cleanup with subscription guards (no manual unsubscribe needed)
11//! - **Filtered observers**: Only notify when specific conditions are met
12//! - **Async notifications**: Non-blocking observer notifications with background threads
13//! - **Panic isolation**: Observer panics don't crash the system
14//! - **Graceful lock recovery**: Continues operation even after lock poisoning from panics
15//! - **Memory protection**: Observer limit (10,000) prevents memory exhaustion
16//! - **Type-safe**: Generic implementation works with any `Clone + Send + Sync` type
17//!
18//! ## Quick Start
19//!
20//! ```rust
21//! use observable_property::ObservableProperty;
22//! use std::sync::Arc;
23//!
24//! // Create an observable property
25//! let property = ObservableProperty::new(42);
26//!
27//! // Subscribe to changes
28//! let observer_id = property.subscribe(Arc::new(|old_value, new_value| {
29//!     println!("Value changed from {} to {}", old_value, new_value);
30//! })).map_err(|e| {
31//!     eprintln!("Failed to subscribe: {}", e);
32//!     e
33//! })?;
34//!
35//! // Change the value (triggers observer)
36//! property.set(100).map_err(|e| {
37//!     eprintln!("Failed to set value: {}", e);
38//!     e
39//! })?;
40//!
41//! // Unsubscribe when done
42//! property.unsubscribe(observer_id).map_err(|e| {
43//!     eprintln!("Failed to unsubscribe: {}", e);
44//!     e
45//! })?;
46//! # Ok::<(), observable_property::PropertyError>(())
47//! ```
48//!
49//! ## Multi-threading Example
50//!
51//! ```rust
52//! use observable_property::ObservableProperty;
53//! use std::sync::Arc;
54//! use std::thread;
55//!
56//! let property = Arc::new(ObservableProperty::new(0));
57//! let property_clone = property.clone();
58//!
59//! // Subscribe from one thread
60//! property.subscribe(Arc::new(|old, new| {
61//!     println!("Value changed: {} -> {}", old, new);
62//! })).map_err(|e| {
63//!     eprintln!("Failed to subscribe: {}", e);
64//!     e
65//! })?;
66//!
67//! // Modify from another thread
68//! thread::spawn(move || {
69//!     if let Err(e) = property_clone.set(42) {
70//!         eprintln!("Failed to set value: {}", e);
71//!     }
72//! }).join().expect("Thread panicked");
73//! # Ok::<(), observable_property::PropertyError>(())
74//! ```
75//!
76//! ## RAII Subscriptions (Recommended)
77//!
78//! For automatic cleanup without manual unsubscribe calls, use RAII subscriptions:
79//!
80//! ```rust
81//! use observable_property::ObservableProperty;
82//! use std::sync::Arc;
83//!
84//! # fn main() -> Result<(), observable_property::PropertyError> {
85//! let property = ObservableProperty::new(0);
86//!
87//! {
88//!     // Create RAII subscription - automatically cleaned up when dropped
89//!     let _subscription = property.subscribe_with_subscription(Arc::new(|old, new| {
90//!         println!("Value changed: {} -> {}", old, new);
91//!     }))?;
92//!
93//!     property.set(42)?; // Prints: "Value changed: 0 -> 42"
94//!
95//!     // Subscription automatically unsubscribes when leaving this scope
96//! }
97//!
98//! // No observer active anymore
99//! property.set(100)?; // No output
100//! # Ok(())
101//! # }
102//! ```
103//!
104//! ## Filtered RAII Subscriptions
105//!
106//! Combine filtering with automatic cleanup for conditional monitoring:
107//!
108//! ```rust
109//! use observable_property::ObservableProperty;
110//! use std::sync::Arc;
111//!
112//! # fn main() -> Result<(), observable_property::PropertyError> {
113//! let temperature = ObservableProperty::new(20.0);
114//!
115//! {
116//!     // Monitor only significant temperature increases with automatic cleanup
117//!     let _heat_warning = temperature.subscribe_filtered_with_subscription(
118//!         Arc::new(|old, new| {
119//!             println!("🔥 Heat warning! {:.1}°C -> {:.1}°C", old, new);
120//!         }),
121//!         |old, new| new > old && (new - old) > 5.0
122//!     )?;
123//!
124//!     temperature.set(22.0)?; // No warning (only 2°C increase)
125//!     temperature.set(28.0)?; // Prints warning (6°C increase from 22°C)
126//!
127//!     // Subscription automatically cleaned up here
128//! }
129//!
130//! temperature.set(35.0)?; // No warning (subscription was cleaned up)
131//! # Ok(())
132//! # }
133//! ```
134//!
135//! ## Subscription Management Comparison
136//!
137//! ```rust
138//! use observable_property::ObservableProperty;
139//! use std::sync::Arc;
140//!
141//! # fn main() -> Result<(), observable_property::PropertyError> {
142//! let property = ObservableProperty::new(0);
143//! let observer = Arc::new(|old: &i32, new: &i32| {
144//!     println!("Value: {} -> {}", old, new);
145//! });
146//!
147//! // Method 1: Manual subscription management
148//! let observer_id = property.subscribe(observer.clone())?;
149//! property.set(42)?;
150//! property.unsubscribe(observer_id)?; // Manual cleanup required
151//!
152//! // Method 2: RAII subscription management (recommended)
153//! {
154//!     let _subscription = property.subscribe_with_subscription(observer)?;
155//!     property.set(100)?;
156//!     // Automatic cleanup when _subscription goes out of scope
157//! }
158//! # Ok(())
159//! # }
160//! ```
161//!
162//! ## Advanced RAII Patterns
163//!
164//! Comprehensive example showing various RAII subscription patterns:
165//!
166//! ```rust
167//! use observable_property::ObservableProperty;
168//! use std::sync::Arc;
169//!
170//! # fn main() -> Result<(), observable_property::PropertyError> {
171//! // System monitoring example
172//! let cpu_usage = ObservableProperty::new(25.0f64); // percentage
173//! let memory_usage = ObservableProperty::new(1024); // MB
174//! let active_connections = ObservableProperty::new(0u32);
175//!
176//! // Conditional monitoring based on system state
177//! let high_load_monitoring = cpu_usage.get()? > 50.0;
178//!
179//! if high_load_monitoring {
180//!     // Critical system monitoring - active only during high load
181//!     let _cpu_critical = cpu_usage.subscribe_filtered_with_subscription(
182//!         Arc::new(|old, new| {
183//!             println!("🚨 Critical CPU usage: {:.1}% -> {:.1}%", old, new);
184//!         }),
185//!         |_, new| *new > 80.0
186//!     )?;
187//!
188//!     let _memory_warning = memory_usage.subscribe_filtered_with_subscription(
189//!         Arc::new(|old, new| {
190//!             println!("⚠️ High memory usage: {}MB -> {}MB", old, new);
191//!         }),
192//!         |_, new| *new > 8192 // > 8GB
193//!     )?;
194//!
195//!     // Simulate system load changes
196//!     cpu_usage.set(85.0)?;     // Would trigger critical alert
197//!     memory_usage.set(9216)?;  // Would trigger memory warning
198//!     
199//!     // All monitoring automatically stops when exiting this block
200//! }
201//!
202//! // Connection monitoring with scoped lifetime
203//! {
204//!     let _connection_monitor = active_connections.subscribe_with_subscription(
205//!         Arc::new(|old, new| {
206//!             if new > old {
207//!                 println!("📈 New connections: {} -> {}", old, new);
208//!             } else if new < old {
209//!                 println!("📉 Connections closed: {} -> {}", old, new);
210//!             }
211//!         })
212//!     )?;
213//!
214//!     active_connections.set(5)?;  // Prints: "📈 New connections: 0 -> 5"
215//!     active_connections.set(3)?;  // Prints: "📉 Connections closed: 5 -> 3"
216//!     active_connections.set(8)?;  // Prints: "📈 New connections: 3 -> 8"
217//!
218//!     // Connection monitoring automatically stops here
219//! }
220//!
221//! // No monitoring active anymore
222//! cpu_usage.set(95.0)?;         // No output
223//! memory_usage.set(10240)?;     // No output  
224//! active_connections.set(15)?;  // No output
225//!
226//! println!("All monitoring automatically cleaned up!");
227//! # Ok(())
228//! # }
229//! ```
230
231use std::collections::HashMap;
232use std::fmt;
233use std::mem;
234use std::panic;
235use std::sync::{Arc, Mutex, RwLock};
236use std::thread;
237use std::time::{Duration, Instant};
238
239#[cfg(feature = "serde")]
240use serde::{Deserialize, Serialize};
241
242#[cfg(feature = "debug")]
243use backtrace::Backtrace;
244
245#[cfg(feature = "async")]
246use std::pin::Pin;
247
248#[cfg(feature = "async")]
249use std::task::{Context, Poll};
250
251/// Maximum number of background threads used for asynchronous observer notifications
252///
253/// This constant controls the degree of parallelism when using `set_async()` to notify
254/// observers. The observer list is divided into batches, with each batch running in
255/// its own background thread, up to this maximum number of threads.
256///
257/// # Rationale
258///
259/// - **Resource Control**: Prevents unbounded thread creation that could exhaust system resources
260/// - **Performance Balance**: Provides parallelism benefits without excessive context switching overhead  
261/// - **Scalability**: Ensures consistent behavior regardless of the number of observers
262/// - **System Responsiveness**: Limits thread contention on multi-core systems
263///
264/// # Implementation Details
265///
266/// When `set_async()` is called:
267/// 1. All observers are collected into a snapshot
268/// 2. Observers are divided into `MAX_THREADS` batches (or fewer if there are fewer observers)
269/// 3. Each batch executes in its own `thread::spawn()` call
270/// 4. Observers within each batch are executed sequentially
271///
272/// For example, with 100 observers and `MAX_THREADS = 4`:
273/// - Batch 1: Observers 1-25 (Thread 1)
274/// - Batch 2: Observers 26-50 (Thread 2)  
275/// - Batch 3: Observers 51-75 (Thread 3)
276/// - Batch 4: Observers 76-100 (Thread 4)
277///
278/// # Tuning Considerations
279///
280/// This value can be adjusted based on your application's needs:
281/// - **CPU-bound observers**: Higher values may improve throughput on multi-core systems
282/// - **I/O-bound observers**: Higher values can improve concurrency for network/disk operations
283/// - **Memory-constrained systems**: Lower values reduce thread overhead
284/// - **Real-time systems**: Lower values reduce scheduling unpredictability
285///
286/// # Thread Safety
287///
288/// This constant is used only during the batching calculation and does not affect
289/// the thread safety of the overall system.
290const MAX_THREADS: usize = 4;
291
292/// Maximum number of observers allowed per property instance
293///
294/// This limit prevents memory exhaustion from unbounded observer registration.
295/// Once this limit is reached, attempts to add more observers will fail with
296/// an `InvalidConfiguration` error.
297///
298/// # Rationale
299///
300/// - **Memory Protection**: Prevents unbounded memory growth from observer accumulation
301/// - **Resource Management**: Ensures predictable memory usage in long-running applications
302/// - **Early Detection**: Catches potential memory leaks from forgotten unsubscriptions
303/// - **System Stability**: Prevents out-of-memory conditions in constrained environments
304///
305/// # Tuning Considerations
306///
307/// This value can be adjusted based on your application's needs:
308/// - **High-frequency properties**: May need fewer observers to avoid notification overhead
309/// - **Low-frequency properties**: Can safely support more observers
310/// - **Memory-constrained systems**: Lower values prevent memory pressure
311/// - **Development/testing**: Higher values may be useful for comprehensive test coverage
312///
313/// # Default Value
314///
315/// The default of 10,000 observers provides generous headroom for most applications while
316/// still preventing pathological cases. In practice, most properties have fewer than 100
317/// observers.
318///
319/// # Example Scenarios
320///
321/// - **User sessions**: 1,000 concurrent users, each with 5 properties = ~5,000 observers
322/// - **IoT devices**: 5,000 devices, each with 1-2 observers = ~10,000 observers
323/// - **Monitoring system**: 100 metrics, each with 20 dashboard widgets = ~2,000 observers
324const MAX_OBSERVERS: usize = 10_000;
325
326/// Trait for implementing property value persistence
327///
328/// This trait allows custom persistence strategies for ObservableProperty values,
329/// enabling automatic save/load functionality to various storage backends like
330/// disk files, databases, cloud storage, etc.
331///
332/// # Examples
333///
334/// ```rust
335/// use observable_property::PropertyPersistence;
336/// use std::fs;
337///
338/// struct FilePersistence {
339///     path: String,
340/// }
341///
342/// impl PropertyPersistence for FilePersistence {
343///     type Value = String;
344///
345///     fn load(&self) -> Result<Self::Value, Box<dyn std::error::Error + Send + Sync>> {
346///         fs::read_to_string(&self.path)
347///             .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
348///     }
349///
350///     fn save(&self, value: &Self::Value) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
351///         fs::write(&self.path, value)
352///             .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
353///     }
354/// }
355/// ```
356pub trait PropertyPersistence: Send + Sync + 'static {
357    /// The type of value being persisted
358    type Value: Clone + Send + Sync + 'static;
359
360    /// Load the value from persistent storage
361    ///
362    /// # Returns
363    ///
364    /// Returns the loaded value or an error if loading fails
365    fn load(&self) -> Result<Self::Value, Box<dyn std::error::Error + Send + Sync>>;
366
367    /// Save the value to persistent storage
368    ///
369    /// # Arguments
370    ///
371    /// * `value` - The value to persist
372    ///
373    /// # Returns
374    ///
375    /// Returns `Ok(())` if successful, or an error if saving fails
376    fn save(&self, value: &Self::Value) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
377}
378
379/// Errors that can occur when working with ObservableProperty
380///
381/// # Note on Lock Poisoning
382///
383/// This implementation uses **graceful degradation** for poisoned locks. When a lock
384/// is poisoned (typically due to a panic in an observer or another thread), the
385/// library automatically recovers the inner value using [`PoisonError::into_inner()`](std::sync::PoisonError::into_inner).
386///
387/// This means:
388/// - **All operations continue to work** even after a lock is poisoned
389/// - No `ReadLockError`, `WriteLockError`, or `PoisonedLock` errors will occur in practice
390/// - The system remains operational and observers continue to function
391///
392/// The error variants are kept for backward compatibility and potential future use cases,
393/// but with the current implementation, poisoned locks are transparent to users.
394///
395/// # Production Benefit
396///
397/// This approach ensures maximum availability and resilience in production systems where
398/// a misbehaving observer shouldn't bring down the entire property system.
399#[derive(Debug, Clone)]
400pub enum PropertyError {
401    /// Failed to acquire a read lock on the property
402    ///
403    /// **Note**: With graceful degradation, this error is unlikely to occur in practice
404    /// as poisoned locks are automatically recovered.
405    ReadLockError {
406        /// Context describing what operation was being attempted
407        context: String,
408    },
409    /// Failed to acquire a write lock on the property
410    ///
411    /// **Note**: With graceful degradation, this error is unlikely to occur in practice
412    /// as poisoned locks are automatically recovered.
413    WriteLockError {
414        /// Context describing what operation was being attempted
415        context: String,
416    },
417    /// Attempted to unsubscribe an observer that doesn't exist
418    ObserverNotFound {
419        /// The ID of the observer that wasn't found
420        id: usize,
421    },
422    /// The property's lock has been poisoned due to a panic in another thread
423    ///
424    /// **Note**: With graceful degradation, this error will not occur in practice
425    /// as the implementation automatically recovers from poisoned locks.
426    PoisonedLock,
427    /// An observer function encountered an error during execution
428    ObserverError {
429        /// Description of what went wrong
430        reason: String,
431    },
432    /// The thread pool for async notifications is exhausted
433    ThreadPoolExhausted,
434    /// Invalid configuration was provided
435    InvalidConfiguration {
436        /// Description of the invalid configuration
437        reason: String,
438    },
439    /// Failed to persist the property value
440    PersistenceError {
441        /// Description of the persistence error
442        reason: String,
443    },
444    /// Attempted to undo when no history is available
445    NoHistory {
446        /// Description of the issue
447        reason: String,
448    },
449    /// Validation failed for the provided value
450    ValidationError {
451        /// Description of why validation failed
452        reason: String,
453    },
454}
455
456impl fmt::Display for PropertyError {
457    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458        match self {
459            PropertyError::ReadLockError { context } => {
460                write!(f, "Failed to acquire read lock: {}", context)
461            }
462            PropertyError::WriteLockError { context } => {
463                write!(f, "Failed to acquire write lock: {}", context)
464            }
465            PropertyError::ObserverNotFound { id } => {
466                write!(f, "Observer with ID {} not found", id)
467            }
468            PropertyError::PoisonedLock => {
469                write!(
470                    f,
471                    "Property is in a poisoned state due to a panic in another thread"
472                )
473            }
474            PropertyError::ObserverError { reason } => {
475                write!(f, "Observer execution failed: {}", reason)
476            }
477            PropertyError::ThreadPoolExhausted => {
478                write!(f, "Thread pool is exhausted and cannot spawn more observers")
479            }
480            PropertyError::InvalidConfiguration { reason } => {
481                write!(f, "Invalid configuration: {}", reason)
482            }
483            PropertyError::PersistenceError { reason } => {
484                write!(f, "Persistence failed: {}", reason)
485            }
486            PropertyError::NoHistory { reason } => {
487                write!(f, "No history available: {}", reason)
488            }
489            PropertyError::ValidationError { reason } => {
490                write!(f, "Validation failed: {}", reason)
491            }
492        }
493    }
494}
495
496impl std::error::Error for PropertyError {}
497
498/// Function type for observers that get called when property values change
499pub type Observer<T> = Arc<dyn Fn(&T, &T) + Send + Sync>;
500
501/// Represents either a strong or weak reference to an observer
502///
503/// This enum allows the property to manage both strongly-held observers (which keep
504/// the observer alive) and weakly-held observers (which allow automatic cleanup when
505/// the observer is dropped elsewhere).
506enum ObserverRef<T: Clone + Send + Sync + 'static> {
507    /// Strong reference to an observer (keeps the observer alive)
508    Strong(Observer<T>),
509    /// Weak reference to an observer (automatically cleaned up when dropped)
510    Weak(std::sync::Weak<dyn Fn(&T, &T) + Send + Sync>),
511}
512
513impl<T: Clone + Send + Sync + 'static> ObserverRef<T> {
514    /// Attempts to get a callable observer from this reference
515    ///
516    /// For strong references, always returns Some. For weak references, attempts
517    /// to upgrade and returns None if the observer has been dropped.
518    fn try_call(&self) -> Option<Observer<T>> {
519        match self {
520            ObserverRef::Strong(arc) => Some(arc.clone()),
521            ObserverRef::Weak(weak) => weak.upgrade(),
522        }
523    }
524}
525
526/// Unique identifier for registered observers
527pub type ObserverId = usize;
528
529/// Performance metrics for an observable property
530///
531/// This struct provides insight into property usage patterns and observer
532/// notification performance, useful for debugging and performance optimization.
533///
534/// # Examples
535///
536/// ```rust
537/// use observable_property::ObservableProperty;
538/// use std::sync::Arc;
539///
540/// # fn main() -> Result<(), observable_property::PropertyError> {
541/// let property = ObservableProperty::new(0);
542///
543/// property.subscribe(Arc::new(|old, new| {
544///     println!("Value changed: {} -> {}", old, new);
545/// }))?;
546///
547/// property.set(42)?;
548/// property.set(100)?;
549///
550/// let metrics = property.get_metrics()?;
551/// println!("Total changes: {}", metrics.total_changes);
552/// println!("Observer calls: {}", metrics.observer_calls);
553/// println!("Avg notification time: {:?}", metrics.avg_notification_time);
554/// # Ok(())
555/// # }
556/// ```
557#[derive(Debug, Clone)]
558pub struct PropertyMetrics {
559    /// Total number of times the property value has changed
560    pub total_changes: usize,
561    /// Total number of observer calls (notification events)
562    pub observer_calls: usize,
563    /// Average time taken to notify all observers per change
564    pub avg_notification_time: Duration,
565}
566
567/// A RAII guard for an observer subscription that automatically unsubscribes when dropped
568///
569/// This struct provides automatic cleanup for observer subscriptions using RAII (Resource
570/// Acquisition Is Initialization) pattern. When a `Subscription` goes out of scope, its
571/// `Drop` implementation automatically removes the associated observer from the property.
572///
573/// This eliminates the need for manual `unsubscribe()` calls and helps prevent resource
574/// leaks in scenarios where observers might otherwise be forgotten.
575///
576/// # Type Requirements
577///
578/// The generic type `T` must implement the same traits as `ObservableProperty<T>`:
579/// - `Clone`: Required for observer notifications
580/// - `Send`: Required for transferring between threads  
581/// - `Sync`: Required for concurrent access from multiple threads
582/// - `'static`: Required for observer callbacks that may outlive the original scope
583///
584/// # Examples
585///
586/// ## Basic RAII Subscription
587///
588/// ```rust
589/// use observable_property::ObservableProperty;
590/// use std::sync::Arc;
591///
592/// # fn main() -> Result<(), observable_property::PropertyError> {
593/// let property = ObservableProperty::new(0);
594///
595/// {
596///     // Create subscription - observer is automatically registered
597///     let _subscription = property.subscribe_with_subscription(Arc::new(|old, new| {
598///         println!("Value changed: {} -> {}", old, new);
599///     }))?;
600///
601///     property.set(42)?; // Observer is called: "Value changed: 0 -> 42"
602///
603///     // When _subscription goes out of scope here, observer is automatically removed
604/// }
605///
606/// property.set(100)?; // No observer output - subscription was automatically cleaned up
607/// # Ok(())
608/// # }
609/// ```
610///
611/// ## Cross-Thread Subscription Management
612///
613/// ```rust
614/// use observable_property::ObservableProperty;
615/// use std::sync::Arc;
616/// use std::thread;
617///
618/// # fn main() -> Result<(), observable_property::PropertyError> {
619/// let property = Arc::new(ObservableProperty::new(0));
620/// let property_clone = property.clone();
621///
622/// // Create subscription in main thread
623/// let subscription = property.subscribe_with_subscription(Arc::new(|old, new| {
624///     println!("Observed: {} -> {}", old, new);
625/// }))?;
626///
627/// // Move subscription to another thread for cleanup
628/// let handle = thread::spawn(move || {
629///     // Subscription is still active here
630///     let _ = property_clone.set(42); // Will trigger observer
631///     
632///     // When subscription is dropped here (end of thread), observer is cleaned up
633///     drop(subscription);
634/// });
635///
636/// handle.join().unwrap();
637/// 
638/// // Observer is no longer active
639/// property.set(100)?; // No output
640/// # Ok(())
641/// # }
642/// ```
643///
644/// ## Conditional Scoped Subscriptions
645///
646/// ```rust
647/// use observable_property::ObservableProperty;
648/// use std::sync::Arc;
649///
650/// # fn main() -> Result<(), observable_property::PropertyError> {
651/// let counter = ObservableProperty::new(0);
652/// let debug_mode = true;
653///
654/// if debug_mode {
655///     let _debug_subscription = counter.subscribe_with_subscription(Arc::new(|old, new| {
656///         println!("Debug: counter {} -> {}", old, new);
657///     }))?;
658///     
659///     counter.set(1)?; // Prints debug info
660///     counter.set(2)?; // Prints debug info
661///     
662///     // Debug subscription automatically cleaned up when exiting if block
663/// }
664///
665/// counter.set(3)?; // No debug output (subscription was cleaned up)
666/// # Ok(())
667/// # }
668/// ```
669///
670/// # Thread Safety
671///
672/// Like `ObservableProperty` itself, `Subscription` is thread-safe. It can be safely
673/// sent between threads and the automatic cleanup will work correctly even if the
674/// subscription is dropped from a different thread than where it was created.
675pub struct Subscription<T: Clone + Send + Sync + 'static> {
676    inner: Arc<RwLock<InnerProperty<T>>>,
677    id: ObserverId,
678}
679
680impl<T: Clone + Send + Sync + 'static> std::fmt::Debug for Subscription<T> {
681    /// Debug implementation that shows the subscription ID without exposing internals
682    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
683        f.debug_struct("Subscription")
684            .field("id", &self.id)
685            .field("inner", &"[ObservableProperty]")
686            .finish()
687    }
688}
689
690impl<T: Clone + Send + Sync + 'static> Drop for Subscription<T> {
691    /// Automatically removes the associated observer when the subscription is dropped
692    ///
693    /// This implementation provides automatic cleanup by removing the observer
694    /// from the property's observer list when the `Subscription` goes out of scope.
695    ///
696    /// # Error Handling
697    ///
698    /// If the property's lock is poisoned or inaccessible during cleanup, the error
699    /// is silently ignored using the `let _ = ...` pattern. This is intentional
700    /// because:
701    /// 1. Drop implementations should not panic
702    /// 2. If the property is poisoned, it's likely unusable anyway
703    /// 3. There's no meaningful way to handle cleanup errors in a destructor
704    ///
705    /// # Thread Safety
706    ///
707    /// This method is safe to call from any thread, even if the subscription
708    /// was created on a different thread.
709    fn drop(&mut self) {
710        // Graceful degradation: always attempt to clean up, even from poisoned locks
711        match self.inner.write() {
712            Ok(mut guard) => {
713                guard.observers.remove(&self.id);
714            }
715            Err(poisoned) => {
716                // Recover from poisoned lock to ensure cleanup happens
717                let mut guard = poisoned.into_inner();
718                guard.observers.remove(&self.id);
719            }
720        }
721    }
722}
723
724/// A custom Stream trait for async iteration over values
725///
726/// This trait provides a simple async iteration interface similar to the standard
727/// `futures::Stream` trait, but implemented using only standard library primitives.
728///
729/// # Note
730///
731/// This is a custom trait and is not compatible with the futures ecosystem's
732/// `Stream` trait. For ecosystem compatibility, consider using the futures-core crate.
733#[cfg(feature = "async")]
734pub trait Stream {
735    /// The type of items yielded by this stream
736    type Item;
737
738    /// Attempts to pull out the next value of this stream
739    ///
740    /// Returns:
741    /// - `Poll::Ready(Some(item))` if a value is ready
742    /// - `Poll::Pending` if no value is ready yet (will wake the task later)
743    /// - `Poll::Ready(None)` if the stream has ended
744    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>>;
745}
746
747/// A thread-safe observable property that notifies observers when its value changes
748///
749/// This type wraps a value of type `T` and allows multiple observers to be notified
750/// whenever the value is modified. All operations are thread-safe and can be called
751/// from multiple threads concurrently.
752///
753/// # Type Requirements
754///
755/// The generic type `T` must implement:
756/// - `Clone`: Required for returning values and passing them to observers
757/// - `Send`: Required for transferring between threads
758/// - `Sync`: Required for concurrent access from multiple threads  
759/// - `'static`: Required for observer callbacks that may outlive the original scope
760///
761/// # Examples
762///
763/// ```rust
764/// use observable_property::ObservableProperty;
765/// use std::sync::Arc;
766///
767/// let property = ObservableProperty::new("initial".to_string());
768///
769/// let observer_id = property.subscribe(Arc::new(|old, new| {
770///     println!("Changed from '{}' to '{}'", old, new);
771/// })).map_err(|e| {
772///     eprintln!("Failed to subscribe: {}", e);
773///     e
774/// })?;
775///
776/// property.set("updated".to_string()).map_err(|e| {
777///     eprintln!("Failed to set value: {}", e);
778///     e
779/// })?; // Prints: Changed from 'initial' to 'updated'
780///
781/// property.unsubscribe(observer_id).map_err(|e| {
782///     eprintln!("Failed to unsubscribe: {}", e);
783///     e
784/// })?;
785/// # Ok::<(), observable_property::PropertyError>(())
786/// ```
787pub struct ObservableProperty<T>
788where
789    T: Clone + Send + Sync + 'static,
790{
791    inner: Arc<RwLock<InnerProperty<T>>>,
792    max_threads: usize,
793    max_observers: usize,
794}
795
796struct InnerProperty<T>
797where
798    T: Clone + Send + Sync + 'static,
799{
800    value: T,
801    observers: HashMap<ObserverId, ObserverRef<T>>,
802    next_id: ObserverId,
803    history: Option<Vec<T>>,
804    history_size: usize,
805    // Metrics tracking
806    total_changes: usize,
807    observer_calls: usize,
808    notification_times: Vec<Duration>,
809    // Debug tracking
810    #[cfg(feature = "debug")]
811    debug_logging_enabled: bool,
812    #[cfg(feature = "debug")]
813    change_logs: Vec<ChangeLog>,
814    // Change coalescing
815    batch_depth: usize,
816    batch_initial_value: Option<T>,
817    // Custom equality function
818    eq_fn: Option<Arc<dyn Fn(&T, &T) -> bool + Send + Sync>>,
819    // Validator function
820    validator: Option<Arc<dyn Fn(&T) -> Result<(), String> + Send + Sync>>,
821    // Event sourcing
822    event_log: Option<Vec<PropertyEvent<T>>>,
823    event_log_size: usize,
824}
825
826/// Information about a property change with stack trace
827#[cfg(feature = "debug")]
828#[derive(Clone)]
829struct ChangeLog {
830    timestamp: Instant,
831    old_value_repr: String,
832    new_value_repr: String,
833    backtrace: String,
834    thread_id: String,
835}
836
837/// Represents a single change event in the property's history
838///
839/// This struct captures all details of a value change for event sourcing,
840/// enabling time-travel debugging, audit logs, and event replay capabilities.
841#[derive(Clone, Debug)]
842pub struct PropertyEvent<T: Clone> {
843    /// When the change occurred
844    pub timestamp: Instant,
845    /// The value before the change
846    pub old_value: T,
847    /// The value after the change
848    pub new_value: T,
849    /// Sequential event number (starts at 0 for first change)
850    pub event_number: usize,
851    /// Thread that triggered the change (for debugging)
852    pub thread_id: String,
853}
854
855impl<T: Clone + Send + Sync + 'static> ObservableProperty<T> {
856    /// Creates a new observable property with the given initial value
857    ///
858    /// # Arguments
859    ///
860    /// * `initial_value` - The starting value for this property
861    ///
862    /// # Examples
863    ///
864    /// ```rust
865    /// use observable_property::ObservableProperty;
866    ///
867    /// let property = ObservableProperty::new(42);
868    /// match property.get() {
869    ///     Ok(value) => assert_eq!(value, 42),
870    ///     Err(e) => eprintln!("Failed to get property value: {}", e),
871    /// }
872    /// ```
873    pub fn new(initial_value: T) -> Self {
874        Self {
875            inner: Arc::new(RwLock::new(InnerProperty {
876                value: initial_value,
877                observers: HashMap::new(),
878                next_id: 0,
879                history: None,
880                history_size: 0,
881                total_changes: 0,
882                observer_calls: 0,
883                notification_times: Vec::new(),
884                #[cfg(feature = "debug")]
885                debug_logging_enabled: false,
886                #[cfg(feature = "debug")]
887                change_logs: Vec::new(),
888                batch_depth: 0,
889                batch_initial_value: None,
890                eq_fn: None,
891                validator: None,
892                event_log: None,
893                event_log_size: 0,
894            })),
895            max_threads: MAX_THREADS,
896            max_observers: MAX_OBSERVERS,
897        }
898    }
899
900    /// Creates a new observable property with custom equality comparison
901    ///
902    /// This constructor allows you to define custom logic for determining when two values
903    /// are considered "equal". Observers are only notified when the equality function
904    /// returns `false` (i.e., when the values are considered different).
905    ///
906    /// This is particularly useful for:
907    /// - Float comparisons with epsilon tolerance (avoiding floating-point precision issues)
908    /// - Case-insensitive string comparisons
909    /// - Semantic equality that differs from structural equality
910    /// - Preventing spurious notifications for "equal enough" values
911    ///
912    /// # Arguments
913    ///
914    /// * `initial_value` - The starting value for this property
915    /// * `eq_fn` - A function that returns `true` if two values should be considered equal,
916    ///             `false` if they should be considered different (which triggers notifications)
917    ///
918    /// # Examples
919    ///
920    /// ## Float comparison with epsilon
921    ///
922    /// ```rust
923    /// use observable_property::ObservableProperty;
924    /// use std::sync::Arc;
925    ///
926    /// # fn main() -> Result<(), observable_property::PropertyError> {
927    /// // Create a property that only notifies if the difference is > 0.001
928    /// let temperature = ObservableProperty::with_equality(
929    ///     20.0_f64,
930    ///     |a, b| (a - b).abs() < 0.001
931    /// );
932    ///
933    /// let _sub = temperature.subscribe_with_subscription(Arc::new(|old, new| {
934    ///     println!("Significant temperature change: {} -> {}", old, new);
935    /// }))?;
936    ///
937    /// temperature.set(20.0005)?;  // No notification (within epsilon)
938    /// temperature.set(20.5)?;     // Notification triggered
939    /// # Ok(())
940    /// # }
941    /// ```
942    ///
943    /// ## Case-insensitive string comparison
944    ///
945    /// ```rust
946    /// use observable_property::ObservableProperty;
947    /// use std::sync::Arc;
948    ///
949    /// # fn main() -> Result<(), observable_property::PropertyError> {
950    /// // Only notify on case-insensitive changes
951    /// let username = ObservableProperty::with_equality(
952    ///     "Alice".to_string(),
953    ///     |a, b| a.to_lowercase() == b.to_lowercase()
954    /// );
955    ///
956    /// let _sub = username.subscribe_with_subscription(Arc::new(|old, new| {
957    ///     println!("Username changed: {} -> {}", old, new);
958    /// }))?;
959    ///
960    /// username.set("alice".to_string())?;  // No notification
961    /// username.set("ALICE".to_string())?;  // No notification
962    /// username.set("Bob".to_string())?;    // Notification triggered
963    /// # Ok(())
964    /// # }
965    /// ```
966    ///
967    /// ## Semantic equality for complex types
968    ///
969    /// ```rust
970    /// use observable_property::ObservableProperty;
971    /// use std::sync::Arc;
972    ///
973    /// # fn main() -> Result<(), observable_property::PropertyError> {
974    /// #[derive(Clone)]
975    /// struct Config {
976    ///     host: String,
977    ///     port: u16,
978    ///     timeout_ms: u64,
979    /// }
980    ///
981    /// // Only notify if critical fields change
982    /// let config = ObservableProperty::with_equality(
983    ///     Config { host: "localhost".to_string(), port: 8080, timeout_ms: 1000 },
984    ///     |a, b| a.host == b.host && a.port == b.port  // Ignore timeout changes
985    /// );
986    ///
987    /// let _sub = config.subscribe_with_subscription(Arc::new(|old, new| {
988    ///     println!("Critical config changed: {}:{} -> {}:{}",
989    ///         old.host, old.port, new.host, new.port);
990    /// }))?;
991    ///
992    /// config.modify(|c| c.timeout_ms = 2000)?;  // No notification (timeout ignored)
993    /// config.modify(|c| c.port = 9090)?;        // Notification triggered
994    /// # Ok(())
995    /// # }
996    /// ```
997    ///
998    /// # Performance Considerations
999    ///
1000    /// The equality function is called every time `set()` or `set_async()` is called,
1001    /// so it should be relatively fast. For expensive comparisons, consider using
1002    /// filtered observers instead.
1003    ///
1004    /// # Thread Safety
1005    ///
1006    /// The equality function must be `Send + Sync + 'static` as it may be called from
1007    /// any thread that modifies the property.
1008    pub fn with_equality<F>(initial_value: T, eq_fn: F) -> Self
1009    where
1010        F: Fn(&T, &T) -> bool + Send + Sync + 'static,
1011    {
1012        Self {
1013            inner: Arc::new(RwLock::new(InnerProperty {
1014                value: initial_value,
1015                observers: HashMap::new(),
1016                next_id: 0,
1017                history: None,
1018                history_size: 0,
1019                total_changes: 0,
1020                observer_calls: 0,
1021                notification_times: Vec::new(),
1022                #[cfg(feature = "debug")]
1023                debug_logging_enabled: false,
1024                #[cfg(feature = "debug")]
1025                change_logs: Vec::new(),
1026                batch_depth: 0,
1027                batch_initial_value: None,
1028                eq_fn: Some(Arc::new(eq_fn)),
1029                validator: None,
1030                event_log: None,
1031                event_log_size: 0,
1032            })),
1033            max_threads: MAX_THREADS,
1034            max_observers: MAX_OBSERVERS,
1035        }
1036    }
1037
1038    /// Creates a new observable property with value validation
1039    ///
1040    /// This constructor enables value validation for the property. Any attempt to set
1041    /// a value that fails validation will be rejected with a `ValidationError`. This
1042    /// ensures the property always contains valid data according to your business rules.
1043    ///
1044    /// The validator function is called:
1045    /// - When the property is created (to validate the initial value)
1046    /// - Every time `set()` or `set_async()` is called (before the value is changed)
1047    /// - When `modify()` is called (after the modification function runs)
1048    ///
1049    /// If validation fails, the property value remains unchanged and an error is returned.
1050    ///
1051    /// # Arguments
1052    ///
1053    /// * `initial_value` - The starting value for this property (must pass validation)
1054    /// * `validator` - A function that validates values, returning `Ok(())` for valid values
1055    ///                 or `Err(String)` with an error message for invalid values
1056    ///
1057    /// # Returns
1058    ///
1059    /// * `Ok(Self)` - If the initial value passes validation
1060    /// * `Err(PropertyError::ValidationError)` - If the initial value fails validation
1061    ///
1062    /// # Use Cases
1063    ///
1064    /// ## Age Validation
1065    ///
1066    /// ```rust
1067    /// use observable_property::ObservableProperty;
1068    /// use std::sync::Arc;
1069    ///
1070    /// # fn main() -> Result<(), observable_property::PropertyError> {
1071    /// // Only allow ages between 0 and 150
1072    /// let age = ObservableProperty::with_validator(
1073    ///     25,
1074    ///     |age| {
1075    ///         if *age <= 150 {
1076    ///             Ok(())
1077    ///         } else {
1078    ///             Err(format!("Age must be between 0 and 150, got {}", age))
1079    ///         }
1080    ///     }
1081    /// )?;
1082    ///
1083    /// let _sub = age.subscribe_with_subscription(Arc::new(|old, new| {
1084    ///     println!("Age changed: {} -> {}", old, new);
1085    /// }))?;
1086    ///
1087    /// age.set(30)?;  // ✓ Valid - prints: "Age changed: 25 -> 30"
1088    ///
1089    /// // Attempt to set invalid age
1090///     match age.set(200) {
1091///         Err(e) => println!("Validation failed: {}", e), // Prints validation error
1092    ///         Ok(_) => unreachable!(),
1093    ///     }
1094    ///
1095    /// assert_eq!(age.get()?, 30); // Value unchanged after failed validation
1096    /// # Ok(())
1097    /// # }
1098    /// ```
1099    ///
1100    /// ## Email Format Validation
1101    ///
1102    /// ```rust
1103    /// use observable_property::ObservableProperty;
1104    ///
1105    /// # fn main() -> Result<(), observable_property::PropertyError> {
1106    /// // Validate email format (simplified)
1107    /// let email = ObservableProperty::with_validator(
1108    ///     "user@example.com".to_string(),
1109    ///     |email| {
1110    ///         if email.contains('@') && email.contains('.') {
1111    ///             Ok(())
1112    ///         } else {
1113    ///             Err(format!("Invalid email format: {}", email))
1114    ///         }
1115    ///     }
1116    /// )?;
1117    ///
1118    /// email.set("valid@email.com".to_string())?;  // ✓ Valid
1119    ///
1120    /// match email.set("invalid-email".to_string()) {
1121    ///     Err(e) => println!("{}", e), // Prints: "Validation failed: Invalid email format: invalid-email"
1122    ///     Ok(_) => unreachable!(),
1123    /// }
1124    /// # Ok(())
1125    /// # }
1126    /// ```
1127    ///
1128    /// ## Range Validation for Floats
1129    ///
1130    /// ```rust
1131    /// use observable_property::ObservableProperty;
1132    ///
1133    /// # fn main() -> Result<(), observable_property::PropertyError> {
1134    /// // Temperature must be between absolute zero and practical maximum
1135    /// let temperature = ObservableProperty::with_validator(
1136    ///     20.0_f64,
1137    ///     |temp| {
1138    ///         if *temp >= -273.15 && *temp <= 1000.0 {
1139    ///             Ok(())
1140    ///         } else {
1141    ///             Err(format!("Temperature {} is out of valid range [-273.15, 1000.0]", temp))
1142    ///         }
1143    ///     }
1144    /// )?;
1145    ///
1146    /// temperature.set(100.0)?;   // ✓ Valid
1147    /// temperature.set(-300.0).unwrap_err();  // ✗ Fails validation
1148    /// # Ok(())
1149    /// # }
1150    /// ```
1151    ///
1152    /// ## Multiple Validation Rules
1153    ///
1154    /// ```rust
1155    /// use observable_property::ObservableProperty;
1156    ///
1157    /// # fn main() -> Result<(), observable_property::PropertyError> {
1158    /// // Username validation with multiple rules
1159    /// let username = ObservableProperty::with_validator(
1160    ///     "alice".to_string(),
1161    ///     |name| {
1162    ///         if name.is_empty() {
1163    ///             return Err("Username cannot be empty".to_string());
1164    ///         }
1165    ///         if name.len() < 3 {
1166    ///             return Err(format!("Username must be at least 3 characters, got {}", name.len()));
1167    ///         }
1168    ///         if name.len() > 20 {
1169    ///             return Err(format!("Username must be at most 20 characters, got {}", name.len()));
1170    ///         }
1171    ///         if !name.chars().all(|c| c.is_alphanumeric() || c == '_') {
1172    ///             return Err("Username can only contain letters, numbers, and underscores".to_string());
1173    ///         }
1174    ///         Ok(())
1175    ///     }
1176    /// )?;
1177    ///
1178    /// username.set("bob".to_string())?;      // ✓ Valid
1179    /// username.set("ab".to_string()).unwrap_err();   // ✗ Too short
1180    /// username.set("user@123".to_string()).unwrap_err(); // ✗ Invalid characters
1181    /// # Ok(())
1182    /// # }
1183    /// ```
1184    ///
1185    /// ## Rejecting Invalid Initial Values
1186    ///
1187    /// ```rust
1188    /// use observable_property::ObservableProperty;
1189    ///
1190    /// # fn main() {
1191    /// // Attempt to create with invalid initial value
1192    /// let result = ObservableProperty::with_validator(
1193    ///     200,
1194    ///     |age| {
1195    ///         if *age <= 150 {
1196    ///             Ok(())
1197    ///         } else {
1198    ///             Err(format!("Age must be at most 150, got {}", age))
1199    ///         }
1200    ///     }
1201    /// );
1202    ///
1203    /// match result {
1204    ///     Err(e) => println!("Failed to create property: {}", e),
1205    ///     Ok(_) => unreachable!(),
1206    /// }
1207    /// # }
1208    /// ```
1209    ///
1210    /// # Performance Considerations
1211    ///
1212    /// The validator function is called on every `set()` or `set_async()` operation,
1213    /// so it should be relatively fast. For expensive validations, consider:
1214    /// - Caching validation results if the same value is set multiple times
1215    /// - Using async validation patterns outside of the property setter
1216    /// - Implementing early-exit validation logic (check cheapest rules first)
1217    ///
1218    /// # Thread Safety
1219    ///
1220    /// The validator function must be `Send + Sync + 'static` as it may be called from
1221    /// any thread that modifies the property. Ensure your validation logic is thread-safe.
1222    ///
1223    /// # Combining with Other Features
1224    ///
1225    /// Validation works alongside other property features:
1226    /// - **Custom equality**: Validation runs before equality checking
1227    /// - **History tracking**: Only valid values are stored in history
1228    /// - **Observers**: Observers only fire when validation succeeds and values differ
1229    /// - **Batching**: Validation occurs when batch is committed, not during batch
1230    pub fn with_validator<F>(
1231        initial_value: T,
1232        validator: F,
1233    ) -> Result<Self, PropertyError>
1234    where
1235        F: Fn(&T) -> Result<(), String> + Send + Sync + 'static,
1236    {
1237        // Validate the initial value
1238        validator(&initial_value).map_err(|reason| PropertyError::ValidationError { reason })?;
1239
1240        Ok(Self {
1241            inner: Arc::new(RwLock::new(InnerProperty {
1242                value: initial_value,
1243                observers: HashMap::new(),
1244                next_id: 0,
1245                history: None,
1246                history_size: 0,
1247                total_changes: 0,
1248                observer_calls: 0,
1249                notification_times: Vec::new(),
1250                #[cfg(feature = "debug")]
1251                debug_logging_enabled: false,
1252                #[cfg(feature = "debug")]
1253                change_logs: Vec::new(),
1254                batch_depth: 0,
1255                batch_initial_value: None,
1256                eq_fn: None,
1257                validator: Some(Arc::new(validator)),
1258                event_log: None,
1259                event_log_size: 0,
1260            })),
1261            max_threads: MAX_THREADS,
1262            max_observers: MAX_OBSERVERS,
1263        })
1264    }
1265
1266    /// Creates a new observable property with a custom maximum thread count for async notifications
1267    ///
1268    /// This constructor allows you to customize the maximum number of threads used for
1269    /// asynchronous observer notifications via `set_async()`. This is useful for tuning
1270    /// performance based on your specific use case and system constraints.
1271    ///
1272    /// # Arguments
1273    ///
1274    /// * `initial_value` - The starting value for this property
1275    /// * `max_threads` - Maximum number of threads to use for async notifications.
1276    ///   If 0 is provided, defaults to 4.
1277    ///
1278    /// # Thread Pool Behavior
1279    ///
1280    /// When `set_async()` is called, observers are divided into batches and each batch
1281    /// runs in its own thread, up to the specified maximum. For example:
1282    /// - With 100 observers and `max_threads = 4`: 4 threads with ~25 observers each
1283    /// - With 10 observers and `max_threads = 8`: 10 threads with 1 observer each
1284    /// - With 2 observers and `max_threads = 4`: 2 threads with 1 observer each
1285    ///
1286    /// # Use Cases
1287    ///
1288    /// ## High-Throughput Systems
1289    /// ```rust
1290    /// use observable_property::ObservableProperty;
1291    ///
1292    /// // For systems with many CPU cores and CPU-bound observers
1293    /// let property = ObservableProperty::with_max_threads(0, 8);
1294    /// ```
1295    ///
1296    /// ## Resource-Constrained Systems
1297    /// ```rust
1298    /// use observable_property::ObservableProperty;
1299    ///
1300    /// // For embedded systems or memory-constrained environments
1301    /// let property = ObservableProperty::with_max_threads(42, 1);
1302    /// ```
1303    ///
1304    /// ## I/O-Heavy Observers
1305    /// ```rust
1306    /// use observable_property::ObservableProperty;
1307    ///
1308    /// // For observers that do network/database operations
1309    /// let property = ObservableProperty::with_max_threads("data".to_string(), 16);
1310    /// ```
1311    ///
1312    /// # Performance Considerations
1313    ///
1314    /// - **Higher values**: Better parallelism but more thread overhead and memory usage
1315    /// - **Lower values**: Less overhead but potentially slower async notifications
1316    /// - **Optimal range**: Typically between 1 and 2x the number of CPU cores
1317    /// - **Zero value**: Automatically uses the default value (4)
1318    ///
1319    /// # Thread Safety
1320    ///
1321    /// This setting only affects async notifications (`set_async()`). Synchronous
1322    /// operations (`set()`) always execute observers sequentially regardless of this setting.
1323    ///
1324    /// # Examples
1325    ///
1326    /// ```rust
1327    /// use observable_property::ObservableProperty;
1328    /// use std::sync::Arc;
1329    ///
1330    /// // Create property with custom thread pool size
1331    /// let property = ObservableProperty::with_max_threads(42, 2);
1332    ///
1333    /// // Subscribe observers as usual
1334    /// let _subscription = property.subscribe_with_subscription(Arc::new(|old, new| {
1335    ///     println!("Value changed: {} -> {}", old, new);
1336    /// })).expect("Failed to create subscription");
1337    ///
1338    /// // Async notifications will use at most 2 threads
1339    /// property.set_async(100).expect("Failed to set value asynchronously");
1340    /// ```
1341    pub fn with_max_threads(initial_value: T, max_threads: usize) -> Self {
1342        let max_threads = if max_threads == 0 {
1343            MAX_THREADS
1344        } else {
1345            max_threads
1346        };
1347        Self {
1348            inner: Arc::new(RwLock::new(InnerProperty {
1349                value: initial_value,
1350                observers: HashMap::new(),
1351                next_id: 0,
1352                history: None,
1353                history_size: 0,
1354                total_changes: 0,
1355                observer_calls: 0,
1356                notification_times: Vec::new(),
1357                #[cfg(feature = "debug")]
1358                debug_logging_enabled: false,
1359                #[cfg(feature = "debug")]
1360                change_logs: Vec::new(),
1361                batch_depth: 0,
1362                batch_initial_value: None,
1363                eq_fn: None,
1364                validator: None,
1365                event_log: None,
1366                event_log_size: 0,
1367            })),
1368            max_threads,
1369            max_observers: MAX_OBSERVERS,
1370        }
1371    }
1372
1373    /// Creates a new observable property with automatic persistence
1374    ///
1375    /// This constructor creates a property that automatically saves its value to persistent
1376    /// storage whenever it changes. It attempts to load the initial value from storage, falling
1377    /// back to the provided `initial_value` if loading fails.
1378    ///
1379    /// # Arguments
1380    ///
1381    /// * `initial_value` - The value to use if loading from persistence fails
1382    /// * `persistence` - An implementation of `PropertyPersistence` that handles save/load operations
1383    ///
1384    /// # Behavior
1385    ///
1386    /// 1. Attempts to load the initial value from `persistence.load()`
1387    /// 2. If loading fails, uses the provided `initial_value`
1388    /// 3. Sets up an internal observer that automatically calls `persistence.save()` on every value change
1389    /// 4. Returns the configured property
1390    ///
1391    /// # Error Handling
1392    ///
1393    /// - Load failures are logged and the provided `initial_value` is used instead
1394    /// - Save failures during subsequent updates will be logged but won't prevent the update
1395    /// - The property continues to operate normally even if persistence operations fail
1396    ///
1397    /// # Type Requirements
1398    ///
1399    /// The persistence handler's `Value` type must match the property's type `T`.
1400    ///
1401    /// # Examples
1402    ///
1403    /// ## File-based Persistence
1404    ///
1405    /// ```rust,no_run
1406    /// use observable_property::{ObservableProperty, PropertyPersistence};
1407    /// use std::fs;
1408    ///
1409    /// struct FilePersistence {
1410    ///     path: String,
1411    /// }
1412    ///
1413    /// impl PropertyPersistence for FilePersistence {
1414    ///     type Value = String;
1415    ///
1416    ///     fn load(&self) -> Result<Self::Value, Box<dyn std::error::Error + Send + Sync>> {
1417    ///         fs::read_to_string(&self.path)
1418    ///             .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
1419    ///     }
1420    ///
1421    ///     fn save(&self, value: &Self::Value) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1422    ///         fs::write(&self.path, value)
1423    ///             .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
1424    ///     }
1425    /// }
1426    ///
1427    /// // Create a property that auto-saves to a file
1428    /// let property = ObservableProperty::with_persistence(
1429    ///     "default_value".to_string(),
1430    ///     FilePersistence { path: "./data.txt".to_string() }
1431    /// );
1432    ///
1433    /// // Value changes are automatically saved to disk
1434    /// property.set("new_value".to_string())
1435    ///     .expect("Failed to set value");
1436    /// ```
1437    ///
1438    /// ## JSON Database Persistence
1439    ///
1440    /// ```rust,no_run
1441    /// # // This example requires serde and serde_json dependencies
1442    /// use observable_property::{ObservableProperty, PropertyPersistence};
1443    /// # /*
1444    /// use serde::{Serialize, Deserialize};
1445    /// # */
1446    /// use std::fs;
1447    ///
1448    /// # /*
1449    /// #[derive(Clone, Serialize, Deserialize)]
1450    /// # */
1451    /// # #[derive(Clone)]
1452    /// struct UserPreferences {
1453    ///     theme: String,
1454    ///     font_size: u32,
1455    /// }
1456    ///
1457    /// struct JsonPersistence {
1458    ///     path: String,
1459    /// }
1460    ///
1461    /// impl PropertyPersistence for JsonPersistence {
1462    ///     type Value = UserPreferences;
1463    ///
1464    ///     fn load(&self) -> Result<Self::Value, Box<dyn std::error::Error + Send + Sync>> {
1465    ///         # /*
1466    ///         let data = fs::read_to_string(&self.path)?;
1467    ///         let prefs = serde_json::from_str(&data)?;
1468    ///         Ok(prefs)
1469    ///         # */
1470    ///         # Ok(UserPreferences { theme: "dark".to_string(), font_size: 14 })
1471    ///     }
1472    ///
1473    ///     fn save(&self, value: &Self::Value) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1474    ///         # /*
1475    ///         let data = serde_json::to_string_pretty(value)?;
1476    ///         fs::write(&self.path, data)?;
1477    ///         # */
1478    ///         Ok(())
1479    ///     }
1480    /// }
1481    ///
1482    /// let default_prefs = UserPreferences {
1483    ///     theme: "dark".to_string(),
1484    ///     font_size: 14,
1485    /// };
1486    ///
1487    /// let prefs = ObservableProperty::with_persistence(
1488    ///     default_prefs,
1489    ///     JsonPersistence { path: "./preferences.json".to_string() }
1490    /// );
1491    ///
1492    /// // Changes are auto-saved as JSON
1493    /// prefs.modify(|p| {
1494    ///     p.theme = "light".to_string();
1495    ///     p.font_size = 16;
1496    /// }).expect("Failed to update preferences");
1497    /// ```
1498    ///
1499    /// # Thread Safety
1500    ///
1501    /// The persistence handler must be `Send + Sync + 'static` since save operations
1502    /// may be called from any thread holding a reference to the property.
1503    ///
1504    /// # Performance Considerations
1505    ///
1506    /// - Persistence operations are called synchronously on every value change
1507    /// - Use fast storage backends or consider debouncing frequent updates
1508    /// - For high-frequency updates, consider implementing a buffered persistence strategy
1509    pub fn with_persistence<P>(initial_value: T, persistence: P) -> Self
1510    where
1511        P: PropertyPersistence<Value = T>,
1512    {
1513        // Try to load from persistence, fall back to initial_value on error
1514        let value = persistence.load().unwrap_or_else(|e| {
1515            eprintln!(
1516                "Failed to load persisted value, using initial value: {}",
1517                e
1518            );
1519            initial_value.clone()
1520        });
1521
1522        // Create the property with the loaded or default value
1523        let property = Self::new(value);
1524
1525        // Set up auto-save observer
1526        let persistence = Arc::new(persistence);
1527        if let Err(e) = property.subscribe(Arc::new(move |_old, new| {
1528            if let Err(save_err) = persistence.save(new) {
1529                eprintln!("Failed to persist property value: {}", save_err);
1530            }
1531        })) {
1532            eprintln!("Failed to subscribe persistence observer: {}", e);
1533        }
1534
1535        property
1536    }
1537
1538    /// Creates a new observable property with history tracking enabled
1539    ///
1540    /// This constructor creates a property that maintains a history of previous values,
1541    /// allowing you to undo changes and view historical values. The history buffer is
1542    /// automatically managed with a fixed size limit.
1543    ///
1544    /// # Arguments
1545    ///
1546    /// * `initial_value` - The starting value for this property
1547    /// * `history_size` - Maximum number of previous values to retain in history.
1548    ///   If 0, history tracking is disabled and behaves like a regular property.
1549    ///
1550    /// # History Behavior
1551    ///
1552    /// - The history buffer stores up to `history_size` previous values
1553    /// - When the buffer is full, the oldest value is removed when a new value is added
1554    /// - The current value is **not** included in the history - only past values
1555    /// - History is stored in chronological order (oldest to newest)
1556    /// - Undo operations pop values from the history and restore them as current
1557    ///
1558    /// # Memory Considerations
1559    ///
1560    /// Each historical value requires memory equivalent to `size_of::<T>()`. For large
1561    /// types or high history sizes, consider:
1562    /// - Using smaller history_size values
1563    /// - Wrapping large types in `Arc<T>` to share data between history entries
1564    /// - Implementing custom equality checks to avoid storing duplicate values
1565    ///
1566    /// # Examples
1567    ///
1568    /// ## Basic History Usage
1569    ///
1570    /// ```rust
1571    /// use observable_property::ObservableProperty;
1572    ///
1573    /// # fn main() -> Result<(), observable_property::PropertyError> {
1574    /// // Create property with space for 5 historical values
1575    /// let property = ObservableProperty::with_history(0, 5);
1576    ///
1577    /// // Make some changes
1578    /// property.set(10)?;
1579    /// property.set(20)?;
1580    /// property.set(30)?;
1581    ///
1582    /// assert_eq!(property.get()?, 30);
1583    ///
1584    /// // Undo last change
1585    /// property.undo()?;
1586    /// assert_eq!(property.get()?, 20);
1587    ///
1588    /// // Undo again
1589    /// property.undo()?;
1590    /// assert_eq!(property.get()?, 10);
1591    /// # Ok(())
1592    /// # }
1593    /// ```
1594    ///
1595    /// ## View History
1596    ///
1597    /// ```rust
1598    /// use observable_property::ObservableProperty;
1599    ///
1600    /// # fn main() -> Result<(), observable_property::PropertyError> {
1601    /// let property = ObservableProperty::with_history("start".to_string(), 3);
1602    ///
1603    /// property.set("second".to_string())?;
1604    /// property.set("third".to_string())?;
1605    /// property.set("fourth".to_string())?;
1606    ///
1607    /// // Get all historical values (oldest to newest)
1608    /// let history = property.get_history();
1609    /// assert_eq!(history.len(), 3);
1610    /// assert_eq!(history[0], "start");   // oldest
1611    /// assert_eq!(history[1], "second");
1612    /// assert_eq!(history[2], "third");   // newest (most recent past value)
1613    ///
1614    /// // Current value is "fourth" and not in history
1615    /// assert_eq!(property.get()?, "fourth");
1616    /// # Ok(())
1617    /// # }
1618    /// ```
1619    ///
1620    /// ## History with Observer Pattern
1621    ///
1622    /// ```rust
1623    /// use observable_property::ObservableProperty;
1624    /// use std::sync::Arc;
1625    ///
1626    /// # fn main() -> Result<(), observable_property::PropertyError> {
1627    /// let property = ObservableProperty::with_history(100, 10);
1628    ///
1629    /// // Observers work normally with history-enabled properties
1630    /// let _subscription = property.subscribe_with_subscription(Arc::new(|old, new| {
1631    ///     println!("Value changed: {} -> {}", old, new);
1632    /// }))?;
1633    ///
1634    /// property.set(200)?; // Triggers observer
1635    /// property.undo()?;   // Also triggers observer when reverting to 100
1636    /// # Ok(())
1637    /// # }
1638    /// ```
1639    ///
1640    /// ## Bounded History Buffer
1641    ///
1642    /// ```rust
1643    /// use observable_property::ObservableProperty;
1644    ///
1645    /// # fn main() -> Result<(), observable_property::PropertyError> {
1646    /// // Only keep 2 historical values
1647    /// let property = ObservableProperty::with_history(1, 2);
1648    ///
1649    /// property.set(2)?;  // history: [1]
1650    /// property.set(3)?;  // history: [1, 2]
1651    /// property.set(4)?;  // history: [2, 3] (oldest '1' was removed)
1652    ///
1653    /// let history = property.get_history();
1654    /// assert_eq!(history, vec![2, 3]);
1655    /// assert_eq!(property.get()?, 4);
1656    /// # Ok(())
1657    /// # }
1658    /// ```
1659    ///
1660    /// # Thread Safety
1661    ///
1662    /// History tracking is fully thread-safe and works correctly even when multiple
1663    /// threads are calling `set()`, `undo()`, and `get_history()` concurrently.
1664    pub fn with_history(initial_value: T, history_size: usize) -> Self {
1665        Self {
1666            inner: Arc::new(RwLock::new(InnerProperty {
1667                value: initial_value,
1668                observers: HashMap::new(),
1669                next_id: 0,
1670                history: if history_size > 0 {
1671                    Some(Vec::with_capacity(history_size))
1672                } else {
1673                    None
1674                },
1675                history_size,
1676                total_changes: 0,
1677                observer_calls: 0,
1678                notification_times: Vec::new(),
1679                #[cfg(feature = "debug")]
1680                debug_logging_enabled: false,
1681                #[cfg(feature = "debug")]
1682                change_logs: Vec::new(),
1683                batch_depth: 0,
1684                batch_initial_value: None,
1685                eq_fn: None,
1686                validator: None,
1687                event_log: None,
1688                event_log_size: 0,
1689            })),
1690            max_threads: MAX_THREADS,
1691            max_observers: MAX_OBSERVERS,
1692        }
1693    }
1694
1695    /// Creates a new observable property with event sourcing enabled
1696    ///
1697    /// This method enables full event logging for the property, recording every change
1698    /// as a timestamped event. This provides powerful capabilities for debugging,
1699    /// auditing, and event replay.
1700    ///
1701    /// # Features
1702    ///
1703    /// - **Complete Audit Trail**: Every change is recorded with old value, new value, and timestamp
1704    /// - **Time-Travel Debugging**: Examine the complete history of state changes
1705    /// - **Event Replay**: Reconstruct property state at any point in time
1706    /// - **Thread Information**: Each event captures which thread made the change
1707    /// - **Sequential Numbering**: Events are numbered starting from 0
1708    ///
1709    /// # Arguments
1710    ///
1711    /// * `initial_value` - The starting value for this property
1712    /// * `event_log_size` - Maximum number of events to keep in memory (0 = unlimited)
1713    ///
1714    /// # Memory Considerations
1715    ///
1716    /// Event logs store complete copies of both old and new values for each change.
1717    /// For properties with large values or high update frequency:
1718    /// - Use a bounded `event_log_size` to prevent unbounded memory growth
1719    /// - Consider using `with_history()` if you only need value snapshots without metadata
1720    /// - Monitor memory usage in production environments
1721    ///
1722    /// When the log exceeds `event_log_size`, the oldest events are automatically removed.
1723    ///
1724    /// # Examples
1725    ///
1726    /// ## Basic Event Logging
1727    ///
1728    /// ```rust
1729    /// use observable_property::ObservableProperty;
1730    /// use std::sync::Arc;
1731    ///
1732    /// # fn main() -> Result<(), observable_property::PropertyError> {
1733    /// // Create property with unlimited event log
1734    /// let counter = ObservableProperty::with_event_log(0, 0);
1735    ///
1736    /// counter.set(1)?;
1737    /// counter.set(2)?;
1738    /// counter.set(3)?;
1739    ///
1740    /// // Retrieve the complete event log
1741    /// let events = counter.get_event_log();
1742    /// assert_eq!(events.len(), 3);
1743    ///
1744    /// // Examine first event
1745    /// assert_eq!(events[0].old_value, 0);
1746    /// assert_eq!(events[0].new_value, 1);
1747    /// assert_eq!(events[0].event_number, 0);
1748    ///
1749    /// // Examine last event
1750    /// assert_eq!(events[2].old_value, 2);
1751    /// assert_eq!(events[2].new_value, 3);
1752    /// assert_eq!(events[2].event_number, 2);
1753    /// # Ok(())
1754    /// # }
1755    /// ```
1756    ///
1757    /// ## Bounded Event Log
1758    ///
1759    /// ```rust
1760    /// use observable_property::ObservableProperty;
1761    ///
1762    /// # fn main() -> Result<(), observable_property::PropertyError> {
1763    /// // Keep only the last 3 events
1764    /// let property = ObservableProperty::with_event_log(100, 3);
1765    ///
1766    /// property.set(101)?;
1767    /// property.set(102)?;
1768    /// property.set(103)?;
1769    /// property.set(104)?; // Oldest event (100->101) is now removed
1770    ///
1771    /// let events = property.get_event_log();
1772    /// assert_eq!(events.len(), 3);
1773    /// assert_eq!(events[0].old_value, 101);
1774    /// assert_eq!(events[2].new_value, 104);
1775    /// # Ok(())
1776    /// # }
1777    /// ```
1778    ///
1779    /// ## Time-Travel Debugging
1780    ///
1781    /// ```rust
1782    /// use observable_property::ObservableProperty;
1783    /// use std::time::Duration;
1784    ///
1785    /// # fn main() -> Result<(), observable_property::PropertyError> {
1786    /// let config = ObservableProperty::with_event_log("default".to_string(), 0);
1787    ///
1788    /// let start = std::time::Instant::now();
1789    /// config.set("config_v1".to_string())?;
1790    /// std::thread::sleep(Duration::from_millis(10));
1791    /// config.set("config_v2".to_string())?;
1792    /// std::thread::sleep(Duration::from_millis(10));
1793    /// config.set("config_v3".to_string())?;
1794    ///
1795    /// // Find what the config was 15ms after start
1796    /// let target_time = start + Duration::from_millis(15);
1797    /// let events = config.get_event_log();
1798    /// 
1799    /// let mut state = "default".to_string();
1800    /// for event in events {
1801    ///     if event.timestamp <= target_time {
1802    ///         state = event.new_value.clone();
1803    ///     } else {
1804    ///         break;
1805    ///     }
1806    /// }
1807    /// 
1808    /// println!("State at +15ms: {}", state);
1809    /// # Ok(())
1810    /// # }
1811    /// ```
1812    ///
1813    /// ## Audit Trail with Thread Information
1814    ///
1815    /// ```rust
1816    /// use observable_property::ObservableProperty;
1817    /// use std::sync::Arc;
1818    /// use std::thread;
1819    ///
1820    /// # fn main() -> Result<(), observable_property::PropertyError> {
1821    /// let shared_state = Arc::new(ObservableProperty::with_event_log(0, 0));
1822    ///
1823    /// let handles: Vec<_> = (0..3)
1824    ///     .map(|i| {
1825    ///         let state = shared_state.clone();
1826    ///         thread::spawn(move || {
1827    ///             state.set(i * 10).expect("Failed to set");
1828    ///         })
1829    ///     })
1830    ///     .collect();
1831    ///
1832    /// for handle in handles {
1833    ///     handle.join().unwrap();
1834    /// }
1835    ///
1836    /// // Examine which threads made changes
1837    /// let events = shared_state.get_event_log();
1838    /// for event in events {
1839    ///     println!(
1840    ///         "Event #{}: {} -> {} (thread: {})",
1841    ///         event.event_number,
1842    ///         event.old_value,
1843    ///         event.new_value,
1844    ///         event.thread_id
1845    ///     );
1846    /// }
1847    /// # Ok(())
1848    /// # }
1849    /// ```
1850    ///
1851    /// ## Event Replay
1852    ///
1853    /// ```rust
1854    /// use observable_property::ObservableProperty;
1855    ///
1856    /// # fn main() -> Result<(), observable_property::PropertyError> {
1857    /// let account_balance = ObservableProperty::with_event_log(1000, 0);
1858    ///
1859    /// // Simulate transactions
1860    /// account_balance.modify(|b| *b -= 100)?; // Withdrawal
1861    /// account_balance.modify(|b| *b += 50)?;  // Deposit
1862    /// account_balance.modify(|b| *b -= 200)?; // Withdrawal
1863    ///
1864    /// // Replay all transactions
1865    /// let events = account_balance.get_event_log();
1866    /// println!("Transaction History:");
1867    /// for event in events {
1868    ///     let change = event.new_value as i32 - event.old_value as i32;
1869    ///     let transaction_type = if change > 0 { "Deposit" } else { "Withdrawal" };
1870    ///     println!(
1871    ///         "[{}] {}: ${} (balance: {} -> {})",
1872    ///         event.event_number,
1873    ///         transaction_type,
1874    ///         change.abs(),
1875    ///         event.old_value,
1876    ///         event.new_value
1877    ///     );
1878    /// }
1879    /// # Ok(())
1880    /// # }
1881    /// ```
1882    ///
1883    /// # Thread Safety
1884    ///
1885    /// Event logging is fully thread-safe and works correctly even when multiple
1886    /// threads are modifying the property concurrently. Event numbers are assigned
1887    /// sequentially based on the order changes complete (not the order they start).
1888    ///
1889    /// # Difference from History
1890    ///
1891    /// While `with_history()` only stores previous values, `with_event_log()` stores
1892    /// complete event objects with timestamps and metadata. This makes event logs
1893    /// more suitable for auditing and debugging, but they consume more memory.
1894    ///
1895    /// | Feature | `with_history()` | `with_event_log()` |
1896    /// |---------|------------------|--------------------|
1897    /// | Stores values | ✓ | ✓ |
1898    /// | Stores timestamps | ✗ | ✓ |
1899    /// | Stores thread info | ✗ | ✓ |
1900    /// | Sequential numbering | ✗ | ✓ |
1901    /// | Old + new values | ✗ | ✓ |
1902    /// | Memory overhead | Low | Higher |
1903    /// | Undo support | ✓ | ✗ (manual) |
1904    pub fn with_event_log(initial_value: T, event_log_size: usize) -> Self {
1905        Self {
1906            inner: Arc::new(RwLock::new(InnerProperty {
1907                value: initial_value,
1908                observers: HashMap::new(),
1909                next_id: 0,
1910                history: None,
1911                history_size: 0,
1912                total_changes: 0,
1913                observer_calls: 0,
1914                notification_times: Vec::new(),
1915                #[cfg(feature = "debug")]
1916                debug_logging_enabled: false,
1917                #[cfg(feature = "debug")]
1918                change_logs: Vec::new(),
1919                batch_depth: 0,
1920                batch_initial_value: None,
1921                eq_fn: None,
1922                validator: None,
1923                event_log: Some(Vec::with_capacity(if event_log_size > 0 { event_log_size } else { 16 })),
1924                event_log_size,
1925            })),
1926            max_threads: MAX_THREADS,
1927            max_observers: MAX_OBSERVERS,
1928        }
1929    }
1930
1931    /// Reverts the property to its previous value from history
1932    ///
1933    /// This method pops the most recent value from the history buffer and makes it
1934    /// the current value. The current value is **not** added to history during undo.
1935    /// All subscribed observers are notified of this change.
1936    ///
1937    /// # Returns
1938    ///
1939    /// - `Ok(())` if the undo was successful
1940    /// - `Err(PropertyError::NoHistory)` if:
1941    ///   - History tracking is not enabled (created without `with_history()`)
1942    ///   - History buffer is empty (no previous values to restore)
1943    ///
1944    /// # Undo Chain Behavior
1945    ///
1946    /// Consecutive undo operations walk back through history until exhausted:
1947    /// ```text
1948    /// Initial: value=4, history=[1, 2, 3]
1949    /// After undo(): value=3, history=[1, 2]
1950    /// After undo(): value=2, history=[1]
1951    /// After undo(): value=1, history=[]
1952    /// After undo(): Error(NoHistory) - no more history
1953    /// ```
1954    ///
1955    /// # Observer Notification
1956    ///
1957    /// Observers are notified with the current value as "old" and the restored
1958    /// historical value as "new", maintaining the same notification pattern as `set()`.
1959    ///
1960    /// # Examples
1961    ///
1962    /// ## Basic Undo
1963    ///
1964    /// ```rust
1965    /// use observable_property::ObservableProperty;
1966    ///
1967    /// # fn main() -> Result<(), observable_property::PropertyError> {
1968    /// let property = ObservableProperty::with_history(1, 5);
1969    ///
1970    /// property.set(2)?;
1971    /// property.set(3)?;
1972    /// assert_eq!(property.get()?, 3);
1973    ///
1974    /// property.undo()?;
1975    /// assert_eq!(property.get()?, 2);
1976    ///
1977    /// property.undo()?;
1978    /// assert_eq!(property.get()?, 1);
1979    ///
1980    /// // No more history
1981    /// assert!(property.undo().is_err());
1982    /// # Ok(())
1983    /// # }
1984    /// ```
1985    ///
1986    /// ## Undo with Observers
1987    ///
1988    /// ```rust
1989    /// use observable_property::ObservableProperty;
1990    /// use std::sync::Arc;
1991    ///
1992    /// # fn main() -> Result<(), observable_property::PropertyError> {
1993    /// let property = ObservableProperty::with_history(10, 5);
1994    ///
1995    /// let _subscription = property.subscribe_with_subscription(Arc::new(|old, new| {
1996    ///     println!("Changed from {} to {}", old, new);
1997    /// }))?;
1998    ///
1999    /// property.set(20)?; // Prints: "Changed from 10 to 20"
2000    /// property.undo()?;  // Prints: "Changed from 20 to 10"
2001    /// # Ok(())
2002    /// # }
2003    /// ```
2004    ///
2005    /// ## Error Handling
2006    ///
2007    /// ```rust
2008    /// use observable_property::{ObservableProperty, PropertyError};
2009    ///
2010    /// # fn main() -> Result<(), PropertyError> {
2011    /// // Property without history
2012    /// let no_history = ObservableProperty::new(42);
2013    /// match no_history.undo() {
2014    ///     Err(PropertyError::NoHistory { .. }) => {
2015    ///         println!("Expected: history not enabled");
2016    ///     }
2017    ///     _ => panic!("Should fail without history"),
2018    /// }
2019    ///
2020    /// // Property with history but empty
2021    /// let empty_history = ObservableProperty::with_history(100, 5);
2022    /// match empty_history.undo() {
2023    ///     Err(PropertyError::NoHistory { .. }) => {
2024    ///         println!("Expected: no history to undo");
2025    ///     }
2026    ///     _ => panic!("Should fail with empty history"),
2027    /// }
2028    /// # Ok(())
2029    /// # }
2030    /// ```
2031    ///
2032    /// ## Undo After Multiple Changes
2033    ///
2034    /// ```rust
2035    /// use observable_property::ObservableProperty;
2036    ///
2037    /// # fn main() -> Result<(), observable_property::PropertyError> {
2038    /// let counter = ObservableProperty::with_history(0, 3);
2039    ///
2040    /// // Make several changes
2041    /// for i in 1..=5 {
2042    ///     counter.set(i)?;
2043    /// }
2044    ///
2045    /// assert_eq!(counter.get()?, 5);
2046    ///
2047    /// // Undo three times (limited by history_size=3)
2048    /// counter.undo()?;
2049    /// assert_eq!(counter.get()?, 4);
2050    ///
2051    /// counter.undo()?;
2052    /// assert_eq!(counter.get()?, 3);
2053    ///
2054    /// counter.undo()?;
2055    /// assert_eq!(counter.get()?, 2);
2056    ///
2057    /// // No more history (oldest value in buffer was 2)
2058    /// assert!(counter.undo().is_err());
2059    /// # Ok(())
2060    /// # }
2061    /// ```
2062    ///
2063    /// # Thread Safety
2064    ///
2065    /// This method is thread-safe and can be called concurrently with `set()`,
2066    /// `get()`, and other operations from multiple threads.
2067    pub fn undo(&self) -> Result<(), PropertyError> {
2068        let (old_value, new_value, observers_snapshot, dead_observer_ids) = {
2069            let mut prop = match self.inner.write() {
2070                Ok(guard) => guard,
2071                Err(poisoned) => poisoned.into_inner(),
2072            };
2073
2074            // Clone the validator Arc before working with history to avoid borrow conflicts
2075            let validator = prop.validator.clone();
2076
2077            // Check if history is enabled and has values
2078            let history = prop.history.as_mut().ok_or_else(|| PropertyError::NoHistory {
2079                reason: "History tracking is not enabled for this property".to_string(),
2080            })?;
2081
2082            if history.is_empty() {
2083                return Err(PropertyError::NoHistory {
2084                    reason: "No history available to undo".to_string(),
2085                });
2086            }
2087
2088            // Pop the most recent historical value
2089            let previous_value = history.pop().unwrap();
2090            
2091            // Validate the historical value if a validator is configured
2092            // This ensures consistency if validation rules have changed since the value was stored
2093            if let Some(validator) = validator {
2094                validator(&previous_value).map_err(|reason| {
2095                    // Put the value back in history if validation fails
2096                    history.push(previous_value.clone());
2097                    PropertyError::ValidationError { 
2098                        reason: format!("Cannot undo to invalid historical value: {}", reason)
2099                    }
2100                })?;
2101            }
2102            
2103            let old_value = mem::replace(&mut prop.value, previous_value.clone());
2104
2105            // Debug logging (requires T: std::fmt::Debug when debug feature is enabled)
2106            // Collect active observers (same pattern as set())
2107            let mut observers_snapshot = Vec::new();
2108            let mut dead_ids = Vec::new();
2109            for (id, observer_ref) in &prop.observers {
2110                if let Some(observer) = observer_ref.try_call() {
2111                    observers_snapshot.push(observer);
2112                } else {
2113                    dead_ids.push(*id);
2114                }
2115            }
2116
2117            (old_value, previous_value, observers_snapshot, dead_ids)
2118        };
2119
2120        // Notify all active observers
2121        for observer in observers_snapshot {
2122            if let Err(e) = panic::catch_unwind(panic::AssertUnwindSafe(|| {
2123                observer(&old_value, &new_value);
2124            })) {
2125                eprintln!("Observer panic during undo: {:?}", e);
2126            }
2127        }
2128
2129        // Clean up dead weak observers
2130        if !dead_observer_ids.is_empty() {
2131            let mut prop = match self.inner.write() {
2132                Ok(guard) => guard,
2133                Err(poisoned) => poisoned.into_inner(),
2134            };
2135            for id in dead_observer_ids {
2136                prop.observers.remove(&id);
2137            }
2138        }
2139
2140        Ok(())
2141    }
2142
2143    /// Returns a snapshot of all historical values
2144    ///
2145    /// This method returns a vector containing all previous values currently stored
2146    /// in the history buffer, ordered from oldest to newest. The current value is
2147    /// **not** included in the returned vector.
2148    ///
2149    /// # Returns
2150    ///
2151    /// A `Vec<T>` containing historical values in chronological order:
2152    /// - `vec[0]` is the oldest value in history
2153    /// - `vec[len-1]` is the most recent past value (the one that would be restored by `undo()`)
2154    /// - Empty vector if history is disabled or no history has been recorded
2155    ///
2156    /// # Memory
2157    ///
2158    /// This method clones all historical values, so the returned vector owns its data
2159    /// independently of the property. This allows safe sharing across threads without
2160    /// holding locks.
2161    ///
2162    /// # Examples
2163    ///
2164    /// ## Basic History Retrieval
2165    ///
2166    /// ```rust
2167    /// use observable_property::ObservableProperty;
2168    ///
2169    /// # fn main() -> Result<(), observable_property::PropertyError> {
2170    /// let property = ObservableProperty::with_history("a".to_string(), 5);
2171    ///
2172    /// property.set("b".to_string())?;
2173    /// property.set("c".to_string())?;
2174    /// property.set("d".to_string())?;
2175    ///
2176    /// let history = property.get_history();
2177    /// assert_eq!(history.len(), 3);
2178    /// assert_eq!(history[0], "a"); // oldest
2179    /// assert_eq!(history[1], "b");
2180    /// assert_eq!(history[2], "c"); // newest (what undo() would restore)
2181    ///
2182    /// // Current value is not in history
2183    /// assert_eq!(property.get()?, "d");
2184    /// # Ok(())
2185    /// # }
2186    /// ```
2187    ///
2188    /// ## Empty History
2189    ///
2190    /// ```rust
2191    /// use observable_property::ObservableProperty;
2192    ///
2193    /// // No history recorded yet
2194    /// let fresh = ObservableProperty::with_history(42, 10);
2195    /// assert!(fresh.get_history().is_empty());
2196    ///
2197    /// // History disabled (size = 0)
2198    /// let no_tracking = ObservableProperty::with_history(42, 0);
2199    /// assert!(no_tracking.get_history().is_empty());
2200    ///
2201    /// // Regular property (no history support)
2202    /// let regular = ObservableProperty::new(42);
2203    /// assert!(regular.get_history().is_empty());
2204    /// ```
2205    ///
2206    /// ## History Buffer Limit
2207    ///
2208    /// ```rust
2209    /// use observable_property::ObservableProperty;
2210    ///
2211    /// # fn main() -> Result<(), observable_property::PropertyError> {
2212    /// // Limited history size
2213    /// let property = ObservableProperty::with_history(1, 3);
2214    ///
2215    /// for i in 2..=6 {
2216    ///     property.set(i)?;
2217    /// }
2218    ///
2219    /// // Only last 3 historical values are kept
2220    /// let history = property.get_history();
2221    /// assert_eq!(history, vec![3, 4, 5]);
2222    /// assert_eq!(property.get()?, 6); // current
2223    /// # Ok(())
2224    /// # }
2225    /// ```
2226    ///
2227    /// ## Iterating Through History
2228    ///
2229    /// ```rust
2230    /// use observable_property::ObservableProperty;
2231    ///
2232    /// # fn main() -> Result<(), observable_property::PropertyError> {
2233    /// let property = ObservableProperty::with_history(0.0f64, 5);
2234    ///
2235    /// property.set(1.5)?;
2236    /// property.set(3.0)?;
2237    /// property.set(4.5)?;
2238    ///
2239    /// println!("Historical values:");
2240    /// for (i, value) in property.get_history().iter().enumerate() {
2241    ///     println!("  [{}] {}", i, value);
2242    /// }
2243    /// # Ok(())
2244    /// # }
2245    /// ```
2246    ///
2247    /// ## Checking History Before Undo
2248    ///
2249    /// ```rust
2250    /// use observable_property::ObservableProperty;
2251    ///
2252    /// # fn main() -> Result<(), observable_property::PropertyError> {
2253    /// let property = ObservableProperty::with_history(100, 5);
2254    /// property.set(200)?;
2255    /// property.set(300)?;
2256    ///
2257    /// // Check what undo would restore
2258    /// let history = property.get_history();
2259    /// if !history.is_empty() {
2260    ///     let would_restore = history.last().unwrap();
2261    ///     println!("Undo would restore: {}", would_restore);
2262    ///     
2263    ///     // Actually perform the undo
2264    ///     property.undo()?;
2265    ///     assert_eq!(property.get()?, *would_restore);
2266    /// }
2267    /// # Ok(())
2268    /// # }
2269    /// ```
2270    ///
2271    /// # Thread Safety
2272    ///
2273    /// This method acquires a read lock, allowing multiple concurrent readers.
2274    /// The returned vector is independent of the property's internal state.
2275    pub fn get_history(&self) -> Vec<T> {
2276        match self.inner.read() {
2277            Ok(prop) => prop.history.as_ref().map_or(Vec::new(), |h| h.clone()),
2278            Err(poisoned) => {
2279                // Graceful degradation: recover from poisoned lock
2280                let prop = poisoned.into_inner();
2281                prop.history.as_ref().map_or(Vec::new(), |h| h.clone())
2282            }
2283        }
2284    }
2285
2286    /// Gets the complete event log for this property
2287    ///
2288    /// Returns a vector of all recorded property change events. Each event contains
2289    /// the old value, new value, timestamp, event number, and thread information.
2290    /// This provides a complete audit trail of all changes to the property.
2291    ///
2292    /// This method acquires a read lock, allowing multiple concurrent readers.
2293    /// The returned vector is independent of the property's internal state.
2294    ///
2295    /// # Returns
2296    ///
2297    /// A vector of `PropertyEvent<T>` objects, in chronological order (oldest first).
2298    /// Returns an empty vector if:
2299    /// - Event logging is not enabled (property not created with `with_event_log()`)
2300    /// - No changes have been made yet
2301    ///
2302    /// # Examples
2303    ///
2304    /// ## Basic Event Log Retrieval
2305    ///
2306    /// ```rust
2307    /// use observable_property::ObservableProperty;
2308    ///
2309    /// # fn main() -> Result<(), observable_property::PropertyError> {
2310    /// let counter = ObservableProperty::with_event_log(0, 0);
2311    ///
2312    /// counter.set(1)?;
2313    /// counter.set(2)?;
2314    /// counter.set(3)?;
2315    ///
2316    /// let events = counter.get_event_log();
2317    /// assert_eq!(events.len(), 3);
2318    ///
2319    /// // First event
2320    /// assert_eq!(events[0].old_value, 0);
2321    /// assert_eq!(events[0].new_value, 1);
2322    /// assert_eq!(events[0].event_number, 0);
2323    ///
2324    /// // Last event
2325    /// assert_eq!(events[2].old_value, 2);
2326    /// assert_eq!(events[2].new_value, 3);
2327    /// assert_eq!(events[2].event_number, 2);
2328    /// # Ok(())
2329    /// # }
2330    /// ```
2331    ///
2332    /// ## Filtering Events by Time
2333    ///
2334    /// ```rust
2335    /// use observable_property::ObservableProperty;
2336    /// use std::time::{Duration, Instant};
2337    ///
2338    /// # fn main() -> Result<(), observable_property::PropertyError> {
2339    /// let property = ObservableProperty::with_event_log(0, 0);
2340    /// let start = Instant::now();
2341    ///
2342    /// property.set(1)?;
2343    /// std::thread::sleep(Duration::from_millis(10));
2344    /// property.set(2)?;
2345    /// std::thread::sleep(Duration::from_millis(10));
2346    /// property.set(3)?;
2347    ///
2348    /// let cutoff = start + Duration::from_millis(15);
2349    /// let recent_events: Vec<_> = property.get_event_log()
2350    ///     .into_iter()
2351    ///     .filter(|e| e.timestamp > cutoff)
2352    ///     .collect();
2353    ///
2354    /// println!("Recent events: {}", recent_events.len());
2355    /// # Ok(())
2356    /// # }
2357    /// ```
2358    ///
2359    /// ## Analyzing Event Patterns
2360    ///
2361    /// ```rust
2362    /// use observable_property::ObservableProperty;
2363    ///
2364    /// # fn main() -> Result<(), observable_property::PropertyError> {
2365    /// let score = ObservableProperty::with_event_log(0, 0);
2366    ///
2367    /// score.modify(|s| *s += 10)?;
2368    /// score.modify(|s| *s -= 3)?;
2369    /// score.modify(|s| *s += 5)?;
2370    ///
2371    /// let events = score.get_event_log();
2372    /// let total_increases = events.iter()
2373    ///     .filter(|e| e.new_value > e.old_value)
2374    ///     .count();
2375    /// let total_decreases = events.iter()
2376    ///     .filter(|e| e.new_value < e.old_value)
2377    ///     .count();
2378    ///
2379    /// println!("Increases: {}, Decreases: {}", total_increases, total_decreases);
2380    /// # Ok(())
2381    /// # }
2382    /// ```
2383    ///
2384    /// ## Event Log with Thread Information
2385    ///
2386    /// ```rust
2387    /// use observable_property::ObservableProperty;
2388    /// use std::sync::Arc;
2389    /// use std::thread;
2390    ///
2391    /// # fn main() -> Result<(), observable_property::PropertyError> {
2392    /// let property = Arc::new(ObservableProperty::with_event_log(0, 0));
2393    ///
2394    /// let handles: Vec<_> = (0..3).map(|i| {
2395    ///     let prop = property.clone();
2396    ///     thread::spawn(move || {
2397    ///         prop.set(i * 10).expect("Set failed");
2398    ///     })
2399    /// }).collect();
2400    ///
2401    /// for handle in handles {
2402    ///     handle.join().unwrap();
2403    /// }
2404    ///
2405    /// // Analyze which threads made changes
2406    /// for event in property.get_event_log() {
2407    ///     println!("Event {}: Thread {}", event.event_number, event.thread_id);
2408    /// }
2409    /// # Ok(())
2410    /// # }
2411    /// ```
2412    ///
2413    /// ## Replaying Property State
2414    ///
2415    /// ```rust
2416    /// use observable_property::ObservableProperty;
2417    ///
2418    /// # fn main() -> Result<(), observable_property::PropertyError> {
2419    /// let property = ObservableProperty::with_event_log(100, 0);
2420    ///
2421    /// property.set(150)?;
2422    /// property.set(200)?;
2423    /// property.set(175)?;
2424    ///
2425    /// // Replay state at each point in time
2426    /// let events = property.get_event_log();
2427    /// let mut state = 100; // Initial value
2428    /// 
2429    /// println!("Initial state: {}", state);
2430    /// for event in events {
2431    ///     state = event.new_value;
2432    ///     println!("After event {}: {}", event.event_number, state);
2433    /// }
2434    /// # Ok(())
2435    /// # }
2436    /// ```
2437    ///
2438    /// # Thread Safety
2439    ///
2440    /// This method is thread-safe and can be called concurrently from multiple threads.
2441    /// The returned event log is a snapshot at the time of the call.
2442    ///
2443    /// # Performance
2444    ///
2445    /// This method clones the entire event log. For properties with large event logs,
2446    /// consider the memory and performance implications. If you only need recent events,
2447    /// use filtering on the result or create the property with a bounded `event_log_size`.
2448    pub fn get_event_log(&self) -> Vec<PropertyEvent<T>> {
2449        match self.inner.read() {
2450            Ok(prop) => prop.event_log.as_ref().map_or(Vec::new(), |log| log.clone()),
2451            Err(poisoned) => {
2452                // Graceful degradation: recover from poisoned lock
2453                let prop = poisoned.into_inner();
2454                prop.event_log.as_ref().map_or(Vec::new(), |log| log.clone())
2455            }
2456        }
2457    }
2458
2459    /// Gets the current value of the property
2460    ///
2461    /// This method acquires a read lock, which allows multiple concurrent readers
2462    /// but will block if a writer currently holds the lock.
2463    ///
2464    /// # Returns
2465    ///
2466    /// `Ok(T)` containing a clone of the current value, or `Err(PropertyError)`
2467    /// if the lock is poisoned.
2468    ///
2469    /// # Examples
2470    ///
2471    /// ```rust
2472    /// use observable_property::ObservableProperty;
2473    ///
2474    /// let property = ObservableProperty::new("hello".to_string());
2475    /// match property.get() {
2476    ///     Ok(value) => assert_eq!(value, "hello"),
2477    ///     Err(e) => eprintln!("Failed to get property value: {}", e),
2478    /// }
2479    /// ```
2480    pub fn get(&self) -> Result<T, PropertyError> {
2481        match self.inner.read() {
2482            Ok(prop) => Ok(prop.value.clone()),
2483            Err(poisoned) => {
2484                // Graceful degradation: recover value from poisoned lock
2485                // This allows continued operation even after a panic in another thread
2486                Ok(poisoned.into_inner().value.clone())
2487            }
2488        }
2489    }
2490
2491    /// Returns performance metrics for this property
2492    ///
2493    /// Provides insight into property usage patterns and observer notification
2494    /// performance. This is useful for profiling, debugging, and performance
2495    /// optimization.
2496    ///
2497    /// # Metrics Provided
2498    ///
2499    /// - **total_changes**: Number of times the property value has been changed
2500    /// - **observer_calls**: Total number of observer notification calls made
2501    /// - **avg_notification_time**: Average time taken to notify all observers
2502    ///
2503    /// # Note
2504    ///
2505    /// - For `set_async()`, the notification time measures the time to spawn threads,
2506    ///   not the actual observer execution time (since threads are fire-and-forget).
2507    /// - Observer calls are counted even if they panic (panic recovery continues).
2508    ///
2509    /// # Examples
2510    ///
2511    /// ```rust
2512    /// use observable_property::ObservableProperty;
2513    /// use std::sync::Arc;
2514    ///
2515    /// # fn main() -> Result<(), observable_property::PropertyError> {
2516    /// let property = ObservableProperty::new(0);
2517    ///
2518    /// // Subscribe multiple observers
2519    /// property.subscribe(Arc::new(|old, new| {
2520    ///     println!("Observer 1: {} -> {}", old, new);
2521    /// }))?;
2522    ///
2523    /// property.subscribe(Arc::new(|old, new| {
2524    ///     println!("Observer 2: {} -> {}", old, new);
2525    /// }))?;
2526    ///
2527    /// // Make some changes
2528    /// property.set(42)?;
2529    /// property.set(100)?;
2530    /// property.set(200)?;
2531    ///
2532    /// // Get performance metrics
2533    /// let metrics = property.get_metrics()?;
2534    /// println!("Total changes: {}", metrics.total_changes); // 3
2535    /// println!("Observer calls: {}", metrics.observer_calls); // 6 (3 changes × 2 observers)
2536    /// println!("Avg notification time: {:?}", metrics.avg_notification_time);
2537    /// # Ok(())
2538    /// # }
2539    /// ```
2540    pub fn get_metrics(&self) -> Result<PropertyMetrics, PropertyError> {
2541        match self.inner.read() {
2542            Ok(prop) => {
2543                let avg_notification_time = if prop.notification_times.is_empty() {
2544                    Duration::from_secs(0)
2545                } else {
2546                    let total: Duration = prop.notification_times.iter().sum();
2547                    total / prop.notification_times.len() as u32
2548                };
2549
2550                Ok(PropertyMetrics {
2551                    total_changes: prop.total_changes,
2552                    observer_calls: prop.observer_calls,
2553                    avg_notification_time,
2554                })
2555            }
2556            Err(poisoned) => {
2557                // Graceful degradation: recover metrics from poisoned lock
2558                let prop = poisoned.into_inner();
2559                let avg_notification_time = if prop.notification_times.is_empty() {
2560                    Duration::from_secs(0)
2561                } else {
2562                    let total: Duration = prop.notification_times.iter().sum();
2563                    total / prop.notification_times.len() as u32
2564                };
2565
2566                Ok(PropertyMetrics {
2567                    total_changes: prop.total_changes,
2568                    observer_calls: prop.observer_calls,
2569                    avg_notification_time,
2570                })
2571            }
2572        }
2573    }
2574
2575    /// Sets the property to a new value and notifies all observers
2576    ///
2577    /// This method will:
2578    /// 1. Acquire a write lock (blocking other readers/writers)
2579    /// 2. Update the value and capture a snapshot of observers
2580    /// 3. Release the lock
2581    /// 4. Notify all observers sequentially with the old and new values
2582    ///
2583    /// Observer notifications are wrapped in panic recovery to prevent one
2584    /// misbehaving observer from affecting others.
2585    ///
2586    /// # Arguments
2587    ///
2588    /// * `new_value` - The new value to set
2589    ///
2590    /// # Returns
2591    ///
2592    /// `Ok(())` if successful, or `Err(PropertyError)` if the lock is poisoned.
2593    ///
2594    /// # Examples
2595    ///
2596    /// ```rust
2597    /// use observable_property::ObservableProperty;
2598    /// use std::sync::Arc;
2599    ///
2600    /// let property = ObservableProperty::new(10);
2601    ///
2602    /// property.subscribe(Arc::new(|old, new| {
2603    ///     println!("Value changed from {} to {}", old, new);
2604    /// })).map_err(|e| {
2605    ///     eprintln!("Failed to subscribe: {}", e);
2606    ///     e
2607    /// })?;
2608    ///
2609    /// property.set(20).map_err(|e| {
2610    ///     eprintln!("Failed to set property value: {}", e);
2611    ///     e
2612    /// })?; // Triggers observer notification
2613    /// # Ok::<(), observable_property::PropertyError>(())
2614    /// ```
2615    pub fn set(&self, new_value: T) -> Result<(), PropertyError> {
2616        // Validate the new value if a validator is configured
2617        {
2618            let prop = match self.inner.read() {
2619                Ok(guard) => guard,
2620                Err(poisoned) => poisoned.into_inner(),
2621            };
2622            
2623            if let Some(validator) = &prop.validator {
2624                validator(&new_value).map_err(|reason| PropertyError::ValidationError { reason })?;
2625            }
2626        }
2627
2628        let notification_start = Instant::now();
2629        let (old_value, observers_snapshot, dead_observer_ids, in_batch) = {
2630            let mut prop = match self.inner.write() {
2631                Ok(guard) => guard,
2632                Err(poisoned) => {
2633                    // Graceful degradation: recover from poisoned write lock
2634                    // Clear the poison flag by taking ownership of the inner value
2635                    poisoned.into_inner()
2636                }
2637            };
2638
2639            // Check if values are equal using custom equality function if provided
2640            let values_equal = if let Some(eq_fn) = &prop.eq_fn {
2641                eq_fn(&prop.value, &new_value)
2642            } else {
2643                false  // No equality function = always notify
2644            };
2645
2646            // If values are equal, skip everything
2647            if values_equal {
2648                return Ok(());
2649            }
2650
2651            // Check if we're in a batch update
2652            let in_batch = prop.batch_depth > 0;
2653
2654            // Performance optimization: use mem::replace to avoid one clone operation
2655            let old_value = mem::replace(&mut prop.value, new_value.clone());
2656            
2657            // Track the change
2658            prop.total_changes += 1;
2659            let event_num = prop.total_changes - 1; // Capture for event numbering
2660            
2661            // Add old value to history if history tracking is enabled
2662            let history_size = prop.history_size;
2663            if let Some(history) = &mut prop.history {
2664                // Add old value to history
2665                history.push(old_value.clone());
2666                
2667                // Enforce history size limit by removing oldest values
2668                if history.len() > history_size {
2669                    let overflow = history.len() - history_size;
2670                    history.drain(0..overflow);
2671                }
2672            }
2673            
2674            // Record event if event logging is enabled
2675            let event_log_size = prop.event_log_size;
2676            if let Some(event_log) = &mut prop.event_log {
2677                let event = PropertyEvent {
2678                    timestamp: Instant::now(),
2679                    old_value: old_value.clone(),
2680                    new_value: new_value.clone(),
2681                    event_number: event_num, // Use captured event number for consistent numbering
2682                    thread_id: format!("{:?}", thread::current().id()),
2683                };
2684                
2685                event_log.push(event);
2686                
2687                // Enforce event log size limit by removing oldest events (if bounded)
2688                if event_log_size > 0 && event_log.len() > event_log_size {
2689                    let overflow = event_log.len() - event_log_size;
2690                    event_log.drain(0..overflow);
2691                }
2692            }
2693            
2694            // Collect active observers and track dead weak observers (only if not in batch)
2695            let mut observers_snapshot = Vec::new();
2696            let mut dead_ids = Vec::new();
2697            if !in_batch {
2698                for (id, observer_ref) in &prop.observers {
2699                    if let Some(observer) = observer_ref.try_call() {
2700                        observers_snapshot.push(observer);
2701                    } else {
2702                        // Weak observer is dead, mark for removal
2703                        dead_ids.push(*id);
2704                    }
2705                }
2706            }
2707            
2708            (old_value, observers_snapshot, dead_ids, in_batch)
2709        };
2710
2711        // Skip notifications if we're in a batch update
2712        if in_batch {
2713            return Ok(());
2714        }
2715
2716        // Notify all active observers
2717        let observer_count = observers_snapshot.len();
2718        for observer in observers_snapshot {
2719            if let Err(e) = panic::catch_unwind(panic::AssertUnwindSafe(|| {
2720                observer(&old_value, &new_value);
2721            })) {
2722                eprintln!("Observer panic: {:?}", e);
2723            }
2724        }
2725        
2726        // Record metrics
2727        let notification_time = notification_start.elapsed();
2728        {
2729            let mut prop = match self.inner.write() {
2730                Ok(guard) => guard,
2731                Err(poisoned) => poisoned.into_inner(),
2732            };
2733            prop.observer_calls += observer_count;
2734            prop.notification_times.push(notification_time);
2735        }
2736
2737        // Clean up dead weak observers
2738        if !dead_observer_ids.is_empty() {
2739            let mut prop = match self.inner.write() {
2740                Ok(guard) => guard,
2741                Err(poisoned) => poisoned.into_inner(),
2742            };
2743            for id in dead_observer_ids {
2744                prop.observers.remove(&id);
2745            }
2746        }
2747
2748        Ok(())
2749    }
2750
2751    /// Sets the property to a new value and notifies observers asynchronously
2752    ///
2753    /// This method is similar to `set()` but spawns observers in background threads
2754    /// for non-blocking operation. This is useful when observers might perform
2755    /// time-consuming operations.
2756    ///
2757    /// Observers are batched into groups and each batch runs in its own thread
2758    /// to limit resource usage while still providing parallelism.
2759    ///
2760    /// # Thread Management (Fire-and-Forget Pattern)
2761    ///
2762    /// **Important**: This method uses a fire-and-forget pattern. Spawned threads are
2763    /// **not joined** and run independently in the background. This design is intentional
2764    /// for non-blocking behavior but has important implications:
2765    ///
2766    /// ## Characteristics:
2767    /// - ✅ **Non-blocking**: Returns immediately without waiting for observers
2768    /// - ✅ **High performance**: No synchronization overhead
2769    /// - ⚠️ **No completion guarantee**: Thread may still be running when method returns
2770    /// - ⚠️ **No error propagation**: Observer errors are logged but not returned
2771    /// - ⚠️ **Testing caveat**: May need explicit delays to observe side effects
2772    /// - ⚠️ **Ordering caveat**: Multiple rapid `set_async()` calls may result in observers
2773    ///   receiving notifications out of order due to thread scheduling. Use `set()` if
2774    ///   sequential ordering is critical.
2775    ///
2776    /// ## Use Cases:
2777    /// - **UI updates**: Fire updates without blocking the main thread
2778    /// - **Logging**: Asynchronous logging that doesn't block operations
2779    /// - **Metrics**: Non-critical telemetry that can be lost
2780    /// - **Notifications**: Fire-and-forget alerts or messages
2781    ///
2782    /// ## When NOT to Use:
2783    /// - **Critical operations**: Use `set()` if you need guarantees
2784    /// - **Transactional updates**: Use `set()` for atomic operations
2785    /// - **Sequential dependencies**: If next operation depends on observer completion
2786    ///
2787    /// ## Testing Considerations:
2788    /// ```rust
2789    /// use observable_property::ObservableProperty;
2790    /// use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
2791    /// use std::time::Duration;
2792    ///
2793    /// # fn main() -> Result<(), observable_property::PropertyError> {
2794    /// let property = ObservableProperty::new(0);
2795    /// let was_called = Arc::new(AtomicBool::new(false));
2796    /// let flag = was_called.clone();
2797    ///
2798    /// property.subscribe(Arc::new(move |_, _| {
2799    ///     flag.store(true, Ordering::SeqCst);
2800    /// }))?;
2801    ///
2802    /// property.set_async(42)?;
2803    ///
2804    /// // ⚠️ Immediate check might fail - thread may not have run yet
2805    /// // assert!(was_called.load(Ordering::SeqCst)); // May fail!
2806    ///
2807    /// // ✅ Add a small delay to allow background thread to complete
2808    /// std::thread::sleep(Duration::from_millis(10));
2809    /// assert!(was_called.load(Ordering::SeqCst)); // Now reliable
2810    /// # Ok(())
2811    /// # }
2812    /// ```
2813    ///
2814    /// # Arguments
2815    ///
2816    /// * `new_value` - The new value to set
2817    ///
2818    /// # Returns
2819    ///
2820    /// `Ok(())` if successful, or `Err(PropertyError)` if the lock is poisoned.
2821    /// Note that this only indicates the property was updated successfully;
2822    /// observer execution happens asynchronously and errors are not returned.
2823    ///
2824    /// # Examples
2825    ///
2826    /// ## Basic Usage
2827    ///
2828    /// ```rust
2829    /// use observable_property::ObservableProperty;
2830    /// use std::sync::Arc;
2831    /// use std::time::Duration;
2832    ///
2833    /// let property = ObservableProperty::new(0);
2834    ///
2835    /// property.subscribe(Arc::new(|old, new| {
2836    ///     // This observer does slow work but won't block the caller
2837    ///     std::thread::sleep(Duration::from_millis(100));
2838    ///     println!("Slow observer: {} -> {}", old, new);
2839    /// })).map_err(|e| {
2840    ///     eprintln!("Failed to subscribe: {}", e);
2841    ///     e
2842    /// })?;
2843    ///
2844    /// // This returns immediately even though observer is slow
2845    /// property.set_async(42).map_err(|e| {
2846    ///     eprintln!("Failed to set value asynchronously: {}", e);
2847    ///     e
2848    /// })?;
2849    ///
2850    /// // Continue working immediately - observer runs in background
2851    /// println!("Main thread continues without waiting");
2852    /// # Ok::<(), observable_property::PropertyError>(())
2853    /// ```
2854    ///
2855    /// ## Multiple Rapid Updates
2856    ///
2857    /// ```rust
2858    /// use observable_property::ObservableProperty;
2859    /// use std::sync::Arc;
2860    ///
2861    /// # fn main() -> Result<(), observable_property::PropertyError> {
2862    /// let property = ObservableProperty::new(0);
2863    ///
2864    /// property.subscribe(Arc::new(|old, new| {
2865    ///     // Expensive operation (e.g., database update, API call)
2866    ///     println!("Processing: {} -> {}", old, new);
2867    /// }))?;
2868    ///
2869    /// // All of these return immediately - observers run in parallel
2870    /// property.set_async(1)?;
2871    /// property.set_async(2)?;
2872    /// property.set_async(3)?;
2873    /// property.set_async(4)?;
2874    /// property.set_async(5)?;
2875    ///
2876    /// // All observer calls are now running in background threads
2877    /// # Ok(())
2878    /// # }
2879    /// ```
2880    pub fn set_async(&self, new_value: T) -> Result<(), PropertyError> {
2881        // Validate the new value if a validator is configured
2882        {
2883            let prop = match self.inner.read() {
2884                Ok(guard) => guard,
2885                Err(poisoned) => poisoned.into_inner(),
2886            };
2887            
2888            if let Some(validator) = &prop.validator {
2889                validator(&new_value).map_err(|reason| PropertyError::ValidationError { reason })?;
2890            }
2891        }
2892
2893        let notification_start = Instant::now();
2894        let (old_value, observers_snapshot, dead_observer_ids, in_batch) = {
2895            let mut prop = match self.inner.write() {
2896                Ok(guard) => guard,
2897                Err(poisoned) => {
2898                    // Graceful degradation: recover from poisoned write lock
2899                    poisoned.into_inner()
2900                }
2901            };
2902
2903            // Check if values are equal using custom equality function if provided
2904            let values_equal = if let Some(eq_fn) = &prop.eq_fn {
2905                eq_fn(&prop.value, &new_value)
2906            } else {
2907                false  // No equality function = always notify
2908            };
2909
2910            // If values are equal, skip everything
2911            if values_equal {
2912                return Ok(());
2913            }
2914
2915            // Check if we're in a batch update
2916            let in_batch = prop.batch_depth > 0;
2917
2918            // Performance optimization: use mem::replace to avoid one clone operation
2919            let old_value = mem::replace(&mut prop.value, new_value.clone());
2920            
2921            // Track the change
2922            prop.total_changes += 1;
2923            let event_num = prop.total_changes - 1; // Capture for event numbering
2924            
2925            // Add old value to history if history tracking is enabled
2926            let history_size = prop.history_size;
2927            if let Some(history) = &mut prop.history {
2928                // Add old value to history
2929                history.push(old_value.clone());
2930                
2931                // Enforce history size limit by removing oldest values
2932                if history.len() > history_size {
2933                    let overflow = history.len() - history_size;
2934                    history.drain(0..overflow);
2935                }
2936            }
2937            
2938            // Record event if event logging is enabled
2939            let event_log_size = prop.event_log_size;
2940            if let Some(event_log) = &mut prop.event_log {
2941                let event = PropertyEvent {
2942                    timestamp: Instant::now(),
2943                    old_value: old_value.clone(),
2944                    new_value: new_value.clone(),
2945                    event_number: event_num, // Use captured event number for consistent numbering
2946                    thread_id: format!("{:?}", thread::current().id()),
2947                };
2948                
2949                event_log.push(event);
2950                
2951                // Enforce event log size limit by removing oldest events (if bounded)
2952                if event_log_size > 0 && event_log.len() > event_log_size {
2953                    let overflow = event_log.len() - event_log_size;
2954                    event_log.drain(0..overflow);
2955                }
2956            }
2957            
2958            // Collect active observers and track dead weak observers (only if not in batch)
2959            let mut observers_snapshot = Vec::new();
2960            let mut dead_ids = Vec::new();
2961            if !in_batch {
2962                for (id, observer_ref) in &prop.observers {
2963                    if let Some(observer) = observer_ref.try_call() {
2964                        observers_snapshot.push(observer);
2965                    } else {
2966                        // Weak observer is dead, mark for removal
2967                        dead_ids.push(*id);
2968                    }
2969                }
2970            }
2971            
2972            (old_value, observers_snapshot, dead_ids, in_batch)
2973        };
2974
2975        // Skip notifications if we're in a batch update
2976        if in_batch {
2977            return Ok(());
2978        }
2979
2980        if observers_snapshot.is_empty() {
2981            // Clean up dead weak observers before returning
2982            if !dead_observer_ids.is_empty() {
2983                let mut prop = match self.inner.write() {
2984                    Ok(guard) => guard,
2985                    Err(poisoned) => poisoned.into_inner(),
2986                };
2987                for id in dead_observer_ids {
2988                    prop.observers.remove(&id);
2989                }
2990            }
2991            return Ok(());
2992        }
2993
2994        let observers_per_thread = observers_snapshot.len().div_ceil(self.max_threads);
2995
2996        // Record metrics for async notifications (time to spawn threads, not execute)
2997        let observer_count = observers_snapshot.len();
2998        
2999        // Fire-and-forget pattern: Spawn threads without joining
3000        // This is intentional for non-blocking behavior. Observers run independently
3001        // and the caller continues immediately without waiting for completion.
3002        // Trade-offs:
3003        //   ✅ Non-blocking, high performance
3004        //   ⚠️ No completion guarantee, no error propagation to caller
3005        for batch in observers_snapshot.chunks(observers_per_thread) {
3006            let batch_observers = batch.to_vec();
3007            let old_val = old_value.clone();
3008            let new_val = new_value.clone();
3009
3010            thread::spawn(move || {
3011                for observer in batch_observers {
3012                    if let Err(e) = panic::catch_unwind(panic::AssertUnwindSafe(|| {
3013                        observer(&old_val, &new_val);
3014                    })) {
3015                        eprintln!("Observer panic in batch thread: {:?}", e);
3016                    }
3017                }
3018            });
3019            // Thread handle intentionally dropped - fire-and-forget pattern
3020        }
3021        
3022        // Record notification time (time to spawn all threads)
3023        let notification_time = notification_start.elapsed();
3024        {
3025            let mut prop = match self.inner.write() {
3026                Ok(guard) => guard,
3027                Err(poisoned) => poisoned.into_inner(),
3028            };
3029            prop.observer_calls += observer_count;
3030            prop.notification_times.push(notification_time);
3031        }
3032
3033        // Clean up dead weak observers
3034        if !dead_observer_ids.is_empty() {
3035            let mut prop = match self.inner.write() {
3036                Ok(guard) => guard,
3037                Err(poisoned) => poisoned.into_inner(),
3038            };
3039            for id in dead_observer_ids {
3040                prop.observers.remove(&id);
3041            }
3042        }
3043
3044        Ok(())
3045    }
3046
3047    /// Begins a batch update, suppressing observer notifications
3048    ///
3049    /// Call this method to start a batch of updates where you want to change
3050    /// the value multiple times but only notify observers once at the end.
3051    /// This is useful for bulk updates where intermediate values don't matter.
3052    ///
3053    /// # Nested Batches
3054    ///
3055    /// This method supports nesting - you can call `begin_update()` multiple times
3056    /// and must call `end_update()` the same number of times. Observers will only
3057    /// be notified when the outermost batch is completed.
3058    ///
3059    /// # Thread Safety
3060    ///
3061    /// Each batch is scoped to the current execution context. If you begin a batch
3062    /// in one thread, it won't affect other threads.
3063    ///
3064    /// # Examples
3065    ///
3066    /// ```rust
3067    /// use observable_property::ObservableProperty;
3068    /// use std::sync::Arc;
3069    ///
3070    /// # fn main() -> Result<(), observable_property::PropertyError> {
3071    /// let property = ObservableProperty::new(0);
3072    ///
3073    /// property.subscribe(Arc::new(|old, new| {
3074    ///     println!("Value changed: {} -> {}", old, new);
3075    /// }))?;
3076    ///
3077    /// // Begin batch update
3078    /// property.begin_update()?;
3079    ///
3080    /// // These changes won't trigger notifications
3081    /// property.set(10)?;
3082    /// property.set(20)?;
3083    /// property.set(30)?;
3084    ///
3085    /// // End batch - single notification from 0 to 30
3086    /// property.end_update()?;
3087    /// # Ok(())
3088    /// # }
3089    /// ```
3090    ///
3091    /// ## Nested Batches
3092    ///
3093    /// ```rust
3094    /// use observable_property::ObservableProperty;
3095    /// use std::sync::Arc;
3096    ///
3097    /// # fn main() -> Result<(), observable_property::PropertyError> {
3098    /// let property = ObservableProperty::new(0);
3099    ///
3100    /// property.subscribe(Arc::new(|old, new| {
3101    ///     println!("Value changed: {} -> {}", old, new);
3102    /// }))?;
3103    ///
3104    /// property.begin_update()?; // Outer batch
3105    /// property.set(5)?;
3106    ///
3107    /// property.begin_update()?; // Inner batch
3108    /// property.set(10)?;
3109    /// property.end_update()?; // End inner batch (no notification yet)
3110    ///
3111    /// property.set(15)?;
3112    /// property.end_update()?; // End outer batch - notification sent: 0 -> 15
3113    /// # Ok(())
3114    /// # }
3115    /// ```
3116    pub fn begin_update(&self) -> Result<(), PropertyError> {
3117        let mut prop = match self.inner.write() {
3118            Ok(guard) => guard,
3119            Err(poisoned) => poisoned.into_inner(),
3120        };
3121
3122        if prop.batch_depth == 0 {
3123            // Store the initial value when starting a new batch
3124            prop.batch_initial_value = Some(prop.value.clone());
3125        }
3126
3127        prop.batch_depth += 1;
3128        Ok(())
3129    }
3130
3131    /// Ends a batch update, sending a single notification with the final value
3132    ///
3133    /// This method completes a batch update started with `begin_update()`. When the
3134    /// outermost batch is completed, observers will be notified once with the value
3135    /// change from the start of the batch to the final value.
3136    ///
3137    /// # Behavior
3138    ///
3139    /// - If the value hasn't changed during the batch, no notification is sent
3140    /// - Supports nested batches - only notifies when all batches are complete
3141    /// - If called without a matching `begin_update()`, returns an error
3142    ///
3143    /// # Examples
3144    ///
3145    /// ```rust
3146    /// use observable_property::ObservableProperty;
3147    /// use std::sync::Arc;
3148    ///
3149    /// # fn main() -> Result<(), observable_property::PropertyError> {
3150    /// let property = ObservableProperty::new(0);
3151    ///
3152    /// property.subscribe(Arc::new(|old, new| {
3153    ///     println!("Value changed: {} -> {}", old, new);
3154    /// }))?;
3155    ///
3156    /// property.begin_update()?;
3157    /// property.set(10)?;
3158    /// property.set(20)?;
3159    /// property.end_update()?; // Prints: "Value changed: 0 -> 20"
3160    /// # Ok(())
3161    /// # }
3162    /// ```
3163    pub fn end_update(&self) -> Result<(), PropertyError> {
3164        let notification_start = Instant::now();
3165        let (should_notify, old_value, new_value, observers_snapshot, dead_observer_ids) = {
3166            let mut prop = match self.inner.write() {
3167                Ok(guard) => guard,
3168                Err(poisoned) => poisoned.into_inner(),
3169            };
3170
3171            if prop.batch_depth == 0 {
3172                return Err(PropertyError::InvalidConfiguration {
3173                    reason: "end_update() called without matching begin_update()".to_string(),
3174                });
3175            }
3176
3177            prop.batch_depth -= 1;
3178
3179            // Only notify when we've exited all nested batches
3180            if prop.batch_depth == 0 {
3181                if let Some(initial_value) = prop.batch_initial_value.take() {
3182                    let current_value = prop.value.clone();
3183                    
3184                    // Collect observers if value changed
3185                    let mut observers_snapshot = Vec::new();
3186                    let mut dead_ids = Vec::new();
3187                    for (id, observer_ref) in &prop.observers {
3188                        if let Some(observer) = observer_ref.try_call() {
3189                            observers_snapshot.push(observer);
3190                        } else {
3191                            dead_ids.push(*id);
3192                        }
3193                    }
3194                    
3195                    (true, initial_value, current_value, observers_snapshot, dead_ids)
3196                } else {
3197                    (false, prop.value.clone(), prop.value.clone(), Vec::new(), Vec::new())
3198                }
3199            } else {
3200                (false, prop.value.clone(), prop.value.clone(), Vec::new(), Vec::new())
3201            }
3202        };
3203
3204        if should_notify && !observers_snapshot.is_empty() {
3205            // Notify all active observers
3206            let observer_count = observers_snapshot.len();
3207            for observer in observers_snapshot {
3208                if let Err(e) = panic::catch_unwind(panic::AssertUnwindSafe(|| {
3209                    observer(&old_value, &new_value);
3210                })) {
3211                    eprintln!("Observer panic: {:?}", e);
3212                }
3213            }
3214            
3215            // Record metrics
3216            let notification_time = notification_start.elapsed();
3217            {
3218                let mut prop = match self.inner.write() {
3219                    Ok(guard) => guard,
3220                    Err(poisoned) => poisoned.into_inner(),
3221                };
3222                prop.observer_calls += observer_count;
3223                prop.notification_times.push(notification_time);
3224            }
3225
3226            // Clean up dead weak observers
3227            if !dead_observer_ids.is_empty() {
3228                let mut prop = match self.inner.write() {
3229                    Ok(guard) => guard,
3230                    Err(poisoned) => poisoned.into_inner(),
3231                };
3232                for id in dead_observer_ids {
3233                    prop.observers.remove(&id);
3234                }
3235            }
3236        }
3237
3238        Ok(())
3239    }
3240
3241    /// Subscribes an observer function to be called when the property changes
3242    ///
3243    /// The observer function will be called with the old and new values whenever
3244    /// the property is modified via `set()` or `set_async()`.
3245    ///
3246    /// # Arguments
3247    ///
3248    /// * `observer` - A function wrapped in `Arc` that takes `(&T, &T)` parameters
3249    ///
3250    /// # Returns
3251    ///
3252    /// `Ok(ObserverId)` containing a unique identifier for this observer,
3253    /// or `Err(PropertyError::InvalidConfiguration)` if the maximum observer limit is exceeded.
3254    ///
3255    /// # Observer Limit
3256    ///
3257    /// To prevent memory exhaustion, there is a maximum limit of observers per property
3258    /// (currently set to 10,000). If you attempt to add more observers than this limit,
3259    /// the subscription will fail with an `InvalidConfiguration` error.
3260    ///
3261    /// This protection helps prevent:
3262    /// - Memory leaks from forgotten unsubscriptions
3263    /// - Unbounded memory growth in long-running applications
3264    /// - Out-of-memory conditions in resource-constrained environments
3265    ///
3266    /// # Examples
3267    ///
3268    /// ```rust
3269    /// use observable_property::ObservableProperty;
3270    /// use std::sync::Arc;
3271    ///
3272    /// let property = ObservableProperty::new(0);
3273    ///
3274    /// let observer_id = property.subscribe(Arc::new(|old_value, new_value| {
3275    ///     println!("Property changed from {} to {}", old_value, new_value);
3276    /// })).map_err(|e| {
3277    ///     eprintln!("Failed to subscribe observer: {}", e);
3278    ///     e
3279    /// })?;
3280    ///
3281    /// // Later, unsubscribe using the returned ID
3282    /// property.unsubscribe(observer_id).map_err(|e| {
3283    ///     eprintln!("Failed to unsubscribe observer: {}", e);
3284    ///     e
3285    /// })?;
3286    /// # Ok::<(), observable_property::PropertyError>(())
3287    /// ```
3288    pub fn subscribe(&self, observer: Observer<T>) -> Result<ObserverId, PropertyError> {
3289        let mut prop = match self.inner.write() {
3290            Ok(guard) => guard,
3291            Err(poisoned) => {
3292                // Graceful degradation: recover from poisoned write lock
3293                poisoned.into_inner()
3294            }
3295        };
3296
3297        // Check observer limit to prevent memory exhaustion
3298        if prop.observers.len() >= self.max_observers {
3299            return Err(PropertyError::InvalidConfiguration {
3300                reason: format!(
3301                    "Maximum observer limit ({}) exceeded. Current observers: {}. \
3302                     Consider unsubscribing unused observers to free resources.",
3303                    self.max_observers,
3304                    prop.observers.len()
3305                ),
3306            });
3307        }
3308
3309        let id = prop.next_id;
3310        // Use wrapping_add to prevent overflow panics in production
3311        // After ~usize::MAX subscriptions, IDs will wrap around
3312        // This is acceptable as old observers are typically unsubscribed
3313        prop.next_id = prop.next_id.wrapping_add(1);
3314        prop.observers.insert(id, ObserverRef::Strong(observer));
3315        Ok(id)
3316    }
3317
3318    /// Removes an observer identified by its ID
3319    ///
3320    /// # Arguments
3321    ///
3322    /// * `id` - The observer ID returned by `subscribe()`
3323    ///
3324    /// # Returns
3325    ///
3326    /// `Ok(bool)` where `true` means the observer was found and removed,
3327    /// `false` means no observer with that ID existed.
3328    /// Returns `Err(PropertyError)` if the lock is poisoned.
3329    ///
3330    /// # Examples
3331    ///
3332    /// ```rust
3333    /// use observable_property::ObservableProperty;
3334    /// use std::sync::Arc;
3335    ///
3336    /// let property = ObservableProperty::new(0);
3337    /// let id = property.subscribe(Arc::new(|_, _| {})).map_err(|e| {
3338    ///     eprintln!("Failed to subscribe: {}", e);
3339    ///     e
3340    /// })?;
3341    ///
3342    /// let was_removed = property.unsubscribe(id).map_err(|e| {
3343    ///     eprintln!("Failed to unsubscribe: {}", e);
3344    ///     e
3345    /// })?;
3346    /// assert!(was_removed); // Observer existed and was removed
3347    ///
3348    /// let was_removed_again = property.unsubscribe(id).map_err(|e| {
3349    ///     eprintln!("Failed to unsubscribe again: {}", e);
3350    ///     e
3351    /// })?;
3352    /// assert!(!was_removed_again); // Observer no longer exists
3353    /// # Ok::<(), observable_property::PropertyError>(())
3354    /// ```
3355    pub fn unsubscribe(&self, id: ObserverId) -> Result<bool, PropertyError> {
3356        let mut prop = match self.inner.write() {
3357            Ok(guard) => guard,
3358            Err(poisoned) => {
3359                // Graceful degradation: recover from poisoned write lock
3360                poisoned.into_inner()
3361            }
3362        };
3363
3364        let was_present = prop.observers.remove(&id).is_some();
3365        Ok(was_present)
3366    }
3367
3368    /// Subscribes a weak observer that automatically cleans up when dropped
3369    ///
3370    /// Unlike `subscribe()` which holds a strong reference to the observer, this method
3371    /// stores only a weak reference. When the observer's `Arc` is dropped elsewhere,
3372    /// the observer will be automatically removed from the property on the next notification.
3373    ///
3374    /// This is useful for scenarios where you want observers to have independent lifetimes
3375    /// without needing explicit unsubscribe calls or `Subscription` guards.
3376    ///
3377    /// # Arguments
3378    ///
3379    /// * `observer` - A weak reference to the observer function
3380    ///
3381    /// # Returns
3382    ///
3383    /// `Ok(ObserverId)` containing a unique identifier for this observer,
3384    /// or `Err(PropertyError::InvalidConfiguration)` if the maximum observer limit is exceeded.
3385    ///
3386    /// # Automatic Cleanup
3387    ///
3388    /// The observer will be automatically removed when:
3389    /// - The `Arc` that the `Weak` was created from is dropped
3390    /// - The next notification occurs (via `set()`, `set_async()`, `modify()`, etc.)
3391    ///
3392    /// # Examples
3393    ///
3394    /// ## Basic Weak Observer
3395    ///
3396    /// ```rust
3397    /// use observable_property::ObservableProperty;
3398    /// use std::sync::Arc;
3399    ///
3400    /// # fn main() -> Result<(), observable_property::PropertyError> {
3401    /// let property = ObservableProperty::new(0);
3402    ///
3403    /// {
3404    ///     // Create observer as trait object
3405    ///     let observer: Arc<dyn Fn(&i32, &i32) + Send + Sync> = Arc::new(|old: &i32, new: &i32| {
3406    ///         println!("Value changed: {} -> {}", old, new);
3407    ///     });
3408    ///     
3409    ///     // Subscribe with a weak reference
3410    ///     property.subscribe_weak(Arc::downgrade(&observer))?;
3411    ///     
3412    ///     property.set(42)?; // Prints: "Value changed: 0 -> 42"
3413    ///     
3414    ///     // When observer Arc goes out of scope, weak reference becomes invalid
3415    /// }
3416    ///
3417    /// // Next set automatically cleans up the dead observer
3418    /// property.set(100)?; // No output - observer was automatically cleaned up
3419    /// # Ok(())
3420    /// # }
3421    /// ```
3422    ///
3423    /// ## Managing Observer Lifetime
3424    ///
3425    /// ```rust
3426    /// use observable_property::ObservableProperty;
3427    /// use std::sync::Arc;
3428    ///
3429    /// # fn main() -> Result<(), observable_property::PropertyError> {
3430    /// let property = ObservableProperty::new(String::from("initial"));
3431    ///
3432    /// // Store the observer Arc somewhere accessible (as trait object)
3433    /// let observer: Arc<dyn Fn(&String, &String) + Send + Sync> = Arc::new(|old: &String, new: &String| {
3434    ///     println!("Text changed: '{}' -> '{}'", old, new);
3435    /// });
3436    ///
3437    /// property.subscribe_weak(Arc::downgrade(&observer))?;
3438    /// property.set(String::from("updated"))?; // Works - observer is alive
3439    ///
3440    /// // Explicitly drop the observer when done
3441    /// drop(observer);
3442    ///
3443    /// property.set(String::from("final"))?; // No output - observer was dropped
3444    /// # Ok(())
3445    /// # }
3446    /// ```
3447    ///
3448    /// ## Multi-threaded Weak Observers
3449    ///
3450    /// ```rust
3451    /// use observable_property::ObservableProperty;
3452    /// use std::sync::Arc;
3453    /// use std::thread;
3454    ///
3455    /// # fn main() -> Result<(), observable_property::PropertyError> {
3456    /// let property = Arc::new(ObservableProperty::new(0));
3457    /// let property_clone = property.clone();
3458    ///
3459    /// // Create observer as trait object
3460    /// let observer: Arc<dyn Fn(&i32, &i32) + Send + Sync> = Arc::new(|old: &i32, new: &i32| {
3461    ///     println!("Thread observer: {} -> {}", old, new);
3462    /// });
3463    ///
3464    /// property.subscribe_weak(Arc::downgrade(&observer))?;
3465    ///
3466    /// let handle = thread::spawn(move || {
3467    ///     property_clone.set(42)
3468    /// });
3469    ///
3470    /// handle.join().unwrap()?; // Prints: "Thread observer: 0 -> 42"
3471    ///
3472    /// // Observer is still alive
3473    /// property.set(100)?; // Prints: "Thread observer: 42 -> 100"
3474    ///
3475    /// // Drop the observer
3476    /// drop(observer);
3477    /// property.set(200)?; // No output
3478    /// # Ok(())
3479    /// # }
3480    /// ```
3481    ///
3482    /// # Comparison with `subscribe()` and `subscribe_with_subscription()`
3483    ///
3484    /// - **`subscribe()`**: Holds strong reference, requires manual `unsubscribe()`
3485    /// - **`subscribe_with_subscription()`**: Holds strong reference, automatic cleanup via RAII guard
3486    /// - **`subscribe_weak()`**: Holds weak reference, cleanup when Arc is dropped elsewhere
3487    ///
3488    /// Use `subscribe_weak()` when:
3489    /// - You want to control observer lifetime independently from subscriptions
3490    /// - You need multiple code paths to potentially drop the observer
3491    /// - You want to avoid keeping observers alive longer than necessary
3492    pub fn subscribe_weak(
3493        &self,
3494        observer: std::sync::Weak<dyn Fn(&T, &T) + Send + Sync>,
3495    ) -> Result<ObserverId, PropertyError> {
3496        let mut prop = match self.inner.write() {
3497            Ok(guard) => guard,
3498            Err(poisoned) => {
3499                // Graceful degradation: recover from poisoned write lock
3500                poisoned.into_inner()
3501            }
3502        };
3503
3504        // Check observer limit to prevent memory exhaustion
3505        if prop.observers.len() >= self.max_observers {
3506            return Err(PropertyError::InvalidConfiguration {
3507                reason: format!(
3508                    "Maximum observer limit ({}) exceeded. Current observers: {}. \
3509                     Consider unsubscribing unused observers to free resources.",
3510                    self.max_observers,
3511                    prop.observers.len()
3512                ),
3513            });
3514        }
3515
3516        let id = prop.next_id;
3517        // Use wrapping_add to prevent overflow panics in production
3518        // After ~usize::MAX subscriptions, IDs will wrap around
3519        // This is acceptable as old observers are typically unsubscribed
3520        prop.next_id = prop.next_id.wrapping_add(1);
3521        prop.observers.insert(id, ObserverRef::Weak(observer));
3522        Ok(id)
3523    }
3524
3525    /// Subscribes an observer that only gets called when a filter condition is met
3526    ///
3527    /// This is useful for observing only specific types of changes, such as
3528    /// when a value increases or crosses a threshold.
3529    ///
3530    /// # Arguments
3531    ///
3532    /// * `observer` - The observer function to call when the filter passes
3533    /// * `filter` - A predicate function that receives `(old_value, new_value)` and returns `bool`
3534    ///
3535    /// # Returns
3536    ///
3537    /// `Ok(ObserverId)` for the filtered observer, or `Err(PropertyError)` if the lock is poisoned.
3538    ///
3539    /// # Examples
3540    ///
3541    /// ```rust
3542    /// use observable_property::ObservableProperty;
3543    /// use std::sync::Arc;
3544    ///
3545    /// let property = ObservableProperty::new(0);
3546    ///
3547    /// // Only notify when value increases
3548    /// let id = property.subscribe_filtered(
3549    ///     Arc::new(|old, new| println!("Value increased: {} -> {}", old, new)),
3550    ///     |old, new| new > old
3551    /// ).map_err(|e| {
3552    ///     eprintln!("Failed to subscribe filtered observer: {}", e);
3553    ///     e
3554    /// })?;
3555    ///
3556    /// property.set(10).map_err(|e| {
3557    ///     eprintln!("Failed to set value: {}", e);
3558    ///     e
3559    /// })?; // Triggers observer (0 -> 10)
3560    /// property.set(5).map_err(|e| {
3561    ///     eprintln!("Failed to set value: {}", e);
3562    ///     e
3563    /// })?;  // Does NOT trigger observer (10 -> 5)
3564    /// property.set(15).map_err(|e| {
3565    ///     eprintln!("Failed to set value: {}", e);
3566    ///     e
3567    /// })?; // Triggers observer (5 -> 15)
3568    /// # Ok::<(), observable_property::PropertyError>(())
3569    /// ```
3570    pub fn subscribe_filtered<F>(
3571        &self,
3572        observer: Observer<T>,
3573        filter: F,
3574    ) -> Result<ObserverId, PropertyError>
3575    where
3576        F: Fn(&T, &T) -> bool + Send + Sync + 'static,
3577    {
3578        let filter = Arc::new(filter);
3579        let filtered_observer = Arc::new(move |old_val: &T, new_val: &T| {
3580            if filter(old_val, new_val) {
3581                observer(old_val, new_val);
3582            }
3583        });
3584
3585        self.subscribe(filtered_observer)
3586    }
3587
3588    /// Subscribes an observer that only gets called after changes stop for a specified duration
3589    ///
3590    /// Debouncing delays observer notifications until a quiet period has passed. Each new
3591    /// change resets the timer. This is useful for expensive operations that shouldn't
3592    /// run on every single change, such as auto-save, search-as-you-type, or form validation.
3593    ///
3594    /// # How It Works
3595    ///
3596    /// When the property changes:
3597    /// 1. A timer starts for the specified `debounce_duration`
3598    /// 2. If another change occurs before the timer expires, the timer resets
3599    /// 3. When the timer finally expires with no new changes, the observer is notified
3600    /// 4. Only the **most recent** change is delivered to the observer
3601    ///
3602    /// # Arguments
3603    ///
3604    /// * `observer` - The observer function to call after the debounce period
3605    /// * `debounce_duration` - How long to wait after the last change before notifying
3606    ///
3607    /// # Returns
3608    ///
3609    /// `Ok(ObserverId)` for the debounced observer, or `Err(PropertyError)` if subscription fails.
3610    ///
3611    /// # Examples
3612    ///
3613    /// ## Auto-Save Example
3614    ///
3615    /// ```rust
3616    /// use observable_property::ObservableProperty;
3617    /// use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
3618    /// use std::time::Duration;
3619    ///
3620    /// # fn main() -> Result<(), observable_property::PropertyError> {
3621    /// let document = ObservableProperty::new("".to_string());
3622    /// let save_count = Arc::new(AtomicUsize::new(0));
3623    /// let count_clone = save_count.clone();
3624    ///
3625    /// // Auto-save only after user stops typing for 500ms
3626    /// document.subscribe_debounced(
3627    ///     Arc::new(move |_old, new| {
3628    ///         count_clone.fetch_add(1, Ordering::SeqCst);
3629    ///         println!("Auto-saving: {}", new);
3630    ///     }),
3631    ///     Duration::from_millis(500)
3632    /// )?;
3633    ///
3634    /// // Rapid changes (user typing)
3635    /// document.set("H".to_string())?;
3636    /// document.set("He".to_string())?;
3637    /// document.set("Hel".to_string())?;
3638    /// document.set("Hell".to_string())?;
3639    /// document.set("Hello".to_string())?;
3640    ///
3641    /// // At this point, no auto-save has occurred yet
3642    /// assert_eq!(save_count.load(Ordering::SeqCst), 0);
3643    ///
3644    /// // Wait for debounce period
3645    /// std::thread::sleep(Duration::from_millis(600));
3646    ///
3647    /// // Now auto-save has occurred exactly once with the final value
3648    /// assert_eq!(save_count.load(Ordering::SeqCst), 1);
3649    /// # Ok(())
3650    /// # }
3651    /// ```
3652    ///
3653    /// ## Search-as-You-Type Example
3654    ///
3655    /// ```rust
3656    /// use observable_property::ObservableProperty;
3657    /// use std::sync::Arc;
3658    /// use std::time::Duration;
3659    ///
3660    /// # fn main() -> Result<(), observable_property::PropertyError> {
3661    /// let search_query = ObservableProperty::new("".to_string());
3662    ///
3663    /// // Only search after user stops typing for 300ms
3664    /// search_query.subscribe_debounced(
3665    ///     Arc::new(|_old, new| {
3666    ///         if !new.is_empty() {
3667    ///             println!("Searching for: {}", new);
3668    ///             // Perform expensive API call here
3669    ///         }
3670    ///     }),
3671    ///     Duration::from_millis(300)
3672    /// )?;
3673    ///
3674    /// // User types quickly - no searches triggered yet
3675    /// search_query.set("r".to_string())?;
3676    /// search_query.set("ru".to_string())?;
3677    /// search_query.set("rus".to_string())?;
3678    /// search_query.set("rust".to_string())?;
3679    ///
3680    /// // Wait for debounce
3681    /// std::thread::sleep(Duration::from_millis(400));
3682    /// // Now search executes once with "rust"
3683    /// # Ok(())
3684    /// # }
3685    /// ```
3686    ///
3687    /// ## Form Validation Example
3688    ///
3689    /// ```rust
3690    /// use observable_property::ObservableProperty;
3691    /// use std::sync::Arc;
3692    /// use std::time::Duration;
3693    ///
3694    /// # fn main() -> Result<(), observable_property::PropertyError> {
3695    /// let email = ObservableProperty::new("".to_string());
3696    ///
3697    /// // Validate email only after user stops typing for 500ms
3698    /// email.subscribe_debounced(
3699    ///     Arc::new(|_old, new| {
3700    ///         if new.contains('@') && new.contains('.') {
3701    ///             println!("✓ Email looks valid");
3702    ///         } else if !new.is_empty() {
3703    ///             println!("✗ Email appears invalid");
3704    ///         }
3705    ///     }),
3706    ///     Duration::from_millis(500)
3707    /// )?;
3708    ///
3709    /// email.set("user".to_string())?;
3710    /// email.set("user@".to_string())?;
3711    /// email.set("user@ex".to_string())?;
3712    /// email.set("user@example".to_string())?;
3713    /// email.set("user@example.com".to_string())?;
3714    ///
3715    /// // Validation only runs once after typing stops
3716    /// std::thread::sleep(Duration::from_millis(600));
3717    /// # Ok(())
3718    /// # }
3719    /// ```
3720    ///
3721    /// # Performance Considerations
3722    ///
3723    /// - Each debounced observer spawns a background thread when changes occur
3724    /// - The thread sleeps for the debounce duration and then checks if it should notify
3725    /// - Multiple rapid changes don't create multiple threads - they just update the pending value
3726    /// - Memory overhead: ~2 Mutex allocations per debounced observer
3727    ///
3728    /// # Thread Safety
3729    ///
3730    /// Debounced observers are fully thread-safe. Multiple threads can trigger changes
3731    /// simultaneously, and the debouncing logic will correctly handle the most recent value.
3732    pub fn subscribe_debounced(
3733        &self,
3734        observer: Observer<T>,
3735        debounce_duration: Duration,
3736    ) -> Result<ObserverId, PropertyError> {
3737        let last_change_time = Arc::new(Mutex::new(Instant::now()));
3738        let pending_values = Arc::new(Mutex::new(None::<(T, T)>));
3739        
3740        let debounced_observer = Arc::new(move |old_val: &T, new_val: &T| {
3741            // Update the last change time and store the values
3742            {
3743                let mut last_time = last_change_time.lock().unwrap();
3744                *last_time = Instant::now();
3745                
3746                let mut pending = pending_values.lock().unwrap();
3747                *pending = Some((old_val.clone(), new_val.clone()));
3748            }
3749            
3750            // Spawn a thread to wait and then notify if no newer changes occurred
3751            let last_change_time_thread = last_change_time.clone();
3752            let pending_values_thread = pending_values.clone();
3753            let observer_thread = observer.clone();
3754            let duration = debounce_duration;
3755            
3756            thread::spawn(move || {
3757                thread::sleep(duration);
3758                
3759                // Check if enough time has passed since the last change
3760                let should_notify = {
3761                    let last_time = last_change_time_thread.lock().unwrap();
3762                    last_time.elapsed() >= duration
3763                };
3764                
3765                if should_notify {
3766                    // Get and clear the pending values
3767                    let values = {
3768                        let mut pending = pending_values_thread.lock().unwrap();
3769                        pending.take()
3770                    };
3771                    
3772                    // Notify the observer with the final values
3773                    if let Some((old, new)) = values {
3774                        let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| {
3775                            observer_thread(&old, &new);
3776                        }));
3777                    }
3778                }
3779            });
3780        });
3781
3782        self.subscribe(debounced_observer)
3783    }
3784
3785    /// Subscribes an observer that gets called at most once per specified duration
3786    ///
3787    /// Throttling ensures that regardless of how frequently the property changes,
3788    /// the observer is notified at most once per `throttle_interval`. The first change
3789    /// triggers an immediate notification, then subsequent changes are rate-limited.
3790    ///
3791    /// # How It Works
3792    ///
3793    /// When the property changes:
3794    /// 1. If enough time has passed since the last notification, notify immediately
3795    /// 2. Otherwise, schedule a notification for after the throttle interval expires
3796    /// 3. During the throttle interval, additional changes update the pending value
3797    ///    but don't trigger additional notifications
3798    ///
3799    /// # Arguments
3800    ///
3801    /// * `observer` - The observer function to call (rate-limited)
3802    /// * `throttle_interval` - Minimum time between observer notifications
3803    ///
3804    /// # Returns
3805    ///
3806    /// `Ok(ObserverId)` for the throttled observer, or `Err(PropertyError)` if subscription fails.
3807    ///
3808    /// # Examples
3809    ///
3810    /// ## Scroll Event Handling
3811    ///
3812    /// ```rust
3813    /// use observable_property::ObservableProperty;
3814    /// use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
3815    /// use std::time::Duration;
3816    ///
3817    /// # fn main() -> Result<(), observable_property::PropertyError> {
3818    /// let scroll_position = ObservableProperty::new(0);
3819    /// let update_count = Arc::new(AtomicUsize::new(0));
3820    /// let count_clone = update_count.clone();
3821    ///
3822    /// // Update UI at most every 100ms, even if scrolling continuously
3823    /// scroll_position.subscribe_throttled(
3824    ///     Arc::new(move |_old, new| {
3825    ///         count_clone.fetch_add(1, Ordering::SeqCst);
3826    ///         println!("Updating UI for scroll position: {}", new);
3827    ///     }),
3828    ///     Duration::from_millis(100)
3829    /// )?;
3830    ///
3831    /// // Rapid scroll events (e.g., 60fps = ~16ms per frame)
3832    /// for i in 1..=20 {
3833    ///     scroll_position.set(i * 10)?;
3834    ///     std::thread::sleep(Duration::from_millis(16));
3835    /// }
3836    ///
3837    /// // UI updates happened less frequently than scroll events
3838    /// let updates = update_count.load(Ordering::SeqCst);
3839    /// assert!(updates < 20); // Throttled to ~100ms intervals
3840    /// assert!(updates > 0);  // But at least some updates occurred
3841    /// # Ok(())
3842    /// # }
3843    /// ```
3844    ///
3845    /// ## Mouse Movement Tracking
3846    ///
3847    /// ```rust
3848    /// use observable_property::ObservableProperty;
3849    /// use std::sync::Arc;
3850    /// use std::time::Duration;
3851    ///
3852    /// # fn main() -> Result<(), observable_property::PropertyError> {
3853    /// let mouse_position = ObservableProperty::new((0, 0));
3854    ///
3855    /// // Track mouse position, but only log every 200ms
3856    /// mouse_position.subscribe_throttled(
3857    ///     Arc::new(|_old, new| {
3858    ///         println!("Mouse at: ({}, {})", new.0, new.1);
3859    ///     }),
3860    ///     Duration::from_millis(200)
3861    /// )?;
3862    ///
3863    /// // Simulate rapid mouse movements
3864    /// for x in 0..50 {
3865    ///     mouse_position.set((x, x * 2))?;
3866    ///     std::thread::sleep(Duration::from_millis(10));
3867    /// }
3868    /// # Ok(())
3869    /// # }
3870    /// ```
3871    ///
3872    /// ## API Rate Limiting
3873    ///
3874    /// ```rust
3875    /// use observable_property::ObservableProperty;
3876    /// use std::sync::Arc;
3877    /// use std::time::Duration;
3878    ///
3879    /// # fn main() -> Result<(), observable_property::PropertyError> {
3880    /// let sensor_reading = ObservableProperty::new(0.0);
3881    ///
3882    /// // Send sensor data to API at most once per second
3883    /// sensor_reading.subscribe_throttled(
3884    ///     Arc::new(|_old, new| {
3885    ///         println!("Sending to API: {:.2}", new);
3886    ///         // Actual API call would go here
3887    ///     }),
3888    ///     Duration::from_secs(1)
3889    /// )?;
3890    ///
3891    /// // High-frequency sensor updates
3892    /// for i in 0..100 {
3893    ///     sensor_reading.set(i as f64 * 0.1)?;
3894    ///     std::thread::sleep(Duration::from_millis(50));
3895    /// }
3896    /// # Ok(())
3897    /// # }
3898    /// ```
3899    ///
3900    /// ## Difference from Debouncing
3901    ///
3902    /// ```rust
3903    /// use observable_property::ObservableProperty;
3904    /// use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
3905    /// use std::time::Duration;
3906    ///
3907    /// # fn main() -> Result<(), observable_property::PropertyError> {
3908    /// let property = ObservableProperty::new(0);
3909    /// let throttle_count = Arc::new(AtomicUsize::new(0));
3910    /// let debounce_count = Arc::new(AtomicUsize::new(0));
3911    ///
3912    /// let throttle_clone = throttle_count.clone();
3913    /// let debounce_clone = debounce_count.clone();
3914    ///
3915    /// // Throttling: Notifies periodically during continuous changes
3916    /// property.subscribe_throttled(
3917    ///     Arc::new(move |_, _| {
3918    ///         throttle_clone.fetch_add(1, Ordering::SeqCst);
3919    ///     }),
3920    ///     Duration::from_millis(100)
3921    /// )?;
3922    ///
3923    /// // Debouncing: Notifies only after changes stop
3924    /// property.subscribe_debounced(
3925    ///     Arc::new(move |_, _| {
3926    ///         debounce_clone.fetch_add(1, Ordering::SeqCst);
3927    ///     }),
3928    ///     Duration::from_millis(100)
3929    /// )?;
3930    ///
3931    /// // Continuous changes for 500ms
3932    /// for i in 1..=50 {
3933    ///     property.set(i)?;
3934    ///     std::thread::sleep(Duration::from_millis(10));
3935    /// }
3936    ///
3937    /// // Wait for debounce to complete
3938    /// std::thread::sleep(Duration::from_millis(150));
3939    ///
3940    /// // Throttled: Multiple notifications during the period
3941    /// assert!(throttle_count.load(Ordering::SeqCst) >= 4);
3942    ///
3943    /// // Debounced: Single notification after changes stopped
3944    /// assert_eq!(debounce_count.load(Ordering::SeqCst), 1);
3945    /// # Ok(())
3946    /// # }
3947    /// ```
3948    ///
3949    /// # Performance Considerations
3950    ///
3951    /// - Throttled observers spawn background threads to handle delayed notifications
3952    /// - First notification is immediate (no delay), subsequent ones are rate-limited
3953    /// - Memory overhead: ~1 Mutex allocation per throttled observer
3954    ///
3955    /// # Thread Safety
3956    ///
3957    /// Throttled observers are fully thread-safe. Multiple threads can trigger changes
3958    /// and the throttling logic will correctly enforce the rate limit.
3959    pub fn subscribe_throttled(
3960        &self,
3961        observer: Observer<T>,
3962        throttle_interval: Duration,
3963    ) -> Result<ObserverId, PropertyError> {
3964        let last_notify_time = Arc::new(Mutex::new(None::<Instant>));
3965        let pending_notification = Arc::new(Mutex::new(None::<(T, T)>));
3966        
3967        let throttled_observer = Arc::new(move |old_val: &T, new_val: &T| {
3968            let should_notify_now = {
3969                let last_time = last_notify_time.lock().unwrap();
3970                match *last_time {
3971                    None => true, // First notification - notify immediately
3972                    Some(last) => last.elapsed() >= throttle_interval,
3973                }
3974            };
3975            
3976            if should_notify_now {
3977                // Notify immediately
3978                {
3979                    let mut last_time = last_notify_time.lock().unwrap();
3980                    *last_time = Some(Instant::now());
3981                }
3982                
3983                let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| {
3984                    observer(old_val, new_val);
3985                }));
3986            } else {
3987                // Schedule a notification for later
3988                {
3989                    let mut pending = pending_notification.lock().unwrap();
3990                    *pending = Some((old_val.clone(), new_val.clone()));
3991                }
3992                
3993                // Check if we need to spawn a thread for the pending notification
3994                let last_notify_time_thread = last_notify_time.clone();
3995                let pending_notification_thread = pending_notification.clone();
3996                let observer_thread = observer.clone();
3997                let interval = throttle_interval;
3998                
3999                thread::spawn(move || {
4000                    // Calculate how long to wait
4001                    let wait_duration = {
4002                        let last_time = last_notify_time_thread.lock().unwrap();
4003                        if let Some(last) = *last_time {
4004                            let elapsed = last.elapsed();
4005                            if elapsed < interval {
4006                                interval - elapsed
4007                            } else {
4008                                Duration::from_millis(0)
4009                            }
4010                        } else {
4011                            Duration::from_millis(0)
4012                        }
4013                    };
4014                    
4015                    if wait_duration > Duration::from_millis(0) {
4016                        thread::sleep(wait_duration);
4017                    }
4018                    
4019                    // Check if we should notify
4020                    let should_notify = {
4021                        let last_time = last_notify_time_thread.lock().unwrap();
4022                        match *last_time {
4023                            Some(last) => last.elapsed() >= interval,
4024                            None => true,
4025                        }
4026                    };
4027                    
4028                    if should_notify {
4029                        // Get and clear pending notification
4030                        let values = {
4031                            let mut pending = pending_notification_thread.lock().unwrap();
4032                            pending.take()
4033                        };
4034                        
4035                        if let Some((old, new)) = values {
4036                            {
4037                                let mut last_time = last_notify_time_thread.lock().unwrap();
4038                                *last_time = Some(Instant::now());
4039                            }
4040                            
4041                            let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| {
4042                                observer_thread(&old, &new);
4043                            }));
4044                        }
4045                    }
4046                });
4047            }
4048        });
4049
4050        self.subscribe(throttled_observer)
4051    }
4052
4053    /// Notifies all observers with a batch of changes
4054    ///
4055    /// This method allows you to trigger observer notifications for multiple
4056    /// value changes efficiently. Unlike individual `set()` calls, this method
4057    /// acquires the observer list once and then notifies all observers with each
4058    /// change in the batch.
4059    ///
4060    /// # Performance Characteristics
4061    ///
4062    /// - **Lock optimization**: Acquires read lock only to snapshot observers, then releases it
4063    /// - **Non-blocking**: Other operations can proceed during observer notifications
4064    /// - **Panic isolation**: Individual observer panics don't affect other observers
4065    ///
4066    /// # Arguments
4067    ///
4068    /// * `changes` - A vector of tuples `(old_value, new_value)` to notify observers about
4069    ///
4070    /// # Returns
4071    ///
4072    /// `Ok(())` if successful. Observer errors are logged but don't cause the method to fail.
4073    ///
4074    /// # Examples
4075    ///
4076    /// ```rust
4077    /// use observable_property::ObservableProperty;
4078    /// use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
4079    ///
4080    /// # fn main() -> Result<(), observable_property::PropertyError> {
4081    /// let property = ObservableProperty::new(0);
4082    /// let call_count = Arc::new(AtomicUsize::new(0));
4083    /// let count_clone = call_count.clone();
4084    ///
4085    /// property.subscribe(Arc::new(move |old, new| {
4086    ///     count_clone.fetch_add(1, Ordering::SeqCst);
4087    ///     println!("Change: {} -> {}", old, new);
4088    /// }))?;
4089    ///
4090    /// // Notify with multiple changes at once
4091    /// property.notify_observers_batch(vec![
4092    ///     (0, 10),
4093    ///     (10, 20),
4094    ///     (20, 30),
4095    /// ])?;
4096    ///
4097    /// assert_eq!(call_count.load(Ordering::SeqCst), 3);
4098    /// # Ok(())
4099    /// # }
4100    /// ```
4101    ///
4102    /// # Note
4103    ///
4104    /// This method does NOT update the property's actual value - it only triggers
4105    /// observer notifications. Use `set()` if you want to update the value and
4106    /// notify observers.
4107    pub fn notify_observers_batch(&self, changes: Vec<(T, T)>) -> Result<(), PropertyError> {
4108        // Acquire lock, clone observers, then release lock immediately
4109        // This prevents blocking other operations during potentially long notification process
4110        let (observers_snapshot, dead_observer_ids) = {
4111            let prop = match self.inner.read() {
4112                Ok(guard) => guard,
4113                Err(poisoned) => {
4114                    // Graceful degradation: recover from poisoned read lock
4115                    poisoned.into_inner()
4116                }
4117            };
4118            
4119            // Collect active observers and track dead weak observers
4120            let mut observers = Vec::new();
4121            let mut dead_ids = Vec::new();
4122            for (id, observer_ref) in &prop.observers {
4123                if let Some(observer) = observer_ref.try_call() {
4124                    observers.push(observer);
4125                } else {
4126                    // Weak observer is dead, mark for removal
4127                    dead_ids.push(*id);
4128                }
4129            }
4130            
4131            (observers, dead_ids)
4132        }; // Lock released here
4133
4134        // Notify observers without holding the lock
4135        for (old_val, new_val) in changes {
4136            for observer in &observers_snapshot {
4137                // Wrap in panic recovery like other notification methods
4138                if let Err(e) = panic::catch_unwind(panic::AssertUnwindSafe(|| {
4139                    observer(&old_val, &new_val);
4140                })) {
4141                    eprintln!("Observer panic in batch notification: {:?}", e);
4142                }
4143            }
4144        }
4145        
4146        // Clean up dead weak observers
4147        if !dead_observer_ids.is_empty() {
4148            let mut prop = match self.inner.write() {
4149                Ok(guard) => guard,
4150                Err(poisoned) => poisoned.into_inner(),
4151            };
4152            for id in dead_observer_ids {
4153                prop.observers.remove(&id);
4154            }
4155        }
4156        
4157        Ok(())
4158    }
4159
4160    /// Subscribes an observer and returns a RAII guard for automatic cleanup
4161    ///
4162    /// This method is similar to `subscribe()` but returns a `Subscription` object
4163    /// that automatically removes the observer when it goes out of scope. This
4164    /// provides a more convenient and safer alternative to manual subscription
4165    /// management.
4166    ///
4167    /// # Arguments
4168    ///
4169    /// * `observer` - A function wrapped in `Arc` that takes `(&T, &T)` parameters
4170    ///
4171    /// # Returns
4172    ///
4173    /// `Ok(Subscription<T>)` containing a RAII guard for the observer,
4174    /// or `Err(PropertyError)` if the lock is poisoned.
4175    ///
4176    /// # Examples
4177    ///
4178    /// ## Basic RAII Subscription
4179    ///
4180    /// ```rust
4181    /// use observable_property::ObservableProperty;
4182    /// use std::sync::Arc;
4183    ///
4184    /// # fn main() -> Result<(), observable_property::PropertyError> {
4185    /// let property = ObservableProperty::new(0);
4186    ///
4187    /// {
4188    ///     let _subscription = property.subscribe_with_subscription(Arc::new(|old, new| {
4189    ///         println!("Value: {} -> {}", old, new);
4190    ///     }))?;
4191    ///
4192    ///     property.set(42)?; // Prints: "Value: 0 -> 42"
4193    ///     property.set(100)?; // Prints: "Value: 42 -> 100"
4194    ///
4195    ///     // Automatic cleanup when _subscription goes out of scope
4196    /// }
4197    ///
4198    /// property.set(200)?; // No output - subscription was cleaned up
4199    /// # Ok(())
4200    /// # }
4201    /// ```
4202    ///
4203    /// ## Comparison with Manual Management
4204    ///
4205    /// ```rust
4206    /// use observable_property::ObservableProperty;
4207    /// use std::sync::Arc;
4208    ///
4209    /// # fn main() -> Result<(), observable_property::PropertyError> {
4210    /// let property = ObservableProperty::new("initial".to_string());
4211    ///
4212    /// // Method 1: Manual subscription management (traditional approach)
4213    /// let observer_id = property.subscribe(Arc::new(|old, new| {
4214    ///     println!("Manual: {} -> {}", old, new);
4215    /// }))?;
4216    ///
4217    /// // Method 2: RAII subscription management (recommended)
4218    /// let _subscription = property.subscribe_with_subscription(Arc::new(|old, new| {
4219    ///     println!("RAII: {} -> {}", old, new);
4220    /// }))?;
4221    ///
4222    /// // Both observers will be called
4223    /// property.set("changed".to_string())?;
4224    /// // Prints:
4225    /// // "Manual: initial -> changed"
4226    /// // "RAII: initial -> changed"
4227    ///
4228    /// // Manual cleanup required for first observer
4229    /// property.unsubscribe(observer_id)?;
4230    ///
4231    /// // Second observer (_subscription) is automatically cleaned up when
4232    /// // the variable goes out of scope - no manual intervention needed
4233    /// # Ok(())
4234    /// # }
4235    /// ```
4236    ///
4237    /// ## Error Handling with Early Returns
4238    ///
4239    /// ```rust
4240    /// use observable_property::ObservableProperty;
4241    /// use std::sync::Arc;
4242    ///
4243    /// fn process_with_monitoring(property: &ObservableProperty<i32>) -> Result<(), observable_property::PropertyError> {
4244    ///     let _monitoring = property.subscribe_with_subscription(Arc::new(|old, new| {
4245    ///         println!("Processing: {} -> {}", old, new);
4246    ///     }))?;
4247    ///
4248    ///     property.set(1)?;
4249    ///     
4250    ///     if property.get()? > 0 {
4251    ///         return Ok(()); // Subscription automatically cleaned up on early return
4252    ///     }
4253    ///
4254    ///     property.set(2)?;
4255    ///     Ok(()) // Subscription automatically cleaned up on normal return
4256    /// }
4257    ///
4258    /// # fn main() -> Result<(), observable_property::PropertyError> {
4259    /// let property = ObservableProperty::new(0);
4260    /// process_with_monitoring(&property)?; // Monitoring active only during function call
4261    /// property.set(99)?; // No monitoring output - subscription was cleaned up
4262    /// # Ok(())
4263    /// # }
4264    /// ```
4265    ///
4266    /// ## Multi-threaded Subscription Management
4267    ///
4268    /// ```rust
4269    /// use observable_property::ObservableProperty;
4270    /// use std::sync::Arc;
4271    /// use std::thread;
4272    ///
4273    /// # fn main() -> Result<(), observable_property::PropertyError> {
4274    /// let property = Arc::new(ObservableProperty::new(0));
4275    /// let property_clone = property.clone();
4276    ///
4277    /// let handle = thread::spawn(move || -> Result<(), observable_property::PropertyError> {
4278    ///     let _subscription = property_clone.subscribe_with_subscription(Arc::new(|old, new| {
4279    ///         println!("Thread observer: {} -> {}", old, new);
4280    ///     }))?;
4281    ///
4282    ///     property_clone.set(42)?; // Prints: "Thread observer: 0 -> 42"
4283    ///     
4284    ///     // Subscription automatically cleaned up when thread ends
4285    ///     Ok(())
4286    /// });
4287    ///
4288    /// handle.join().unwrap()?;
4289    /// property.set(100)?; // No output - thread subscription was cleaned up
4290    /// # Ok(())
4291    /// # }
4292    /// ```
4293    ///
4294    /// # Use Cases
4295    ///
4296    /// This method is particularly useful in scenarios such as:
4297    /// - Temporary observers that should be active only during a specific scope
4298    /// - Error-prone code where manual cleanup might be forgotten
4299    /// - Complex control flow where multiple exit points make manual cleanup difficult
4300    /// - Resource-constrained environments where observer leaks are problematic
4301    pub fn subscribe_with_subscription(
4302        &self,
4303        observer: Observer<T>,
4304    ) -> Result<Subscription<T>, PropertyError> {
4305        let id = self.subscribe(observer)?;
4306        Ok(Subscription {
4307            inner: Arc::clone(&self.inner),
4308            id,
4309        })
4310    }
4311
4312    /// Subscribes a filtered observer and returns a RAII guard for automatic cleanup
4313    ///
4314    /// This method combines the functionality of `subscribe_filtered()` with the automatic
4315    /// cleanup benefits of `subscribe_with_subscription()`. The observer will only be
4316    /// called when the filter condition is satisfied, and it will be automatically
4317    /// unsubscribed when the returned `Subscription` goes out of scope.
4318    ///
4319    /// # Arguments
4320    ///
4321    /// * `observer` - The observer function to call when the filter passes
4322    /// * `filter` - A predicate function that receives `(old_value, new_value)` and returns `bool`
4323    ///
4324    /// # Returns
4325    ///
4326    /// `Ok(Subscription<T>)` containing a RAII guard for the filtered observer,
4327    /// or `Err(PropertyError)` if the lock is poisoned.
4328    ///
4329    /// # Examples
4330    ///
4331    /// ## Basic Filtered RAII Subscription
4332    ///
4333    /// ```rust
4334    /// use observable_property::ObservableProperty;
4335    /// use std::sync::Arc;
4336    ///
4337    /// # fn main() -> Result<(), observable_property::PropertyError> {
4338    /// let counter = ObservableProperty::new(0);
4339    ///
4340    /// {
4341    ///     // Monitor only increases with automatic cleanup
4342    ///     let _increase_monitor = counter.subscribe_filtered_with_subscription(
4343    ///         Arc::new(|old, new| {
4344    ///             println!("Counter increased: {} -> {}", old, new);
4345    ///         }),
4346    ///         |old, new| new > old
4347    ///     )?;
4348    ///
4349    ///     counter.set(5)?;  // Prints: "Counter increased: 0 -> 5"
4350    ///     counter.set(3)?;  // No output (decrease)
4351    ///     counter.set(7)?;  // Prints: "Counter increased: 3 -> 7"
4352    ///
4353    ///     // Subscription automatically cleaned up when leaving scope
4354    /// }
4355    ///
4356    /// counter.set(10)?; // No output - subscription was cleaned up
4357    /// # Ok(())
4358    /// # }
4359    /// ```
4360    ///
4361    /// ## Multi-Condition Temperature Monitoring
4362    ///
4363    /// ```rust
4364    /// use observable_property::ObservableProperty;
4365    /// use std::sync::Arc;
4366    ///
4367    /// # fn main() -> Result<(), observable_property::PropertyError> {
4368    /// let temperature = ObservableProperty::new(20.0_f64);
4369    ///
4370    /// {
4371    ///     // Create filtered subscription that only triggers for significant temperature increases
4372    ///     let _heat_warning = temperature.subscribe_filtered_with_subscription(
4373    ///         Arc::new(|old_temp, new_temp| {
4374    ///             println!("🔥 Heat warning! Temperature rose from {:.1}°C to {:.1}°C",
4375    ///                      old_temp, new_temp);
4376    ///         }),
4377    ///         |old, new| new > old && (new - old) > 5.0  // Only trigger for increases > 5°C
4378    ///     )?;
4379    ///
4380    ///     // Create another filtered subscription for cooling alerts
4381    ///     let _cooling_alert = temperature.subscribe_filtered_with_subscription(
4382    ///         Arc::new(|old_temp, new_temp| {
4383    ///             println!("❄️ Cooling alert! Temperature dropped from {:.1}°C to {:.1}°C",
4384    ///                      old_temp, new_temp);
4385    ///         }),
4386    ///         |old, new| new < old && (old - new) > 3.0  // Only trigger for decreases > 3°C
4387    ///     )?;
4388    ///
4389    ///     // Test the filters
4390    ///     temperature.set(22.0)?; // No alerts (increase of only 2°C)
4391    ///     temperature.set(28.0)?; // Heat warning triggered (increase of 6°C from 22°C)
4392    ///     temperature.set(23.0)?; // Cooling alert triggered (decrease of 5°C)
4393    ///
4394    ///     // Both subscriptions are automatically cleaned up when they go out of scope
4395    /// }
4396    ///
4397    /// temperature.set(35.0)?; // No alerts - subscriptions were cleaned up
4398    /// # Ok(())
4399    /// # }
4400    /// ```
4401    ///
4402    /// ## Conditional Monitoring with Complex Filters
4403    ///
4404    /// ```rust
4405    /// use observable_property::ObservableProperty;
4406    /// use std::sync::Arc;
4407    ///
4408    /// # fn main() -> Result<(), observable_property::PropertyError> {
4409    /// let stock_price = ObservableProperty::new(100.0_f64);
4410    ///
4411    /// {
4412    ///     // Monitor significant price movements (> 5% change)
4413    ///     let _volatility_alert = stock_price.subscribe_filtered_with_subscription(
4414    ///         Arc::new(|old_price, new_price| {
4415    ///             let change_percent = ((new_price - old_price) / old_price * 100.0).abs();
4416    ///             println!("📈 Significant price movement: ${:.2} -> ${:.2} ({:.1}%)",
4417    ///                     old_price, new_price, change_percent);
4418    ///         }),
4419    ///         |old, new| {
4420    ///             let change_percent = ((new - old) / old * 100.0).abs();
4421    ///             change_percent > 5.0  // Trigger on > 5% change
4422    ///         }
4423    ///     )?;
4424    ///
4425    ///     stock_price.set(103.0)?; // No alert (3% change)
4426    ///     stock_price.set(108.0)?; // Alert triggered (4.85% from 103, but let's say it rounds up)
4427    ///     stock_price.set(95.0)?;  // Alert triggered (12% decrease)
4428    ///
4429    ///     // Subscription automatically cleaned up when leaving scope
4430    /// }
4431    ///
4432    /// stock_price.set(200.0)?; // No alert - monitoring ended
4433    /// # Ok(())
4434    /// # }
4435    /// ```
4436    ///
4437    /// ## Cross-Thread Filtered Monitoring
4438    ///
4439    /// ```rust
4440    /// use observable_property::ObservableProperty;
4441    /// use std::sync::Arc;
4442    /// use std::thread;
4443    /// use std::time::Duration;
4444    ///
4445    /// # fn main() -> Result<(), observable_property::PropertyError> {
4446    /// let network_latency = Arc::new(ObservableProperty::new(50)); // milliseconds
4447    /// let latency_clone = network_latency.clone();
4448    ///
4449    /// let monitor_handle = thread::spawn(move || -> Result<(), observable_property::PropertyError> {
4450    ///     // Monitor high latency in background thread with automatic cleanup
4451    ///     let _high_latency_alert = latency_clone.subscribe_filtered_with_subscription(
4452    ///         Arc::new(|old_ms, new_ms| {
4453    ///             println!("⚠️ High latency detected: {}ms -> {}ms", old_ms, new_ms);
4454    ///         }),
4455    ///         |_, new| *new > 100  // Alert when latency exceeds 100ms
4456    ///     )?;
4457    ///
4458    ///     // Simulate monitoring for a short time
4459    ///     thread::sleep(Duration::from_millis(10));
4460    ///     
4461    ///     // Subscription automatically cleaned up when thread ends
4462    ///     Ok(())
4463    /// });
4464    ///
4465    /// // Simulate network conditions
4466    /// network_latency.set(80)?;  // No alert (under threshold)
4467    /// network_latency.set(150)?; // Alert triggered in background thread
4468    ///
4469    /// monitor_handle.join().unwrap()?;
4470    /// network_latency.set(200)?; // No alert - background monitoring ended
4471    /// # Ok(())
4472    /// # }
4473    /// ```
4474    ///
4475    /// # Use Cases
4476    ///
4477    /// This method is ideal for:
4478    /// - Threshold-based monitoring with automatic cleanup
4479    /// - Temporary conditional observers in specific code blocks
4480    /// - Event-driven systems where observers should be active only during certain phases
4481    /// - Resource management scenarios where filtered observers have limited lifetimes
4482    ///
4483    /// # Performance Notes
4484    ///
4485    /// The filter function is evaluated for every property change, so it should be
4486    /// lightweight. Complex filtering logic should be optimized to avoid performance
4487    /// bottlenecks, especially in high-frequency update scenarios.
4488    pub fn subscribe_filtered_with_subscription<F>(
4489        &self,
4490        observer: Observer<T>,
4491        filter: F,
4492    ) -> Result<Subscription<T>, PropertyError>
4493    where
4494        F: Fn(&T, &T) -> bool + Send + Sync + 'static,
4495    {
4496        let id = self.subscribe_filtered(observer, filter)?;
4497        Ok(Subscription {
4498            inner: Arc::clone(&self.inner),
4499            id,
4500        })
4501    }
4502
4503    /// Creates a new observable property with full configuration control
4504    ///
4505    /// This constructor provides complete control over the property's configuration,
4506    /// allowing you to customize both thread pool size and maximum observer count.
4507    ///
4508    /// # Arguments
4509    ///
4510    /// * `initial_value` - The starting value for this property
4511    /// * `max_threads` - Maximum threads for async notifications (0 = use default)
4512    /// * `max_observers` - Maximum number of allowed observers (0 = use default)
4513    ///
4514    /// # Examples
4515    ///
4516    /// ```rust
4517    /// use observable_property::ObservableProperty;
4518    ///
4519    /// // Create a property optimized for high-frequency updates with many observers
4520    /// let property = ObservableProperty::with_config(0, 8, 50000);
4521    /// assert_eq!(property.get().unwrap(), 0);
4522    /// ```
4523    pub fn with_config(initial_value: T, max_threads: usize, max_observers: usize) -> Self {
4524        Self {
4525            inner: Arc::new(RwLock::new(InnerProperty {
4526                value: initial_value,
4527                observers: HashMap::new(),
4528                next_id: 0,
4529                history: None,
4530                history_size: 0,
4531                total_changes: 0,
4532                observer_calls: 0,
4533                notification_times: Vec::new(),
4534                #[cfg(feature = "debug")]
4535                debug_logging_enabled: false,
4536                #[cfg(feature = "debug")]
4537                change_logs: Vec::new(),
4538                batch_depth: 0,
4539                batch_initial_value: None,
4540                eq_fn: None,
4541                validator: None,
4542                event_log: None,
4543                event_log_size: 0,
4544            })),
4545            max_threads: if max_threads == 0 { MAX_THREADS } else { max_threads },
4546            max_observers: if max_observers == 0 { MAX_OBSERVERS } else { max_observers },
4547        }
4548    }
4549
4550    /// Returns the current number of active observers
4551    ///
4552    /// This method is useful for debugging, monitoring, and testing to verify
4553    /// that observers are being properly managed and cleaned up.
4554    ///
4555    /// # Returns
4556    ///
4557    /// The number of currently subscribed observers, or 0 if the lock is poisoned.
4558    ///
4559    /// # Examples
4560    ///
4561    /// ```rust
4562    /// use observable_property::ObservableProperty;
4563    /// use std::sync::Arc;
4564    ///
4565    /// # fn main() -> Result<(), observable_property::PropertyError> {
4566    /// let property = ObservableProperty::new(42);
4567    /// assert_eq!(property.observer_count(), 0);
4568    ///
4569    /// let id1 = property.subscribe(Arc::new(|_, _| {}))?;
4570    /// assert_eq!(property.observer_count(), 1);
4571    ///
4572    /// let id2 = property.subscribe(Arc::new(|_, _| {}))?;
4573    /// assert_eq!(property.observer_count(), 2);
4574    ///
4575    /// property.unsubscribe(id1)?;
4576    /// assert_eq!(property.observer_count(), 1);
4577    /// # Ok(())
4578    /// # }
4579    /// ```
4580    pub fn observer_count(&self) -> usize {
4581        match self.inner.read() {
4582            Ok(prop) => prop.observers.len(),
4583            Err(poisoned) => {
4584                // Graceful degradation: recover from poisoned lock
4585                poisoned.into_inner().observers.len()
4586            }
4587        }
4588    }
4589
4590    /// Gets the current value without Result wrapping
4591    ///
4592    /// This is a convenience method that returns `None` if the lock is poisoned
4593    /// (which shouldn't happen with graceful degradation) instead of a Result.
4594    ///
4595    /// # Returns
4596    ///
4597    /// `Some(T)` containing the current value, or `None` if somehow inaccessible.
4598    ///
4599    /// # Examples
4600    ///
4601    /// ```rust
4602    /// use observable_property::ObservableProperty;
4603    ///
4604    /// let property = ObservableProperty::new(42);
4605    /// assert_eq!(property.try_get(), Some(42));
4606    /// ```
4607    pub fn try_get(&self) -> Option<T> {
4608        self.get().ok()
4609    }
4610
4611    /// Atomically modifies the property value using a closure
4612    ///
4613    /// This method allows you to update the property based on its current value
4614    /// in a single atomic operation. The closure receives a mutable reference to
4615    /// the value and can modify it in place.
4616    ///
4617    /// # Arguments
4618    ///
4619    /// * `f` - A closure that receives `&mut T` and modifies it
4620    ///
4621    /// # Returns
4622    ///
4623    /// `Ok(())` if successful, or `Err(PropertyError)` if the lock is poisoned.
4624    ///
4625    /// # Examples
4626    ///
4627    /// ```rust
4628    /// use observable_property::ObservableProperty;
4629    /// use std::sync::Arc;
4630    ///
4631    /// # fn main() -> Result<(), observable_property::PropertyError> {
4632    /// let counter = ObservableProperty::new(0);
4633    ///
4634    /// counter.subscribe(Arc::new(|old, new| {
4635    ///     println!("Counter: {} -> {}", old, new);
4636    /// }))?;
4637    ///
4638    /// // Increment counter atomically
4639    /// counter.modify(|value| *value += 1)?;
4640    /// assert_eq!(counter.get()?, 1);
4641    ///
4642    /// // Double the counter atomically
4643    /// counter.modify(|value| *value *= 2)?;
4644    /// assert_eq!(counter.get()?, 2);
4645    /// # Ok(())
4646    /// # }
4647    /// ```
4648    pub fn modify<F>(&self, f: F) -> Result<(), PropertyError>
4649    where
4650        F: FnOnce(&mut T),
4651    {
4652        let (old_value, new_value, observers_snapshot, dead_observer_ids) = {
4653            let mut prop = match self.inner.write() {
4654                Ok(guard) => guard,
4655                Err(poisoned) => {
4656                    // Graceful degradation: recover from poisoned write lock
4657                    poisoned.into_inner()
4658                }
4659            };
4660
4661            let old_value = prop.value.clone();
4662            f(&mut prop.value);
4663            let new_value = prop.value.clone();
4664            
4665            // Validate the modified value if a validator is configured
4666            if let Some(validator) = &prop.validator {
4667                validator(&new_value).map_err(|reason| {
4668                    // Restore the old value if validation fails
4669                    prop.value = old_value.clone();
4670                    PropertyError::ValidationError { reason }
4671                })?;
4672            }
4673            
4674            // Check if values are equal using custom equality function if provided
4675            let values_equal = if let Some(eq_fn) = &prop.eq_fn {
4676                eq_fn(&old_value, &new_value)
4677            } else {
4678                false  // No equality function = always notify
4679            };
4680
4681            // If values are equal, skip everything
4682            if values_equal {
4683                return Ok(());
4684            }
4685            
4686            // Track the change
4687            prop.total_changes += 1;
4688            let event_num = prop.total_changes - 1; // Capture for event numbering
4689            
4690            // Add old value to history if history tracking is enabled
4691            let history_size = prop.history_size;
4692            if let Some(history) = &mut prop.history {
4693                // Add old value to history
4694                history.push(old_value.clone());
4695                
4696                // Enforce history size limit by removing oldest values
4697                if history.len() > history_size {
4698                    let overflow = history.len() - history_size;
4699                    history.drain(0..overflow);
4700                }
4701            }
4702            
4703            // Record event if event logging is enabled
4704            let event_log_size = prop.event_log_size;
4705            if let Some(event_log) = &mut prop.event_log {
4706                let event = PropertyEvent {
4707                    timestamp: Instant::now(),
4708                    old_value: old_value.clone(),
4709                    new_value: new_value.clone(),
4710                    event_number: event_num, // Use captured event number for consistent numbering
4711                    thread_id: format!("{:?}", thread::current().id()),
4712                };
4713                
4714                event_log.push(event);
4715                
4716                // Enforce event log size limit by removing oldest events (if bounded)
4717                if event_log_size > 0 && event_log.len() > event_log_size {
4718                    let overflow = event_log.len() - event_log_size;
4719                    event_log.drain(0..overflow);
4720                }
4721            }
4722            
4723            // Collect active observers and track dead weak observers
4724            let mut observers = Vec::new();
4725            let mut dead_ids = Vec::new();
4726            for (id, observer_ref) in &prop.observers {
4727                if let Some(observer) = observer_ref.try_call() {
4728                    observers.push(observer);
4729                } else {
4730                    // Weak observer is dead, mark for removal
4731                    dead_ids.push(*id);
4732                }
4733            }
4734            
4735            (old_value, new_value, observers, dead_ids)
4736        };
4737
4738        // Notify observers with old and new values
4739        for observer in observers_snapshot {
4740            if let Err(e) = panic::catch_unwind(panic::AssertUnwindSafe(|| {
4741                observer(&old_value, &new_value);
4742            })) {
4743                eprintln!("Observer panic in modify: {:?}", e);
4744            }
4745        }
4746
4747        // Clean up dead weak observers
4748        if !dead_observer_ids.is_empty() {
4749            let mut prop = match self.inner.write() {
4750                Ok(guard) => guard,
4751                Err(poisoned) => poisoned.into_inner(),
4752            };
4753            for id in dead_observer_ids {
4754                prop.observers.remove(&id);
4755            }
4756        }
4757
4758        Ok(())
4759    }
4760
4761    /// Creates a derived property that automatically updates when this property changes
4762    ///
4763    /// This method applies a transformation function to create a new `ObservableProperty` of a
4764    /// potentially different type. The derived property automatically updates whenever the source
4765    /// property changes, maintaining the transformation relationship.
4766    ///
4767    /// This enables functional reactive programming patterns and property chaining, similar to
4768    /// `map` operations in functional programming or reactive frameworks.
4769    ///
4770    /// # Type Parameters
4771    ///
4772    /// * `U` - The type of the derived property (must be `Clone + Send + Sync + 'static`)
4773    /// * `F` - The transformation function type
4774    ///
4775    /// # Arguments
4776    ///
4777    /// * `transform` - A function that converts values from type `T` to type `U`
4778    ///
4779    /// # Returns
4780    ///
4781    /// * `Ok(ObservableProperty<U>)` - The derived property with the transformed initial value
4782    /// * `Err(PropertyError)` - If unable to read the source property or create the subscription
4783    ///
4784    /// # Lifetime and Ownership
4785    ///
4786    /// - The derived property remains connected to the source property through an observer subscription
4787    /// - The subscription keeps both properties alive as long as the source has observers
4788    /// - When the derived property is dropped, updates stop, but the source property continues working
4789    /// - The transformation function is called immediately to compute the initial value, then on every change
4790    ///
4791    /// # Examples
4792    ///
4793    /// ## Temperature Conversion (Celsius to Fahrenheit)
4794    ///
4795    /// ```rust
4796    /// use observable_property::ObservableProperty;
4797    /// use std::sync::Arc;
4798    ///
4799    /// # fn main() -> Result<(), observable_property::PropertyError> {
4800    /// // Create a Celsius property
4801    /// let celsius = ObservableProperty::new(20.0);
4802    ///
4803    /// // Derive a Fahrenheit property that auto-updates
4804    /// let fahrenheit = celsius.map(|c| c * 9.0 / 5.0 + 32.0)?;
4805    ///
4806    /// assert_eq!(fahrenheit.get()?, 68.0);
4807    ///
4808    /// // Observe the derived property
4809    /// let _sub = fahrenheit.subscribe_with_subscription(Arc::new(|_old, new| {
4810    ///     println!("Fahrenheit: {:.1}°F", new);
4811    /// }))?;
4812    ///
4813    /// celsius.set(25.0)?;  // Prints: "Fahrenheit: 77.0°F"
4814    /// assert_eq!(fahrenheit.get()?, 77.0);
4815    ///
4816    /// celsius.set(0.0)?;   // Prints: "Fahrenheit: 32.0°F"
4817    /// assert_eq!(fahrenheit.get()?, 32.0);
4818    /// # Ok(())
4819    /// # }
4820    /// ```
4821    ///
4822    /// ## String Formatting
4823    ///
4824    /// ```rust
4825    /// use observable_property::ObservableProperty;
4826    ///
4827    /// # fn main() -> Result<(), observable_property::PropertyError> {
4828    /// let count = ObservableProperty::new(42);
4829    ///
4830    /// // Create a formatted string property
4831    /// let message = count.map(|n| format!("Count: {}", n))?;
4832    ///
4833    /// assert_eq!(message.get()?, "Count: 42");
4834    ///
4835    /// count.set(100)?;
4836    /// assert_eq!(message.get()?, "Count: 100");
4837    /// # Ok(())
4838    /// # }
4839    /// ```
4840    ///
4841    /// ## Mathematical Transformations
4842    ///
4843    /// ```rust
4844    /// use observable_property::ObservableProperty;
4845    ///
4846    /// # fn main() -> Result<(), observable_property::PropertyError> {
4847    /// let radius = ObservableProperty::new(5.0);
4848    ///
4849    /// // Derive area from radius (πr²)
4850    /// let area = radius.map(|r| std::f64::consts::PI * r * r)?;
4851    ///
4852    /// assert!((area.get()? - 78.54).abs() < 0.01);
4853    ///
4854    /// radius.set(10.0)?;
4855    /// assert!((area.get()? - 314.16).abs() < 0.01);
4856    /// # Ok(())
4857    /// # }
4858    /// ```
4859    ///
4860    /// ## Chaining Multiple Transformations
4861    ///
4862    /// ```rust
4863    /// use observable_property::ObservableProperty;
4864    ///
4865    /// # fn main() -> Result<(), observable_property::PropertyError> {
4866    /// let base = ObservableProperty::new(10);
4867    ///
4868    /// // Chain multiple transformations
4869    /// let doubled = base.map(|x| x * 2)?;
4870    /// let squared = doubled.map(|x| x * x)?;
4871    /// let formatted = squared.map(|x| format!("Result: {}", x))?;
4872    ///
4873    /// assert_eq!(formatted.get()?, "Result: 400");
4874    ///
4875    /// base.set(5)?;
4876    /// assert_eq!(formatted.get()?, "Result: 100");  // (5 * 2)² = 100
4877    /// # Ok(())
4878    /// # }
4879    /// ```
4880    ///
4881    /// ## Type Conversion
4882    ///
4883    /// ```rust
4884    /// use observable_property::ObservableProperty;
4885    ///
4886    /// # fn main() -> Result<(), observable_property::PropertyError> {
4887    /// let integer = ObservableProperty::new(42);
4888    ///
4889    /// // Convert integer to float
4890    /// let float_value = integer.map(|i| *i as f64)?;
4891    /// assert_eq!(float_value.get()?, 42.0);
4892    ///
4893    /// // Convert to boolean (is even?)
4894    /// let is_even = integer.map(|i| i % 2 == 0)?;
4895    /// assert_eq!(is_even.get()?, true);
4896    ///
4897    /// integer.set(43)?;
4898    /// assert_eq!(is_even.get()?, false);
4899    /// # Ok(())
4900    /// # }
4901    /// ```
4902    ///
4903    /// ## Complex Object Transformation
4904    ///
4905    /// ```rust
4906    /// use observable_property::ObservableProperty;
4907    ///
4908    /// # fn main() -> Result<(), observable_property::PropertyError> {
4909    /// #[derive(Clone)]
4910    /// struct User {
4911    ///     first_name: String,
4912    ///     last_name: String,
4913    ///     age: u32,
4914    /// }
4915    ///
4916    /// let user = ObservableProperty::new(User {
4917    ///     first_name: "John".to_string(),
4918    ///     last_name: "Doe".to_string(),
4919    ///     age: 30,
4920    /// });
4921    ///
4922    /// // Derive full name from user
4923    /// let full_name = user.map(|u| format!("{} {}", u.first_name, u.last_name))?;
4924    /// assert_eq!(full_name.get()?, "John Doe");
4925    ///
4926    /// // Derive adult status
4927    /// let is_adult = user.map(|u| u.age >= 18)?;
4928    /// assert_eq!(is_adult.get()?, true);
4929    /// # Ok(())
4930    /// # }
4931    /// ```
4932    ///
4933    /// ## Working with Options
4934    ///
4935    /// ```rust
4936    /// use observable_property::ObservableProperty;
4937    ///
4938    /// # fn main() -> Result<(), observable_property::PropertyError> {
4939    /// let optional = ObservableProperty::new(Some(42));
4940    ///
4941    /// // Extract value with default
4942    /// let value_or_zero = optional.map(|opt| opt.unwrap_or(0))?;
4943    /// assert_eq!(value_or_zero.get()?, 42);
4944    ///
4945    /// optional.set(None)?;
4946    /// assert_eq!(value_or_zero.get()?, 0);
4947    /// # Ok(())
4948    /// # }
4949    /// ```
4950    ///
4951    /// # Performance Considerations
4952    ///
4953    /// - The transformation function is called on every change to the source property
4954    /// - Keep transformation functions lightweight for best performance
4955    /// - The derived property maintains its own observer list independent of the source
4956    /// - Cloning the source property is cheap (internal Arc), but each clone shares the same observers
4957    ///
4958    /// # Thread Safety
4959    ///
4960    /// The transformation function must be `Send + Sync + 'static` as it may be called from
4961    /// any thread that modifies the source property. Ensure your transformation logic is thread-safe.
4962    ///
4963    /// # Comparison with `computed()`
4964    ///
4965    /// - `map()` is simpler and works on a single source property
4966    /// - `computed()` can depend on multiple source properties
4967    /// - `map()` is an instance method; `computed()` is a standalone function
4968    /// - For single-source transformations, `map()` is more ergonomic
4969    pub fn map<U, F>(&self, transform: F) -> Result<ObservableProperty<U>, PropertyError>
4970    where
4971        U: Clone + Send + Sync + 'static,
4972        F: Fn(&T) -> U + Send + Sync + 'static,
4973    {
4974        // Get initial value and transform it
4975        let initial_value = self.get()?;
4976        let derived = ObservableProperty::new(transform(&initial_value));
4977        
4978        // Subscribe to source changes and update derived property
4979        let derived_clone = derived.clone();
4980        let transform = Arc::new(transform);
4981        self.subscribe(Arc::new(move |_old, new| {
4982            let transformed = transform(new);
4983            if let Err(e) = derived_clone.set(transformed) {
4984                eprintln!("Failed to update derived property: {}", e);
4985            }
4986        }))?;
4987        
4988        Ok(derived)
4989    }
4990
4991    /// Updates the property through multiple intermediate states and notifies observers for each change
4992    ///
4993    /// This method is useful for animations, multi-step transformations, or any scenario where
4994    /// you want to record and notify observers about intermediate states during a complex update.
4995    /// The provided function receives a mutable reference to the current value and returns a
4996    /// vector of intermediate states. Observers are notified for each transition between states.
4997    ///
4998    /// # Behavior
4999    ///
5000    /// 1. Captures the initial value
5001    /// 2. Calls the provided function with `&mut T` to get intermediate states
5002    /// 3. Updates the property's value to the final state (last intermediate state, or unchanged if empty)
5003    /// 4. Notifies observers for each state transition:
5004    ///    - initial → intermediate\[0\]
5005    ///    - intermediate\[0\] → intermediate\[1\]
5006    ///    - ... → intermediate\[n\]
5007    ///
5008    /// # Arguments
5009    ///
5010    /// * `f` - A closure that receives `&mut T` and returns a vector of intermediate states
5011    ///
5012    /// # Returns
5013    ///
5014    /// `Ok(())` if successful, or `Err(PropertyError)` if the lock is poisoned.
5015    ///
5016    /// # Examples
5017    ///
5018    /// ## Animation with Intermediate States
5019    ///
5020    /// ```rust
5021    /// use observable_property::ObservableProperty;
5022    /// use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
5023    ///
5024    /// # fn main() -> Result<(), observable_property::PropertyError> {
5025    /// let position = ObservableProperty::new(0);
5026    /// let notification_count = Arc::new(AtomicUsize::new(0));
5027    /// let count_clone = notification_count.clone();
5028    ///
5029    /// position.subscribe(Arc::new(move |old, new| {
5030    ///     count_clone.fetch_add(1, Ordering::SeqCst);
5031    ///     println!("Position: {} -> {}", old, new);
5032    /// }))?;
5033    ///
5034    /// // Animate from 0 to 100 in steps of 25
5035    /// position.update_batch(|_current| {
5036    ///     vec![25, 50, 75, 100]
5037    /// })?;
5038    ///
5039    /// // Observers were notified 4 times:
5040    /// // 0 -> 25, 25 -> 50, 50 -> 75, 75 -> 100
5041    /// assert_eq!(notification_count.load(Ordering::SeqCst), 4);
5042    /// assert_eq!(position.get()?, 100);
5043    /// # Ok(())
5044    /// # }
5045    /// ```
5046    ///
5047    /// ## Multi-Step Transformation
5048    ///
5049    /// ```rust
5050    /// use observable_property::ObservableProperty;
5051    /// use std::sync::Arc;
5052    ///
5053    /// # fn main() -> Result<(), observable_property::PropertyError> {
5054    /// let data = ObservableProperty::new(String::from("hello"));
5055    ///
5056    /// data.subscribe(Arc::new(|old, new| {
5057    ///     println!("Transformation: '{}' -> '{}'", old, new);
5058    /// }))?;
5059    ///
5060    /// // Transform through multiple steps
5061    /// data.update_batch(|current| {
5062    ///     let step1 = current.to_uppercase(); // "HELLO"
5063    ///     let step2 = format!("{}!", step1);   // "HELLO!"
5064    ///     let step3 = format!("{} WORLD", step2); // "HELLO! WORLD"
5065    ///     vec![step1, step2, step3]
5066    /// })?;
5067    ///
5068    /// assert_eq!(data.get()?, "HELLO! WORLD");
5069    /// # Ok(())
5070    /// # }
5071    /// ```
5072    ///
5073    /// ## Counter with Intermediate Values
5074    ///
5075    /// ```rust
5076    /// use observable_property::ObservableProperty;
5077    /// use std::sync::Arc;
5078    ///
5079    /// # fn main() -> Result<(), observable_property::PropertyError> {
5080    /// let counter = ObservableProperty::new(0);
5081    ///
5082    /// counter.subscribe(Arc::new(|old, new| {
5083    ///     println!("Count: {} -> {}", old, new);
5084    /// }))?;
5085    ///
5086    /// // Increment with recording intermediate states
5087    /// counter.update_batch(|current| {
5088    ///     *current += 10; // Modify in place (optional)
5089    ///     vec![5, 8, 10] // Intermediate states to report
5090    /// })?;
5091    ///
5092    /// // Final value is the last intermediate state
5093    /// assert_eq!(counter.get()?, 10);
5094    /// # Ok(())
5095    /// # }
5096    /// ```
5097    ///
5098    /// ## Empty Intermediate States
5099    ///
5100    /// ```rust
5101    /// use observable_property::ObservableProperty;
5102    /// use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
5103    ///
5104    /// # fn main() -> Result<(), observable_property::PropertyError> {
5105    /// let value = ObservableProperty::new(42);
5106    /// let was_notified = Arc::new(AtomicBool::new(false));
5107    /// let flag = was_notified.clone();
5108    ///
5109    /// value.subscribe(Arc::new(move |_, _| {
5110    ///     flag.store(true, Ordering::SeqCst);
5111    /// }))?;
5112    ///
5113    /// // No intermediate states - value remains unchanged, no notifications
5114    /// value.update_batch(|current| {
5115    ///     *current = 100; // This modification is ignored
5116    ///     Vec::new() // No intermediate states
5117    /// })?;
5118    ///
5119    /// assert!(!was_notified.load(Ordering::SeqCst));
5120    /// assert_eq!(value.get()?, 42); // Value unchanged
5121    /// # Ok(())
5122    /// # }
5123    /// ```
5124    ///
5125    /// # Performance Considerations
5126    ///
5127    /// - Lock is held during function execution and state collection
5128    /// - All intermediate states are stored in memory before notification
5129    /// - Observers are notified sequentially for each state transition
5130    /// - Consider using `set()` or `modify()` if you don't need intermediate state tracking
5131    ///
5132    /// # Use Cases
5133    ///
5134    /// - **Animations**: Smooth transitions through intermediate visual states
5135    /// - **Progressive calculations**: Show progress through multi-step computations
5136    /// - **State machines**: Record transitions through multiple states
5137    /// - **Debugging**: Track how a value transforms through complex operations
5138    /// - **History tracking**: Maintain a record of transformation steps
5139    pub fn update_batch<F>(&self, f: F) -> Result<(), PropertyError>
5140    where
5141        F: FnOnce(&mut T) -> Vec<T>,
5142    {
5143        let (initial_value, intermediate_states, observers_snapshot, dead_observer_ids) = {
5144            let mut prop = match self.inner.write() {
5145                Ok(guard) => guard,
5146                Err(poisoned) => {
5147                    // Graceful degradation: recover from poisoned write lock
5148                    poisoned.into_inner()
5149                }
5150            };
5151
5152            let initial_value = prop.value.clone();
5153            let states = f(&mut prop.value);
5154            
5155            // Update to the final state if intermediate states were provided
5156            // Otherwise, restore the original value (ignore any in-place modifications)
5157            if let Some(final_state) = states.last() {
5158                prop.value = final_state.clone();
5159                
5160                // Add initial value to history if history tracking is enabled
5161                // Note: We only track the pre-batch initial value, not intermediates
5162                let history_size = prop.history_size;
5163                if let Some(history) = &mut prop.history {
5164                    history.push(initial_value.clone());
5165                    
5166                    // Enforce history size limit by removing oldest values
5167                    if history.len() > history_size {
5168                        let overflow = history.len() - history_size;
5169                        history.drain(0..overflow);
5170                    }
5171                }
5172            } else {
5173                prop.value = initial_value.clone();
5174            }
5175            
5176            // Collect active observers and track dead weak observers
5177            let mut observers = Vec::new();
5178            let mut dead_ids = Vec::new();
5179            for (id, observer_ref) in &prop.observers {
5180                if let Some(observer) = observer_ref.try_call() {
5181                    observers.push(observer);
5182                } else {
5183                    // Weak observer is dead, mark for removal
5184                    dead_ids.push(*id);
5185                }
5186            }
5187            
5188            (initial_value, states, observers, dead_ids)
5189        };
5190
5191        // Notify observers for each state transition
5192        if !intermediate_states.is_empty() {
5193            let mut previous_state = initial_value;
5194            
5195            for current_state in intermediate_states {
5196                for observer in &observers_snapshot {
5197                    if let Err(e) = panic::catch_unwind(panic::AssertUnwindSafe(|| {
5198                        observer(&previous_state, &current_state);
5199                    })) {
5200                        eprintln!("Observer panic in update_batch: {:?}", e);
5201                    }
5202                }
5203                previous_state = current_state;
5204            }
5205        }
5206
5207        // Clean up dead weak observers
5208        if !dead_observer_ids.is_empty() {
5209            let mut prop = match self.inner.write() {
5210                Ok(guard) => guard,
5211                Err(poisoned) => poisoned.into_inner(),
5212            };
5213            for id in dead_observer_ids {
5214                prop.observers.remove(&id);
5215            }
5216        }
5217
5218        Ok(())
5219    }
5220
5221    /// Creates a bidirectional binding between two properties
5222    ///
5223    /// This method establishes a two-way synchronization where changes to either property
5224    /// will automatically update the other. This is particularly useful for model-view
5225    /// synchronization patterns where a UI control and a data model need to stay in sync.
5226    ///
5227    /// # How It Works
5228    ///
5229    /// 1. Each property subscribes to changes in the other
5230    /// 2. When property A changes, property B is updated to match
5231    /// 3. When property B changes, property A is updated to match
5232    /// 4. Infinite loops are prevented by comparing values before updating
5233    ///
5234    /// # Loop Prevention
5235    ///
5236    /// The method uses value comparison to prevent infinite update loops. If the new
5237    /// value equals the current value, no update is triggered. This requires `T` to
5238    /// implement `PartialEq`.
5239    ///
5240    /// # Type Requirements
5241    ///
5242    /// The value type must implement:
5243    /// - `Clone` - For copying values between properties
5244    /// - `PartialEq` - For comparing values to prevent infinite loops
5245    /// - `Send + Sync` - For thread-safe operation
5246    /// - `'static` - For storing in observers
5247    ///
5248    /// # Returns
5249    ///
5250    /// - `Ok(())` if the binding was successfully established
5251    /// - `Err(PropertyError)` if subscription fails (e.g., observer limit exceeded)
5252    ///
5253    /// # Subscription Management
5254    ///
5255    /// The subscriptions created by this method are stored as strong references and will
5256    /// remain active until one of the properties is dropped or the observers are manually
5257    /// unsubscribed. The returned `ObserverId`s can be used to unsubscribe if needed.
5258    ///
5259    /// # Examples
5260    ///
5261    /// ## Basic Two-Way Binding
5262    ///
5263    /// ```rust
5264    /// use observable_property::ObservableProperty;
5265    ///
5266    /// # fn main() -> Result<(), observable_property::PropertyError> {
5267    /// let model = ObservableProperty::new(0);
5268    /// let view = ObservableProperty::new(0);
5269    ///
5270    /// // Establish bidirectional binding
5271    /// model.bind_bidirectional(&view)?;
5272    ///
5273    /// // Update model - view automatically updates
5274    /// model.set(42)?;
5275    /// assert_eq!(view.get()?, 42);
5276    ///
5277    /// // Update view - model automatically updates
5278    /// view.set(100)?;
5279    /// assert_eq!(model.get()?, 100);
5280    /// # Ok(())
5281    /// # }
5282    /// ```
5283    ///
5284    /// ## Model-View Synchronization
5285    ///
5286    /// ```rust
5287    /// use observable_property::ObservableProperty;
5288    /// use std::sync::Arc;
5289    ///
5290    /// # fn main() -> Result<(), observable_property::PropertyError> {
5291    /// // Model representing application state
5292    /// let username = ObservableProperty::new("".to_string());
5293    ///
5294    /// // View representing UI input field
5295    /// let username_field = ObservableProperty::new("".to_string());
5296    ///
5297    /// // Bind them together
5298    /// username.bind_bidirectional(&username_field)?;
5299    ///
5300    /// // Add validation observer on the model
5301    /// username.subscribe(Arc::new(|_old, new| {
5302    ///     if new.len() > 3 {
5303    ///         println!("Valid username: {}", new);
5304    ///     }
5305    /// }))?;
5306    ///
5307    /// // User types in UI field
5308    /// username_field.set("john".to_string())?;
5309    /// // Both properties are now "john", validation observer triggered
5310    /// assert_eq!(username.get()?, "john");
5311    ///
5312    /// // Programmatic model update
5313    /// username.set("alice".to_string())?;
5314    /// // UI field automatically reflects the change
5315    /// assert_eq!(username_field.get()?, "alice");
5316    /// # Ok(())
5317    /// # }
5318    /// ```
5319    ///
5320    /// ## Multiple Property Synchronization
5321    ///
5322    /// ```rust
5323    /// use observable_property::ObservableProperty;
5324    ///
5325    /// # fn main() -> Result<(), observable_property::PropertyError> {
5326    /// let slider_value = ObservableProperty::new(50);
5327    /// let text_input = ObservableProperty::new(50);
5328    /// let display_label = ObservableProperty::new(50);
5329    ///
5330    /// // Create a synchronized group of controls
5331    /// slider_value.bind_bidirectional(&text_input)?;
5332    /// slider_value.bind_bidirectional(&display_label)?;
5333    ///
5334    /// // Update any one of them
5335    /// text_input.set(75)?;
5336    ///
5337    /// // All are synchronized
5338    /// assert_eq!(slider_value.get()?, 75);
5339    /// assert_eq!(text_input.get()?, 75);
5340    /// assert_eq!(display_label.get()?, 75);
5341    /// # Ok(())
5342    /// # }
5343    /// ```
5344    ///
5345    /// ## With Additional Observers
5346    ///
5347    /// ```rust
5348    /// use observable_property::ObservableProperty;
5349    /// use std::sync::Arc;
5350    ///
5351    /// # fn main() -> Result<(), observable_property::PropertyError> {
5352    /// let celsius = ObservableProperty::new(0.0);
5353    /// let fahrenheit = ObservableProperty::new(32.0);
5354    ///
5355    /// // Note: For unit conversion, you'd typically use computed properties
5356    /// // instead of bidirectional binding, but this shows the concept
5357    /// celsius.bind_bidirectional(&fahrenheit)?;
5358    ///
5359    /// // Add logging to observe synchronization
5360    /// celsius.subscribe(Arc::new(|old, new| {
5361    ///     println!("Celsius changed: {:.1}°C -> {:.1}°C", old, new);
5362    /// }))?;
5363    ///
5364    /// fahrenheit.subscribe(Arc::new(|old, new| {
5365    ///     println!("Fahrenheit changed: {:.1}°F -> {:.1}°F", old, new);
5366    /// }))?;
5367    ///
5368    /// celsius.set(100.0)?;
5369    /// // Both properties are now 100.0 (not a real unit conversion!)
5370    /// // Prints:
5371    /// // "Celsius changed: 0.0°C -> 100.0°C"
5372    /// // "Fahrenheit changed: 32.0°F -> 100.0°F"
5373    /// # Ok(())
5374    /// # }
5375    /// ```
5376    ///
5377    /// # Thread Safety
5378    ///
5379    /// The binding is fully thread-safe. Both properties can be updated from any thread,
5380    /// and the synchronization will work correctly across thread boundaries.
5381    ///
5382    /// # Performance Considerations
5383    ///
5384    /// - Each bound property creates two observer subscriptions (one in each direction)
5385    /// - Value comparisons are performed on every update to prevent loops
5386    /// - Consider using computed properties for one-way transformations instead
5387    /// - Binding many properties in a chain may amplify update overhead
5388    ///
5389    /// # Limitations
5390    ///
5391    /// - Both properties must have the same type `T`
5392    /// - Not suitable for complex transformations (use computed properties instead)
5393    /// - Value comparison relies on `PartialEq` implementation quality
5394    /// - Circular update chains with 3+ properties may have propagation delays
5395    pub fn bind_bidirectional(
5396        &self,
5397        other: &ObservableProperty<T>,
5398    ) -> Result<(), PropertyError>
5399    where
5400        T: PartialEq,
5401    {
5402        // Subscribe self to other's changes
5403        // When other changes, update self
5404        let self_inner = Arc::clone(&self.inner);
5405        other.subscribe(Arc::new(move |_old, new| {
5406            // Check if self's current value differs to prevent infinite loop
5407            let should_update = {
5408                match self_inner.read() {
5409                    Ok(prop) => &prop.value != new,
5410                    Err(poisoned) => &poisoned.into_inner().value != new,
5411                }
5412            };
5413
5414            if should_update {
5415                let mut prop = match self_inner.write() {
5416                    Ok(guard) => guard,
5417                    Err(poisoned) => poisoned.into_inner(),
5418                };
5419
5420                let old_value = mem::replace(&mut prop.value, new.clone());
5421
5422                // Add to history if enabled
5423                let history_size = prop.history_size;
5424                if let Some(history) = &mut prop.history {
5425                    history.push(old_value.clone());
5426                    if history.len() > history_size {
5427                        let overflow = history.len() - history_size;
5428                        history.drain(0..overflow);
5429                    }
5430                }
5431
5432                // Collect and notify observers
5433                let mut observers = Vec::new();
5434                for (_id, observer_ref) in &prop.observers {
5435                    if let Some(observer) = observer_ref.try_call() {
5436                        observers.push(observer);
5437                    }
5438                }
5439                
5440                // Release lock before notifying
5441                drop(prop);
5442
5443                for observer in observers {
5444                    if let Err(e) = panic::catch_unwind(panic::AssertUnwindSafe(|| {
5445                        observer(&old_value, new);
5446                    })) {
5447                        eprintln!("Observer panic in bidirectional binding: {:?}", e);
5448                    }
5449                }
5450            }
5451        }))?;
5452
5453        // Subscribe other to self's changes
5454        // When self changes, update other
5455        let other_inner = Arc::clone(&other.inner);
5456        self.subscribe(Arc::new(move |_old, new| {
5457            // Check if other's current value differs to prevent infinite loop
5458            let should_update = {
5459                match other_inner.read() {
5460                    Ok(prop) => &prop.value != new,
5461                    Err(poisoned) => &poisoned.into_inner().value != new,
5462                }
5463            };
5464
5465            if should_update {
5466                let mut prop = match other_inner.write() {
5467                    Ok(guard) => guard,
5468                    Err(poisoned) => poisoned.into_inner(),
5469                };
5470
5471                let old_value = mem::replace(&mut prop.value, new.clone());
5472
5473                // Add to history if enabled
5474                let history_size = prop.history_size;
5475                if let Some(history) = &mut prop.history {
5476                    history.push(old_value.clone());
5477                    if history.len() > history_size {
5478                        let overflow = history.len() - history_size;
5479                        history.drain(0..overflow);
5480                    }
5481                }
5482
5483                // Collect and notify observers
5484                let mut observers = Vec::new();
5485                for (_id, observer_ref) in &prop.observers {
5486                    if let Some(observer) = observer_ref.try_call() {
5487                        observers.push(observer);
5488                    }
5489                }
5490                
5491                // Release lock before notifying
5492                drop(prop);
5493
5494                for observer in observers {
5495                    if let Err(e) = panic::catch_unwind(panic::AssertUnwindSafe(|| {
5496                        observer(&old_value, new);
5497                    })) {
5498                        eprintln!("Observer panic in bidirectional binding: {:?}", e);
5499                    }
5500                }
5501            }
5502        }))?;
5503
5504        Ok(())
5505    }
5506
5507    /// Converts the observable property to an async stream
5508    ///
5509    /// Creates a stream that yields the current value followed by all future values
5510    /// as the property changes. The stream will continue indefinitely until dropped.
5511    ///
5512    /// # Features
5513    ///
5514    /// This method requires the `async` feature to be enabled.
5515    ///
5516    /// # Returns
5517    ///
5518    /// Returns a `PropertyStream<T>` that yields cloned values whenever
5519    /// the property changes.
5520    ///
5521    /// # Note
5522    ///
5523    /// This implementation uses only standard library primitives and does not
5524    /// depend on external async runtimes. The custom `Stream` trait is not
5525    /// compatible with the futures ecosystem.
5526    ///
5527    /// # Examples
5528    ///
5529    /// ```rust,no_run
5530    /// use observable_property::{ObservableProperty, Stream};
5531    /// use std::sync::Arc;
5532    /// use std::pin::Pin;
5533    ///
5534    /// async fn example() -> Result<(), Box<dyn std::error::Error>> {
5535    ///     let property = Arc::new(ObservableProperty::new(0));
5536    ///     let mut stream = Box::pin(property.to_stream());
5537    ///
5538    ///     // Spawn a thread to modify the property
5539    ///     let prop_clone = property.clone();
5540    ///     std::thread::spawn(move || {
5541    ///         std::thread::sleep(std::time::Duration::from_millis(100));
5542    ///         prop_clone.set(42).ok();
5543    ///         std::thread::sleep(std::time::Duration::from_millis(100));
5544    ///         prop_clone.set(100).ok();
5545    ///     });
5546    ///
5547    ///     // Manual polling (you'd typically use an async runtime for this)
5548    ///     // This is just for demonstration
5549    ///     Ok(())
5550    /// }
5551    /// ```
5552    #[cfg(feature = "async")]
5553    pub fn to_stream(&self) -> PropertyStream<T> {
5554        use std::sync::mpsc;
5555        
5556        let (tx, rx) = mpsc::channel::<T>();
5557        let inner = self.inner.clone();
5558
5559        // Get the current value to send as the first item
5560        let current_value = inner
5561            .read()
5562            .or_else(|poisoned| Ok::<_, ()>(poisoned.into_inner()))
5563            .map(|prop| prop.value.clone())
5564            .ok();
5565
5566        // Subscribe to changes and send them to the channel
5567        let subscription_id = self
5568            .subscribe(Arc::new(move |_old, new| {
5569                let _ = tx.send(new.clone());
5570            }))
5571            .ok();
5572
5573        PropertyStream {
5574            rx,
5575            current_value,
5576            subscription_id,
5577            property: self.inner.clone(),
5578            waker: Arc::new(Mutex::new(None)),
5579        }
5580    }
5581}
5582
5583/// A stream that yields values from an ObservableProperty
5584///
5585/// This stream implementation uses only standard library primitives and
5586/// provides a simple async iteration interface.
5587#[cfg(feature = "async")]
5588pub struct PropertyStream<T>
5589where
5590    T: Clone + Send + Sync + 'static,
5591{
5592    rx: std::sync::mpsc::Receiver<T>,
5593    current_value: Option<T>,
5594    subscription_id: Option<ObserverId>,
5595    property: Arc<RwLock<InnerProperty<T>>>,
5596    waker: Arc<Mutex<Option<std::task::Waker>>>,
5597}
5598
5599#[cfg(feature = "async")]
5600impl<T: Clone + Send + Sync + Unpin + 'static> Stream for PropertyStream<T> {
5601    type Item = T;
5602
5603    fn poll_next(
5604        self: Pin<&mut Self>,
5605        cx: &mut Context<'_>,
5606    ) -> Poll<Option<Self::Item>> {
5607        // Get mutable access to self
5608        let this = self.get_mut();
5609
5610        // First, yield the current value if we haven't yet
5611        if let Some(value) = this.current_value.take() {
5612            return Poll::Ready(Some(value));
5613        }
5614
5615        // Try to receive a value from the channel without blocking
5616        match this.rx.try_recv() {
5617            Ok(value) => Poll::Ready(Some(value)),
5618            Err(std::sync::mpsc::TryRecvError::Empty) => {
5619                // Store the waker so the observer can wake us up
5620                if let Ok(mut waker_lock) = this.waker.lock() {
5621                    *waker_lock = Some(cx.waker().clone());
5622                }
5623                Poll::Pending
5624            }
5625            Err(std::sync::mpsc::TryRecvError::Disconnected) => Poll::Ready(None),
5626        }
5627    }
5628}
5629
5630#[cfg(feature = "async")]
5631impl<T: Clone + Send + Sync + 'static> Drop for PropertyStream<T> {
5632    fn drop(&mut self) {
5633        // Clean up subscription when stream is dropped
5634        if let Some(id) = self.subscription_id {
5635            if let Ok(mut prop) = self.property.write().or_else(|poisoned| Ok::<_, ()>(poisoned.into_inner())) {
5636                prop.observers.remove(&id);
5637            }
5638        }
5639    }
5640}
5641
5642#[cfg(feature = "async")]
5643impl<T: Clone + Send + Sync + Unpin + 'static> ObservableProperty<T> {
5644    /// Asynchronously waits for a specific condition to be met
5645    ///
5646    /// This method will await until the predicate function returns `true` for
5647    /// the property value. It checks the current value immediately, and if the
5648    /// predicate is not satisfied, it subscribes to changes and waits.
5649    ///
5650    /// # Features
5651    ///
5652    /// This method requires the `async` feature to be enabled.
5653    ///
5654    /// # Arguments
5655    ///
5656    /// * `predicate` - A function that tests whether the condition is met
5657    ///
5658    /// # Returns
5659    ///
5660    /// Returns the first value that satisfies the predicate.
5661    ///
5662    /// # Examples
5663    ///
5664    /// ```rust,no_run
5665    /// use observable_property::ObservableProperty;
5666    /// use std::sync::Arc;
5667    ///
5668    /// #[tokio::main]
5669    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
5670    ///     let property = Arc::new(ObservableProperty::new(0));
5671    ///
5672    ///     // Spawn a task to modify the property after a delay
5673    ///     let prop_clone = property.clone();
5674    ///     tokio::spawn(async move {
5675    ///         for i in 1..=10 {
5676    ///             tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
5677    ///             prop_clone.set(i).ok();
5678    ///         }
5679    ///     });
5680    ///
5681    ///     // Wait for the property to reach a specific value
5682    ///     let result = property.wait_for(|value| *value >= 5).await;
5683    ///     println!("Property reached: {}", result);
5684    ///     assert!(result >= 5);
5685    ///
5686    ///     Ok(())
5687    /// }
5688    /// ```
5689    ///
5690    /// # Advanced Example: Multiple Conditions
5691    ///
5692    /// ```rust,no_run
5693    /// use observable_property::ObservableProperty;
5694    /// use std::sync::Arc;
5695    ///
5696    /// async fn example() -> Result<(), Box<dyn std::error::Error>> {
5697    ///     let temperature = Arc::new(ObservableProperty::new(20.0));
5698    ///
5699    ///     // Spawn a thread to simulate temperature changes
5700    ///     let temp_clone = temperature.clone();
5701    ///     std::thread::spawn(move || {
5702    ///         for i in 0..20 {
5703    ///             std::thread::sleep(std::time::Duration::from_millis(50));
5704    ///             temp_clone.set(20.0 + i as f64 * 0.5).ok();
5705    ///         }
5706    ///     });
5707    ///
5708    ///     // Wait for critical temperature
5709    ///     let critical = temperature.wait_for(|temp| *temp > 25.0).await;
5710    ///     println!("Critical temperature reached: {:.1}°C", critical);
5711    ///
5712    ///     Ok(())
5713    /// }
5714    /// ```
5715    #[cfg(feature = "async")]
5716    pub fn wait_for<F>(&self, predicate: F) -> WaitForFuture<T, F>
5717    where
5718        F: Fn(&T) -> bool + Send + Sync + Unpin + 'static,
5719    {
5720        WaitForFuture::new(self.inner.clone(), predicate, self)
5721    }
5722}
5723
5724/// A Future that resolves when an ObservableProperty meets a specific condition
5725#[cfg(feature = "async")]
5726pub struct WaitForFuture<T, F>
5727where
5728    T: Clone + Send + Sync + Unpin + 'static,
5729    F: Fn(&T) -> bool + Send + Sync + Unpin + 'static,
5730{
5731    property: Arc<RwLock<InnerProperty<T>>>,
5732    rx: std::sync::mpsc::Receiver<T>,
5733    subscription_id: Option<ObserverId>,
5734    result: Option<T>,
5735    _phantom: std::marker::PhantomData<F>,
5736}
5737
5738#[cfg(feature = "async")]
5739impl<T, F> WaitForFuture<T, F>
5740where
5741    T: Clone + Send + Sync + Unpin + 'static,
5742    F: Fn(&T) -> bool + Send + Sync + Unpin + 'static,
5743{
5744    fn new<P>(property: Arc<RwLock<InnerProperty<T>>>, predicate: F, obs_property: &P) -> Self
5745    where
5746        P: HasInner<T>,
5747    {
5748        // Check current value first
5749        if let Ok(current) = property
5750            .read()
5751            .or_else(|poisoned| Ok::<_, ()>(poisoned.into_inner()))
5752            .map(|prop| prop.value.clone())
5753        {
5754            if predicate(&current) {
5755                // Condition already met - create a dummy channel
5756                let (tx, rx) = std::sync::mpsc::channel();
5757                let _ = tx.send(current.clone());
5758                return Self {
5759                    property,
5760                    rx,
5761                    subscription_id: None,
5762                    result: Some(current),
5763                    _phantom: std::marker::PhantomData,
5764                };
5765            }
5766        }
5767
5768        let predicate = Arc::new(predicate);
5769        let (tx, rx) = std::sync::mpsc::channel();
5770
5771        // Subscribe to changes
5772        let subscription_id = obs_property
5773            .subscribe_internal(Arc::new(move |_old, new| {
5774                if predicate(new) {
5775                    let _ = tx.send(new.clone());
5776                }
5777            }))
5778            .ok();
5779
5780        Self {
5781            property,
5782            rx,
5783            subscription_id,
5784            result: None,
5785            _phantom: std::marker::PhantomData,
5786        }
5787    }
5788}
5789
5790#[cfg(feature = "async")]
5791impl<T, F> std::future::Future for WaitForFuture<T, F>
5792where
5793    T: Clone + Send + Sync + Unpin + 'static,
5794    F: Fn(&T) -> bool + Send + Sync + Unpin + 'static,
5795{
5796    type Output = T;
5797
5798    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
5799        let this = self.get_mut();
5800        
5801        // If we already have a result, return it immediately
5802        if let Some(result) = this.result.take() {
5803            return Poll::Ready(result);
5804        }
5805
5806        // Try to receive without blocking
5807        match this.rx.try_recv() {
5808            Ok(value) => {
5809                // Clean up subscription
5810                if let Some(id) = this.subscription_id.take() {
5811                    if let Ok(mut prop) = this.property.write().or_else(|poisoned| Ok::<_, ()>(poisoned.into_inner())) {
5812                        prop.observers.remove(&id);
5813                    }
5814                }
5815                Poll::Ready(value)
5816            }
5817            Err(std::sync::mpsc::TryRecvError::Empty) => Poll::Pending,
5818            Err(std::sync::mpsc::TryRecvError::Disconnected) => {
5819                // Channel disconnected, try to get current value as fallback
5820                let value = this
5821                    .property
5822                    .read()
5823                    .or_else(|poisoned| Ok::<_, ()>(poisoned.into_inner()))
5824                    .map(|prop| prop.value.clone())
5825                    .unwrap();
5826                Poll::Ready(value)
5827            }
5828        }
5829    }
5830}
5831
5832#[cfg(feature = "async")]
5833impl<T, F> Drop for WaitForFuture<T, F>
5834where
5835    T: Clone + Send + Sync + Unpin + 'static,
5836    F: Fn(&T) -> bool + Send + Sync + Unpin + 'static,
5837{
5838    fn drop(&mut self) {
5839        // Clean up subscription when future is dropped
5840        if let Some(id) = self.subscription_id.take() {
5841            if let Ok(mut prop) = self.property.write().or_else(|poisoned| Ok::<_, ()>(poisoned.into_inner())) {
5842                prop.observers.remove(&id);
5843            }
5844        }
5845    }
5846}
5847
5848// Helper trait to allow WaitForFuture to call subscribe without circular dependencies
5849#[cfg(feature = "async")]
5850trait HasInner<T: Clone + Send + Sync + 'static> {
5851    fn subscribe_internal(&self, observer: Observer<T>) -> Result<ObserverId, PropertyError>;
5852}
5853
5854#[cfg(feature = "async")]
5855impl<T: Clone + Send + Sync + 'static> HasInner<T> for ObservableProperty<T> {
5856    fn subscribe_internal(&self, observer: Observer<T>) -> Result<ObserverId, PropertyError> {
5857        self.subscribe(observer)
5858    }
5859}
5860
5861// Debug feature methods - only available when T implements Debug
5862#[cfg(feature = "debug")]
5863impl<T: Clone + Send + Sync + std::fmt::Debug + 'static> ObservableProperty<T> {
5864    /// Manually logs a property change with stack trace.
5865    ///
5866    /// Captures and stores:
5867    /// - Timestamp of the change
5868    /// - Old and new values (formatted via Debug trait)
5869    /// - Full stack trace at the call site
5870    /// - Thread ID
5871    ///
5872    /// # Examples
5873    ///
5874    /// ```rust
5875    /// # #[cfg(feature = "debug")]
5876    /// # {
5877    /// use observable_property::ObservableProperty;
5878    ///
5879    /// let property = ObservableProperty::new(42);
5880    /// property.enable_change_logging();
5881    ///
5882    /// let old = property.get().expect("Failed to get value");
5883    /// property.set(100).ok();
5884    /// property.log_change(&old, &100, "Updated from main");
5885    ///
5886    /// let logs = property.get_change_logs();
5887    /// assert_eq!(logs.len(), 1);
5888    /// # }
5889    /// ```
5890    pub fn log_change(&self, old_value: &T, new_value: &T, label: &str) {
5891        let mut prop = match self.inner.write() {
5892            Ok(guard) => guard,
5893            Err(poisoned) => poisoned.into_inner(),
5894        };
5895
5896        if !prop.debug_logging_enabled {
5897            return;
5898        }
5899
5900        let backtrace = Backtrace::new();
5901        let thread_id = format!("{:?}", thread::current().id());
5902        let old_value_repr = format!("{:?}", old_value);
5903        let new_value_repr = format!("{:?} ({})", new_value, label);
5904
5905        prop.change_logs.push(ChangeLog {
5906            timestamp: Instant::now(),
5907            old_value_repr,
5908            new_value_repr,
5909            backtrace: format!("{:?}", backtrace),
5910            thread_id,
5911        });
5912    }
5913
5914    /// Enables debug logging of property changes.
5915    ///
5916    /// After calling this method, use `log_change()` to manually record changes
5917    /// with stack traces. Logs can be retrieved with `get_change_logs()`.
5918    ///
5919    /// # Performance Impact
5920    ///
5921    /// Stack trace capture has significant overhead:
5922    /// - ~10-100μs per change (varies by platform and stack depth)
5923    /// - Memory usage grows with change count (no automatic limit)
5924    /// - Only enable during debugging/development
5925    ///
5926    /// # Requirements
5927    ///
5928    /// This method is only available when the `debug` feature is enabled:
5929    /// ```toml
5930    /// [dependencies]
5931    /// observable-property = { version = "0.4", features = ["debug"] }
5932    /// ```
5933    ///
5934    /// Additionally, the type `T` must implement `std::fmt::Debug` for value logging.
5935    ///
5936    /// # Examples
5937    ///
5938    /// ## Basic Debug Logging
5939    /// ```rust
5940    /// # #[cfg(feature = "debug")]
5941    /// # {
5942    /// use observable_property::ObservableProperty;
5943    ///
5944    /// let property = ObservableProperty::new(42);
5945    /// property.enable_change_logging();
5946    ///
5947    /// let old = property.get().expect("Failed to get");
5948    /// property.set(100).ok();
5949    /// property.log_change(&old, &100, "update 1");
5950    ///
5951    /// let old = property.get().expect("Failed to get");
5952    /// property.set(200).ok();
5953    /// property.log_change(&old, &200, "update 2");
5954    ///
5955    /// // Each change is now logged with a stack trace
5956    /// let logs = property.get_change_logs();
5957    /// assert_eq!(logs.len(), 2);
5958    /// # }
5959    /// ```
5960    ///
5961    /// ## Debugging Unexpected Changes
5962    /// ```rust
5963    /// # #[cfg(feature = "debug")]
5964    /// # {
5965    /// use observable_property::ObservableProperty;
5966    /// use std::sync::Arc;
5967    /// use std::thread;
5968    ///
5969    /// let property = Arc::new(ObservableProperty::new(0));
5970    /// property.enable_change_logging();
5971    ///
5972    /// // Multiple threads modifying the property
5973    /// let handles: Vec<_> = (0..3).map(|i| {
5974    ///     let prop = property.clone();
5975    ///     thread::spawn(move || {
5976    ///         let old = prop.get().expect("Failed to get");
5977    ///         let new_val = i * 10;
5978    ///         prop.set(new_val).ok();
5979    ///         prop.log_change(&old, &new_val, "thread update");
5980    ///     })
5981    /// }).collect();
5982    ///
5983    /// for h in handles { h.join().ok(); }
5984    ///
5985    /// // Print detailed logs showing which thread made each change
5986    /// property.print_change_logs();
5987    /// # }
5988    /// ```
5989    ///
5990    /// # See Also
5991    ///
5992    /// - [`disable_change_logging`](#method.disable_change_logging) - Stop capturing logs
5993    /// - [`get_change_logs`](#method.get_change_logs) - Retrieve captured logs
5994    /// - [`print_change_logs`](#method.print_change_logs) - Pretty-print logs to stdout
5995    /// - [`clear_change_logs`](#method.clear_change_logs) - Clear accumulated logs
5996    pub fn enable_change_logging(&self) {
5997        let mut prop = match self.inner.write() {
5998            Ok(guard) => guard,
5999            Err(poisoned) => poisoned.into_inner(),
6000        };
6001        prop.debug_logging_enabled = true;
6002    }
6003
6004    /// Disables debug logging of property changes
6005    ///
6006    /// Stops capturing new change logs, but preserves existing logs.
6007    /// Use `clear_change_logs()` to remove accumulated logs.
6008    ///
6009    /// # Examples
6010    ///
6011    /// ```rust
6012    /// # #[cfg(feature = "debug")]
6013    /// # {
6014    /// use observable_property::ObservableProperty;
6015    ///
6016    /// let property = ObservableProperty::new(42);
6017    /// property.enable_change_logging();
6018    ///
6019    /// let old = property.get().expect("Failed to get");
6020    /// property.set(100).ok();
6021    /// property.log_change(&old, &100, "logged"); // Logged
6022    ///
6023    /// property.disable_change_logging();
6024    ///
6025    /// let old = property.get().expect("Failed to get");
6026    /// property.set(200).ok();
6027    /// property.log_change(&old, &200, "not logged"); // Not logged
6028    ///
6029    /// let logs = property.get_change_logs();
6030    /// assert_eq!(logs.len(), 1); // Only the first change
6031    /// # }
6032    /// ```
6033    pub fn disable_change_logging(&self) {
6034        let mut prop = match self.inner.write() {
6035            Ok(guard) => guard,
6036            Err(poisoned) => poisoned.into_inner(),
6037        };
6038        prop.debug_logging_enabled = false;
6039    }
6040
6041    /// Clears all accumulated change logs
6042    ///
6043    /// Removes all captured change logs from memory. The debug logging
6044    /// enabled/disabled state is not affected.
6045    ///
6046    /// # Examples
6047    ///
6048    /// ```rust
6049    /// # #[cfg(feature = "debug")]
6050    /// # {
6051    /// use observable_property::ObservableProperty;
6052    ///
6053    /// let property = ObservableProperty::new(42);
6054    /// property.enable_change_logging();
6055    ///
6056    /// let old = property.get().expect("Failed to get");
6057    /// property.set(100).ok();
6058    /// property.log_change(&old, &100, "update");
6059    ///
6060    /// assert_eq!(property.get_change_logs().len(), 1);
6061    /// property.clear_change_logs();
6062    /// assert_eq!(property.get_change_logs().len(), 0);
6063    /// # }
6064    /// ```
6065    pub fn clear_change_logs(&self) {
6066        let mut prop = match self.inner.write() {
6067            Ok(guard) => guard,
6068            Err(poisoned) => poisoned.into_inner(),
6069        };
6070        prop.change_logs.clear();
6071    }
6072
6073    /// Retrieves all captured change logs
6074    ///
6075    /// Returns a vector of formatted strings, each containing:
6076    /// - Timestamp (relative to first log)
6077    /// - Thread ID
6078    /// - Old and new values
6079    /// - Full stack trace
6080    ///
6081    /// # Returns
6082    ///
6083    /// A vector of log strings, one per captured change, in chronological order.
6084    /// Returns an empty vector if no changes have been logged.
6085    ///
6086    /// # Examples
6087    ///
6088    /// ```rust
6089    /// # #[cfg(feature = "debug")]
6090    /// # {
6091    /// use observable_property::ObservableProperty;
6092    ///
6093    /// let property = ObservableProperty::new(42);
6094    /// property.enable_change_logging();
6095    ///
6096    /// let old = property.get().expect("Failed to get");
6097    /// property.set(100).ok();
6098    /// property.log_change(&old, &100, "update");
6099    ///
6100    /// for log in property.get_change_logs() {
6101    ///     println!("{}", log);
6102    /// }
6103    /// # }
6104    /// ```
6105    pub fn get_change_logs(&self) -> Vec<String> {
6106        let prop = match self.inner.read() {
6107            Ok(guard) => guard,
6108            Err(poisoned) => poisoned.into_inner(),
6109        };
6110        
6111        if prop.change_logs.is_empty() {
6112            return Vec::new();
6113        }
6114
6115        let first_timestamp = prop.change_logs[0].timestamp;
6116        prop.change_logs.iter().map(|log| {
6117            let elapsed = log.timestamp.duration_since(first_timestamp);
6118            format!(
6119                "[+{:.3}s] Thread: {}\n  {} -> {}\n  Stack trace:\n{}\n",
6120                elapsed.as_secs_f64(),
6121                log.thread_id,
6122                log.old_value_repr,
6123                log.new_value_repr,
6124                log.backtrace
6125            )
6126        }).collect()
6127    }
6128
6129    /// Pretty-prints all change logs to stdout
6130    ///
6131    /// This is a convenience method that prints each log entry in a readable format.
6132    /// Useful for quick debugging in terminals.
6133    ///
6134    /// # Examples
6135    ///
6136    /// ```rust
6137    /// # #[cfg(feature = "debug")]
6138    /// # {
6139    /// use observable_property::ObservableProperty;
6140    ///
6141    /// let property = ObservableProperty::new(42);
6142    /// property.enable_change_logging();
6143    ///
6144    /// let old = property.get().expect("Failed to get");
6145    /// property.set(100).ok();
6146    /// property.log_change(&old, &100, "update 1");
6147    ///
6148    /// let old = property.get().expect("Failed to get");
6149    /// property.set(200).ok();
6150    /// property.log_change(&old, &200, "update 2");
6151    ///
6152    /// // Prints formatted logs to stdout
6153    /// property.print_change_logs();
6154    /// # }
6155    /// ```
6156    pub fn print_change_logs(&self) {
6157        println!("===== Property Change Logs =====");
6158        for log in self.get_change_logs() {
6159            println!("{}", log);
6160        }
6161        println!("================================");
6162    }
6163}
6164
6165
6166impl<T: Clone + Default + Send + Sync + 'static> ObservableProperty<T> {
6167    /// Gets the current value or returns the default if inaccessible
6168    ///
6169    /// This convenience method is only available when `T` implements `Default`.
6170    /// It provides a fallback to `T::default()` if the value cannot be read.
6171    ///
6172    /// # Examples
6173    ///
6174    /// ```rust
6175    /// use observable_property::ObservableProperty;
6176    ///
6177    /// let property = ObservableProperty::new(42);
6178    /// assert_eq!(property.get_or_default(), 42);
6179    ///
6180    /// // Even if somehow inaccessible, returns default
6181    /// let empty_property: ObservableProperty<i32> = ObservableProperty::new(0);
6182    /// assert_eq!(empty_property.get_or_default(), 0);
6183    /// ```
6184    pub fn get_or_default(&self) -> T {
6185        self.get().unwrap_or_default()
6186    }
6187}
6188
6189impl<T: Clone + Send + Sync + 'static> Clone for ObservableProperty<T> {
6190    /// Creates a new reference to the same observable property
6191    ///
6192    /// This creates a new `ObservableProperty` instance that shares the same
6193    /// underlying data with the original. Changes made through either instance
6194    /// will be visible to observers subscribed through both instances.
6195    ///
6196    /// # Examples
6197    ///
6198    /// ```rust
6199    /// use observable_property::ObservableProperty;
6200    /// use std::sync::Arc;
6201    ///
6202    /// let property1 = ObservableProperty::new(42);
6203    /// let property2 = property1.clone();
6204    ///
6205    /// property2.subscribe(Arc::new(|old, new| {
6206    ///     println!("Observer on property2 saw change: {} -> {}", old, new);
6207    /// })).map_err(|e| {
6208    ///     eprintln!("Failed to subscribe: {}", e);
6209    ///     e
6210    /// })?;
6211    ///
6212    /// // This change through property1 will trigger the observer on property2
6213    /// property1.set(100).map_err(|e| {
6214    ///     eprintln!("Failed to set value: {}", e);
6215    ///     e
6216    /// })?;
6217    /// # Ok::<(), observable_property::PropertyError>(())
6218    /// ```
6219    fn clone(&self) -> Self {
6220        Self {
6221            inner: Arc::clone(&self.inner),
6222            max_threads: self.max_threads,
6223            max_observers: self.max_observers,
6224        }
6225    }
6226}
6227
6228impl<T: Clone + std::fmt::Debug + Send + Sync + 'static> std::fmt::Debug for ObservableProperty<T> {
6229    /// Debug implementation that shows the current value if accessible
6230    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6231        match self.get() {
6232            Ok(value) => f
6233                .debug_struct("ObservableProperty")
6234                .field("value", &value)
6235                .field("observers_count", &"[hidden]")
6236                .field("max_threads", &self.max_threads)
6237                .field("max_observers", &self.max_observers)
6238                .finish(),
6239            Err(_) => f
6240                .debug_struct("ObservableProperty")
6241                .field("value", &"[inaccessible]")
6242                .field("observers_count", &"[hidden]")
6243                .field("max_threads", &self.max_threads)
6244                .field("max_observers", &self.max_observers)
6245                .finish(),
6246        }
6247    }
6248}
6249
6250#[cfg(feature = "serde")]
6251impl<T: Clone + Send + Sync + 'static + Serialize> Serialize for ObservableProperty<T> {
6252    /// Serializes only the current value of the property, not the observers
6253    ///
6254    /// # Note
6255    ///
6256    /// This serialization only captures the current value. Observer subscriptions
6257    /// and internal state are not serialized, as they contain function pointers
6258    /// and runtime state that cannot be meaningfully serialized.
6259    ///
6260    /// # Examples
6261    ///
6262    /// ```rust
6263    /// # #[cfg(feature = "serde")] {
6264    /// use observable_property::ObservableProperty;
6265    /// use serde_json;
6266    ///
6267    /// let property = ObservableProperty::new(42);
6268    /// let json = serde_json::to_string(&property).unwrap();
6269    /// assert_eq!(json, "42");
6270    /// # }
6271    /// ```
6272    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
6273    where
6274        S: serde::Serializer,
6275    {
6276        match self.get() {
6277            Ok(value) => value.serialize(serializer),
6278            Err(_) => Err(serde::ser::Error::custom("Failed to read property value")),
6279        }
6280    }
6281}
6282
6283#[cfg(feature = "serde")]
6284impl<'de, T: Clone + Send + Sync + 'static + Deserialize<'de>> Deserialize<'de> for ObservableProperty<T> {
6285    /// Deserializes a value and creates a new ObservableProperty with no observers
6286    ///
6287    /// # Note
6288    ///
6289    /// The deserialized property will have no observers. You'll need to
6290    /// re-establish any subscriptions after deserialization.
6291    ///
6292    /// # Examples
6293    ///
6294    /// ```rust
6295    /// # #[cfg(feature = "serde")] {
6296    /// use observable_property::ObservableProperty;
6297    /// use serde_json;
6298    ///
6299    /// let json = "42";
6300    /// let property: ObservableProperty<i32> = serde_json::from_str(json).unwrap();
6301    /// assert_eq!(property.get().unwrap(), 42);
6302    /// # }
6303    /// ```
6304    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
6305    where
6306        D: serde::Deserializer<'de>,
6307    {
6308        let value = T::deserialize(deserializer)?;
6309        Ok(ObservableProperty::new(value))
6310    }
6311}
6312
6313/// Creates a computed property that automatically recomputes when any dependency changes
6314///
6315/// A computed property is a special type of `ObservableProperty` whose value is derived
6316/// from one or more other observable properties (dependencies). When any dependency changes,
6317/// the computed property automatically recalculates its value and notifies its own observers.
6318///
6319/// # Type Parameters
6320///
6321/// * `T` - The type of the dependency properties
6322/// * `U` - The type of the computed property's value
6323/// * `F` - The compute function type
6324///
6325/// # Arguments
6326///
6327/// * `dependencies` - A vector of `Arc<ObservableProperty<T>>` that this computed property depends on
6328/// * `compute_fn` - A function that takes a slice of current dependency values and returns the computed value
6329///
6330/// # Returns
6331///
6332/// An `Arc<ObservableProperty<U>>` that will automatically update when any dependency changes.
6333/// Returns an error if subscribing to dependencies fails.
6334///
6335/// # How It Works
6336///
6337/// 1. Creates a new `ObservableProperty<U>` with the initial computed value
6338/// 2. Subscribes to each dependency property
6339/// 3. When any dependency changes, recomputes the value using all current dependency values
6340/// 4. Updates the computed property, which triggers its own observers
6341///
6342/// # Examples
6343///
6344/// ## Basic Math Computation
6345///
6346/// ```rust
6347/// use observable_property::{ObservableProperty, computed};
6348/// use std::sync::Arc;
6349///
6350/// # fn main() -> Result<(), observable_property::PropertyError> {
6351/// // Create source properties
6352/// let width = Arc::new(ObservableProperty::new(10));
6353/// let height = Arc::new(ObservableProperty::new(5));
6354///
6355/// // Create computed property for area
6356/// let area = computed(
6357///     vec![width.clone(), height.clone()],
6358///     |values| values[0] * values[1]
6359/// )?;
6360///
6361/// // Initial computed value
6362/// assert_eq!(area.get()?, 50);
6363///
6364/// // Change width - area updates automatically
6365/// width.set(20)?;
6366/// std::thread::sleep(std::time::Duration::from_millis(10));
6367/// assert_eq!(area.get()?, 100);
6368///
6369/// // Change height - area updates automatically
6370/// height.set(8)?;
6371/// std::thread::sleep(std::time::Duration::from_millis(10));
6372/// assert_eq!(area.get()?, 160);
6373/// # Ok(())
6374/// # }
6375/// ```
6376///
6377/// ## String Concatenation
6378///
6379/// ```rust
6380/// use observable_property::{ObservableProperty, computed};
6381/// use std::sync::Arc;
6382///
6383/// # fn main() -> Result<(), observable_property::PropertyError> {
6384/// let first_name = Arc::new(ObservableProperty::new("John".to_string()));
6385/// let last_name = Arc::new(ObservableProperty::new("Doe".to_string()));
6386///
6387/// let full_name = computed(
6388///     vec![first_name.clone(), last_name.clone()],
6389///     |values| format!("{} {}", values[0], values[1])
6390/// )?;
6391///
6392/// assert_eq!(full_name.get()?, "John Doe");
6393///
6394/// first_name.set("Jane".to_string())?;
6395/// std::thread::sleep(std::time::Duration::from_millis(10));
6396/// assert_eq!(full_name.get()?, "Jane Doe");
6397/// # Ok(())
6398/// # }
6399/// ```
6400///
6401/// ## Total Price with Tax
6402///
6403/// ```rust
6404/// use observable_property::{ObservableProperty, computed};
6405/// use std::sync::Arc;
6406///
6407/// # fn main() -> Result<(), observable_property::PropertyError> {
6408/// let subtotal = Arc::new(ObservableProperty::new(100.0_f64));
6409/// let tax_rate = Arc::new(ObservableProperty::new(0.08_f64)); // 8% tax
6410///
6411/// let total = computed(
6412///     vec![subtotal.clone(), tax_rate.clone()],
6413///     |values| values[0] * (1.0 + values[1])
6414/// )?;
6415///
6416/// assert_eq!(total.get()?, 108.0);
6417///
6418/// subtotal.set(200.0)?;
6419/// std::thread::sleep(std::time::Duration::from_millis(10));
6420/// assert_eq!(total.get()?, 216.0);
6421///
6422/// tax_rate.set(0.10)?; // Change to 10%
6423/// std::thread::sleep(std::time::Duration::from_millis(10));
6424/// // Use approximate comparison due to floating point precision
6425/// let result = total.get()?;
6426/// assert!((result - 220.0).abs() < 0.0001);
6427/// # Ok(())
6428/// # }
6429/// ```
6430///
6431/// ## Observing Computed Properties
6432///
6433/// Computed properties are themselves `ObservableProperty` instances, so you can subscribe to them:
6434///
6435/// ```rust
6436/// use observable_property::{ObservableProperty, computed};
6437/// use std::sync::Arc;
6438///
6439/// # fn main() -> Result<(), observable_property::PropertyError> {
6440/// let a = Arc::new(ObservableProperty::new(5));
6441/// let b = Arc::new(ObservableProperty::new(10));
6442///
6443/// let sum = computed(
6444///     vec![a.clone(), b.clone()],
6445///     |values| values[0] + values[1]
6446/// )?;
6447///
6448/// // Subscribe to changes in the computed property
6449/// sum.subscribe(Arc::new(|old, new| {
6450///     println!("Sum changed from {} to {}", old, new);
6451/// }))?;
6452///
6453/// a.set(7)?; // Will print: "Sum changed from 15 to 17"
6454/// std::thread::sleep(std::time::Duration::from_millis(10));
6455/// # Ok(())
6456/// # }
6457/// ```
6458///
6459/// ## Chaining Computed Properties
6460///
6461/// You can create computed properties that depend on other computed properties:
6462///
6463/// ```rust
6464/// use observable_property::{ObservableProperty, computed};
6465/// use std::sync::Arc;
6466///
6467/// # fn main() -> Result<(), observable_property::PropertyError> {
6468/// let celsius = Arc::new(ObservableProperty::new(0.0));
6469///
6470/// // First computed property: Celsius to Fahrenheit
6471/// let fahrenheit = computed(
6472///     vec![celsius.clone()],
6473///     |values| values[0] * 9.0 / 5.0 + 32.0
6474/// )?;
6475///
6476/// // Second computed property: Fahrenheit to Kelvin
6477/// let kelvin = computed(
6478///     vec![fahrenheit.clone()],
6479///     |values| (values[0] - 32.0) * 5.0 / 9.0 + 273.15
6480/// )?;
6481///
6482/// assert_eq!(celsius.get()?, 0.0);
6483/// assert_eq!(fahrenheit.get()?, 32.0);
6484/// assert_eq!(kelvin.get()?, 273.15);
6485///
6486/// celsius.set(100.0)?;
6487/// std::thread::sleep(std::time::Duration::from_millis(10));
6488/// assert_eq!(fahrenheit.get()?, 212.0);
6489/// assert_eq!(kelvin.get()?, 373.15);
6490/// # Ok(())
6491/// # }
6492/// ```
6493///
6494/// # Thread Safety
6495///
6496/// Computed properties are fully thread-safe. Updates happen asynchronously in response to
6497/// dependency changes, and proper synchronization ensures the computed value is always
6498/// based on the current dependency values at the time of computation.
6499///
6500/// # Performance Considerations
6501///
6502/// - The compute function is called every time any dependency changes
6503/// - For expensive computations, consider using `subscribe_debounced` or `subscribe_throttled`
6504///   on the dependencies before computing
6505/// - The computed property uses async notifications, so there may be a small delay between
6506///   a dependency change and the computed value update
6507pub fn computed<T, U, F>(
6508    dependencies: Vec<Arc<ObservableProperty<T>>>,
6509    compute_fn: F,
6510) -> Result<Arc<ObservableProperty<U>>, PropertyError>
6511where
6512    T: Clone + Send + Sync + 'static,
6513    U: Clone + Send + Sync + 'static,
6514    F: Fn(&[T]) -> U + Send + Sync + 'static,
6515{
6516    // Collect initial values from all dependencies
6517    let initial_values: Result<Vec<T>, PropertyError> = 
6518        dependencies.iter().map(|dep| dep.get()).collect();
6519    let initial_values = initial_values?;
6520    
6521    // Compute initial value
6522    let initial_computed = compute_fn(&initial_values);
6523    
6524    // Create the computed property
6525    let computed_property = Arc::new(ObservableProperty::new(initial_computed));
6526    
6527    // Wrap compute_fn in Arc for sharing across multiple subscriptions
6528    let compute_fn = Arc::new(compute_fn);
6529    
6530    // Subscribe to each dependency
6531    for dependency in dependencies.iter() {
6532        let deps_clone = dependencies.clone();
6533        let computed_clone = computed_property.clone();
6534        let compute_fn_clone = compute_fn.clone();
6535        
6536        // Subscribe to this dependency
6537        dependency.subscribe(Arc::new(move |_old, _new| {
6538            // When any dependency changes, collect all current values
6539            let current_values: Result<Vec<T>, PropertyError> = 
6540                deps_clone.iter().map(|dep| dep.get()).collect();
6541            
6542            if let Ok(values) = current_values {
6543                // Recompute the value
6544                let new_computed = compute_fn_clone(&values);
6545                
6546                // Update the computed property
6547                if let Err(e) = computed_clone.set(new_computed) {
6548                    eprintln!("Error updating computed property: {}", e);
6549                }
6550            }
6551        }))?;
6552    }
6553    
6554    Ok(computed_property)
6555}
6556
6557/// Test helper utilities for working with `ObservableProperty` in tests.
6558///
6559/// This module provides convenient functions for testing observable properties,
6560/// including waiting for notifications and collecting changes.
6561#[cfg(test)]
6562pub mod testing {
6563    use super::*;
6564    use std::sync::mpsc::{channel, Receiver};
6565    use std::time::Duration;
6566
6567    /// Blocks the current thread until the next notification from the property.
6568    ///
6569    /// This is useful for synchronizing test code with property changes.
6570    /// Times out after 5 seconds to prevent tests from hanging indefinitely.
6571    ///
6572    /// # Panics
6573    ///
6574    /// Panics if:
6575    /// - The subscription fails
6576    /// - No notification is received within 5 seconds
6577    ///
6578    /// # Example
6579    ///
6580    /// ```
6581    /// use observable_property::{ObservableProperty, testing::await_notification};
6582    /// use std::thread;
6583    ///
6584    /// let property = ObservableProperty::new(0);
6585    ///
6586    /// thread::spawn({
6587    ///     let prop = property.clone();
6588    ///     move || {
6589    ///         thread::sleep(std::time::Duration::from_millis(100));
6590    ///         prop.set(42).unwrap();
6591    ///     }
6592    /// });
6593    ///
6594    /// await_notification(&property);
6595    /// assert_eq!(property.get().unwrap(), 42);
6596    /// ```
6597    pub fn await_notification<T: Clone + Send + Sync + 'static>(
6598        property: &ObservableProperty<T>,
6599    ) {
6600        let (tx, rx) = channel();
6601        
6602        let _subscription = property
6603            .subscribe_with_subscription(Arc::new(move |_old, _new| {
6604                let _ = tx.send(());
6605            }))
6606            .expect("Failed to subscribe for await_notification");
6607        
6608        rx.recv_timeout(Duration::from_secs(5))
6609            .expect("Timeout waiting for notification");
6610    }
6611
6612    /// Records all changes to a property for test assertions.
6613    ///
6614    /// Returns a `ChangeCollector` that captures old and new values
6615    /// whenever the property changes.
6616    ///
6617    /// # Example
6618    ///
6619    /// ```
6620    /// use observable_property::{ObservableProperty, testing::collect_changes};
6621    ///
6622    /// let property = ObservableProperty::new(0);
6623    /// let collector = collect_changes(&property);
6624    ///
6625    /// property.set(1).unwrap();
6626    /// property.set(2).unwrap();
6627    /// property.set(3).unwrap();
6628    ///
6629    /// let changes = collector.changes();
6630    /// assert_eq!(changes.len(), 3);
6631    /// assert_eq!(changes[0], (0, 1));
6632    /// assert_eq!(changes[1], (1, 2));
6633    /// assert_eq!(changes[2], (2, 3));
6634    /// ```
6635    pub fn collect_changes<T: Clone + Send + Sync + 'static>(
6636        property: &ObservableProperty<T>,
6637    ) -> ChangeCollector<T> {
6638        ChangeCollector::new(property)
6639    }
6640
6641    /// Collects changes to an observable property for testing purposes.
6642    ///
6643    /// This struct maintains a subscription to a property and records
6644    /// all changes (old value, new value) in a thread-safe manner.
6645    pub struct ChangeCollector<T: Clone + Send + Sync + 'static> {
6646        receiver: Receiver<(T, T)>,
6647        _subscription: Subscription<T>,
6648    }
6649
6650    impl<T: Clone + Send + Sync + 'static> ChangeCollector<T> {
6651        /// Creates a new `ChangeCollector` that subscribes to the given property.
6652        fn new(property: &ObservableProperty<T>) -> Self {
6653            let (tx, rx) = channel();
6654            
6655            let subscription = property
6656                .subscribe_with_subscription(Arc::new(move |old, new| {
6657                    let _ = tx.send((old.clone(), new.clone()));
6658                }))
6659                .expect("Failed to subscribe for collect_changes");
6660            
6661            ChangeCollector {
6662                receiver: rx,
6663                _subscription: subscription,
6664            }
6665        }
6666
6667        /// Returns all collected changes as a vector of (old_value, new_value) tuples.
6668        ///
6669        /// This method drains all available changes from the internal receiver.
6670        pub fn changes(&self) -> Vec<(T, T)> {
6671            let mut changes = Vec::new();
6672            while let Ok(change) = self.receiver.try_recv() {
6673                changes.push(change);
6674            }
6675            changes
6676        }
6677
6678        /// Waits for at least `count` changes to occur, with a timeout.
6679        ///
6680        /// Returns all collected changes once the count is reached.
6681        /// Times out after 5 seconds.
6682        ///
6683        /// # Panics
6684        ///
6685        /// Panics if the expected number of changes is not received within the timeout.
6686        pub fn wait_for_changes(&self, count: usize) -> Vec<(T, T)> {
6687            let mut changes = Vec::new();
6688            let timeout = Duration::from_secs(5);
6689            let start = std::time::Instant::now();
6690            
6691            while changes.len() < count {
6692                if start.elapsed() > timeout {
6693                    panic!(
6694                        "Timeout waiting for {} changes, only received {}",
6695                        count,
6696                        changes.len()
6697                    );
6698                }
6699                
6700                match self.receiver.recv_timeout(Duration::from_millis(100)) {
6701                    Ok(change) => changes.push(change),
6702                    Err(std::sync::mpsc::RecvTimeoutError::Timeout) => continue,
6703                    Err(e) => panic!("Error receiving change: {}", e),
6704                }
6705            }
6706            
6707            changes
6708        }
6709
6710        /// Returns the total number of changes currently collected.
6711        pub fn count(&self) -> usize {
6712            self.changes().len()
6713        }
6714    }
6715}
6716
6717#[cfg(test)]
6718mod tests {
6719    use super::*;
6720    use std::sync::atomic::{AtomicUsize, Ordering};
6721    use std::time::Duration;
6722
6723    #[test]
6724    fn test_property_creation_and_basic_operations() {
6725        let prop = ObservableProperty::new(42);
6726
6727        // Test initial value
6728        match prop.get() {
6729            Ok(value) => assert_eq!(value, 42),
6730            Err(e) => panic!("Failed to get initial value: {}", e),
6731        }
6732
6733        // Test setting value
6734        if let Err(e) = prop.set(100) {
6735            panic!("Failed to set value: {}", e);
6736        }
6737
6738        match prop.get() {
6739            Ok(value) => assert_eq!(value, 100),
6740            Err(e) => panic!("Failed to get updated value: {}", e),
6741        }
6742    }
6743
6744    #[test]
6745    fn test_observer_subscription_and_notification() {
6746        let prop = ObservableProperty::new("initial".to_string());
6747        let notification_count = Arc::new(AtomicUsize::new(0));
6748        let last_old_value = Arc::new(RwLock::new(String::new()));
6749        let last_new_value = Arc::new(RwLock::new(String::new()));
6750
6751        let count_clone = notification_count.clone();
6752        let old_clone = last_old_value.clone();
6753        let new_clone = last_new_value.clone();
6754
6755        let observer_id = match prop.subscribe(Arc::new(move |old, new| {
6756            count_clone.fetch_add(1, Ordering::SeqCst);
6757            if let Ok(mut old_val) = old_clone.write() {
6758                *old_val = old.clone();
6759            }
6760            if let Ok(mut new_val) = new_clone.write() {
6761                *new_val = new.clone();
6762            }
6763        })) {
6764            Ok(id) => id,
6765            Err(e) => panic!("Failed to subscribe observer: {}", e),
6766        };
6767
6768        // Change value and verify notification
6769        if let Err(e) = prop.set("changed".to_string()) {
6770            panic!("Failed to set property value: {}", e);
6771        }
6772
6773        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
6774
6775        match last_old_value.read() {
6776            Ok(old_val) => assert_eq!(*old_val, "initial"),
6777            Err(e) => panic!("Failed to read old value: {:?}", e),
6778        }
6779
6780        match last_new_value.read() {
6781            Ok(new_val) => assert_eq!(*new_val, "changed"),
6782            Err(e) => panic!("Failed to read new value: {:?}", e),
6783        }
6784
6785        // Test unsubscription
6786        match prop.unsubscribe(observer_id) {
6787            Ok(was_present) => assert!(was_present),
6788            Err(e) => panic!("Failed to unsubscribe observer: {}", e),
6789        }
6790
6791        // Change value again - should not notify
6792        if let Err(e) = prop.set("not_notified".to_string()) {
6793            panic!("Failed to set property value after unsubscribe: {}", e);
6794        }
6795        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
6796    }
6797
6798    #[test]
6799    fn test_filtered_observer() {
6800        let prop = ObservableProperty::new(0i32);
6801        let notification_count = Arc::new(AtomicUsize::new(0));
6802        let count_clone = notification_count.clone();
6803
6804        // Observer only triggered when value increases
6805        let observer_id = match prop.subscribe_filtered(
6806            Arc::new(move |_, _| {
6807                count_clone.fetch_add(1, Ordering::SeqCst);
6808            }),
6809            |old, new| new > old,
6810        ) {
6811            Ok(id) => id,
6812            Err(e) => panic!("Failed to subscribe filtered observer: {}", e),
6813        };
6814
6815        // Should trigger (0 -> 5)
6816        if let Err(e) = prop.set(5) {
6817            panic!("Failed to set property value to 5: {}", e);
6818        }
6819        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
6820
6821        // Should NOT trigger (5 -> 3)
6822        if let Err(e) = prop.set(3) {
6823            panic!("Failed to set property value to 3: {}", e);
6824        }
6825        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
6826
6827        // Should trigger (3 -> 10)
6828        if let Err(e) = prop.set(10) {
6829            panic!("Failed to set property value to 10: {}", e);
6830        }
6831        assert_eq!(notification_count.load(Ordering::SeqCst), 2);
6832
6833        match prop.unsubscribe(observer_id) {
6834            Ok(_) => {}
6835            Err(e) => panic!("Failed to unsubscribe filtered observer: {}", e),
6836        }
6837    }
6838
6839    #[test]
6840    fn test_thread_safety_concurrent_reads() {
6841        let prop = Arc::new(ObservableProperty::new(42i32));
6842        let num_threads = 10;
6843        let reads_per_thread = 100;
6844
6845        let handles: Vec<_> = (0..num_threads)
6846            .map(|_| {
6847                let prop_clone = prop.clone();
6848                thread::spawn(move || {
6849                    for _ in 0..reads_per_thread {
6850                        match prop_clone.get() {
6851                            Ok(value) => assert_eq!(value, 42),
6852                            Err(e) => panic!("Failed to read property value: {}", e),
6853                        }
6854                        thread::sleep(Duration::from_millis(1));
6855                    }
6856                })
6857            })
6858            .collect();
6859
6860        for handle in handles {
6861            if let Err(e) = handle.join() {
6862                panic!("Thread failed to complete: {:?}", e);
6863            }
6864        }
6865    }
6866
6867    #[test]
6868    fn test_async_set_performance() {
6869        let prop = ObservableProperty::new(0i32);
6870        let slow_observer_count = Arc::new(AtomicUsize::new(0));
6871        let count_clone = slow_observer_count.clone();
6872
6873        // Add observer that simulates slow work
6874        let _id = match prop.subscribe(Arc::new(move |_, _| {
6875            thread::sleep(Duration::from_millis(50));
6876            count_clone.fetch_add(1, Ordering::SeqCst);
6877        })) {
6878            Ok(id) => id,
6879            Err(e) => panic!("Failed to subscribe slow observer: {}", e),
6880        };
6881
6882        // Test synchronous set (should be slow)
6883        let start = std::time::Instant::now();
6884        if let Err(e) = prop.set(1) {
6885            panic!("Failed to set property value synchronously: {}", e);
6886        }
6887        let sync_duration = start.elapsed();
6888
6889        // Test asynchronous set (should be fast)
6890        let start = std::time::Instant::now();
6891        if let Err(e) = prop.set_async(2) {
6892            panic!("Failed to set property value asynchronously: {}", e);
6893        }
6894        let async_duration = start.elapsed();
6895
6896        // Async should be much faster than sync
6897        assert!(async_duration < sync_duration);
6898        assert!(async_duration.as_millis() < 10); // Should be very fast
6899
6900        // Wait for async observer to complete
6901        thread::sleep(Duration::from_millis(100));
6902
6903        // Both observers should have been called
6904        assert_eq!(slow_observer_count.load(Ordering::SeqCst), 2);
6905    }
6906
6907    #[test]
6908    fn test_lock_poisoning() {
6909        // Create a property that we'll poison
6910        let prop = Arc::new(ObservableProperty::new(0));
6911        let prop_clone = prop.clone();
6912
6913        // Create a thread that will deliberately poison the lock
6914        let poison_thread = thread::spawn(move || {
6915            // Get write lock and then panic, which will poison the lock
6916            let _guard = prop_clone.inner.write().expect("Failed to acquire write lock for poisoning test");
6917            panic!("Deliberate panic to poison the lock");
6918        });
6919
6920        // Wait for the thread to complete (it will panic)
6921        let _ = poison_thread.join();
6922
6923        // With graceful degradation, operations should succeed even with poisoned locks
6924        // The implementation recovers the inner value using into_inner()
6925        
6926        // get() should succeed by recovering from poisoned lock
6927        match prop.get() {
6928            Ok(value) => assert_eq!(value, 0), // Should recover the value
6929            Err(e) => panic!("get() should succeed with graceful degradation, got error: {:?}", e),
6930        }
6931
6932        // set() should succeed by recovering from poisoned lock
6933        match prop.set(42) {
6934            Ok(_) => {}, // Expected success with graceful degradation
6935            Err(e) => panic!("set() should succeed with graceful degradation, got error: {:?}", e),
6936        }
6937        
6938        // Verify the value was actually set
6939        assert_eq!(prop.get().expect("Failed to get value after set"), 42);
6940
6941        // subscribe() should succeed by recovering from poisoned lock
6942        match prop.subscribe(Arc::new(|_, _| {})) {
6943            Ok(_) => {}, // Expected success with graceful degradation
6944            Err(e) => panic!("subscribe() should succeed with graceful degradation, got error: {:?}", e),
6945        }
6946    }
6947
6948    #[test]
6949    fn test_observer_panic_isolation() {
6950        let prop = ObservableProperty::new(0);
6951        let call_counts = Arc::new(AtomicUsize::new(0));
6952
6953        // First observer will panic
6954        let panic_observer_id = prop
6955            .subscribe(Arc::new(|_, _| {
6956                panic!("This observer deliberately panics");
6957            }))
6958            .expect("Failed to subscribe panic observer");
6959
6960        // Second observer should still be called despite first one panicking
6961        let counts = call_counts.clone();
6962        let normal_observer_id = prop
6963            .subscribe(Arc::new(move |_, _| {
6964                counts.fetch_add(1, Ordering::SeqCst);
6965            }))
6966            .expect("Failed to subscribe normal observer");
6967
6968        // Trigger the observers - this shouldn't panic despite the first observer panicking
6969        prop.set(42).expect("Failed to set property value");
6970
6971        // Verify the second observer was still called
6972        assert_eq!(call_counts.load(Ordering::SeqCst), 1);
6973
6974        // Clean up
6975        prop.unsubscribe(panic_observer_id).expect("Failed to unsubscribe panic observer");
6976        prop.unsubscribe(normal_observer_id).expect("Failed to unsubscribe normal observer");
6977    }
6978
6979    #[test]
6980    fn test_unsubscribe_nonexistent_observer() {
6981        let property = ObservableProperty::new(0);
6982
6983        // Generate a valid observer ID
6984        let valid_id = property.subscribe(Arc::new(|_, _| {})).expect("Failed to subscribe test observer");
6985
6986        // Create an ID that doesn't exist (valid_id + 1000 should not exist)
6987        let nonexistent_id = valid_id + 1000;
6988
6989        // Test unsubscribing a nonexistent observer
6990        match property.unsubscribe(nonexistent_id) {
6991            Ok(was_present) => {
6992                assert!(
6993                    !was_present,
6994                    "Unsubscribe should return false for nonexistent ID"
6995                );
6996            }
6997            Err(e) => panic!("Unsubscribe returned error: {:?}", e),
6998        }
6999
7000        // Also verify that unsubscribing twice returns false the second time
7001        property.unsubscribe(valid_id).expect("Failed first unsubscribe"); // First unsubscribe should return true
7002
7003        let result = property.unsubscribe(valid_id).expect("Failed second unsubscribe");
7004        assert!(!result, "Second unsubscribe should return false");
7005    }
7006
7007    #[test]
7008    fn test_observer_id_wraparound() {
7009        let prop = ObservableProperty::new(0);
7010
7011        // Test that observer IDs increment properly and don't wrap around unexpectedly
7012        let id1 = prop.subscribe(Arc::new(|_, _| {})).expect("Failed to subscribe observer 1");
7013        let id2 = prop.subscribe(Arc::new(|_, _| {})).expect("Failed to subscribe observer 2");
7014        let id3 = prop.subscribe(Arc::new(|_, _| {})).expect("Failed to subscribe observer 3");
7015
7016        assert!(id2 > id1, "Observer IDs should increment");
7017        assert!(id3 > id2, "Observer IDs should continue incrementing");
7018        assert_eq!(id2, id1 + 1, "Observer IDs should increment by 1");
7019        assert_eq!(id3, id2 + 1, "Observer IDs should increment by 1");
7020
7021        // Clean up
7022        prop.unsubscribe(id1).expect("Failed to unsubscribe observer 1");
7023        prop.unsubscribe(id2).expect("Failed to unsubscribe observer 2");
7024        prop.unsubscribe(id3).expect("Failed to unsubscribe observer 3");
7025    }
7026
7027    #[test]
7028    fn test_concurrent_subscribe_unsubscribe() {
7029        let prop = Arc::new(ObservableProperty::new(0));
7030        let num_threads = 8;
7031        let operations_per_thread = 100;
7032
7033        let handles: Vec<_> = (0..num_threads)
7034            .map(|thread_id| {
7035                let prop_clone = prop.clone();
7036                thread::spawn(move || {
7037                    let mut local_ids = Vec::new();
7038
7039                    for i in 0..operations_per_thread {
7040                        // Subscribe an observer
7041                        let observer_id = prop_clone
7042                            .subscribe(Arc::new(move |old, new| {
7043                                // Do some work to simulate real observer
7044                                let _ = thread_id + i + old + new;
7045                            }))
7046                            .expect("Subscribe should succeed");
7047
7048                        local_ids.push(observer_id);
7049
7050                        // Occasionally unsubscribe some observers
7051                        if i % 10 == 0 && !local_ids.is_empty() {
7052                            let idx = i % local_ids.len();
7053                            let id_to_remove = local_ids.remove(idx);
7054                            prop_clone
7055                                .unsubscribe(id_to_remove)
7056                                .expect("Unsubscribe should succeed");
7057                        }
7058                    }
7059
7060                    // Clean up remaining observers
7061                    for id in local_ids {
7062                        prop_clone
7063                            .unsubscribe(id)
7064                            .expect("Final cleanup should succeed");
7065                    }
7066                })
7067            })
7068            .collect();
7069
7070        // Wait for all threads to complete
7071        for handle in handles {
7072            handle.join().expect("Thread should complete successfully");
7073        }
7074
7075        // Property should still be functional
7076        prop.set(42)
7077            .expect("Property should still work after concurrent operations");
7078    }
7079
7080    #[test]
7081    fn test_multiple_observer_panics_isolation() {
7082        let prop = ObservableProperty::new(0);
7083        let successful_calls = Arc::new(AtomicUsize::new(0));
7084
7085        // Create multiple observers that will panic
7086        let _panic_id1 = prop
7087            .subscribe(Arc::new(|_, _| {
7088                panic!("First panic observer");
7089            }))
7090            .expect("Failed to subscribe first panic observer");
7091
7092        let _panic_id2 = prop
7093            .subscribe(Arc::new(|_, _| {
7094                panic!("Second panic observer");
7095            }))
7096            .expect("Failed to subscribe second panic observer");
7097
7098        // Create observers that should succeed despite the panics
7099        let count1 = successful_calls.clone();
7100        let _success_id1 = prop
7101            .subscribe(Arc::new(move |_, _| {
7102                count1.fetch_add(1, Ordering::SeqCst);
7103            }))
7104            .expect("Failed to subscribe first success observer");
7105
7106        let count2 = successful_calls.clone();
7107        let _success_id2 = prop
7108            .subscribe(Arc::new(move |_, _| {
7109                count2.fetch_add(1, Ordering::SeqCst);
7110            }))
7111            .expect("Failed to subscribe second success observer");
7112
7113        // Trigger all observers
7114        prop.set(42).expect("Failed to set property value for panic isolation test");
7115
7116        // Both successful observers should have been called despite the panics
7117        assert_eq!(successful_calls.load(Ordering::SeqCst), 2);
7118    }
7119
7120    #[test]
7121    fn test_async_observer_panic_isolation() {
7122        let prop = ObservableProperty::new(0);
7123        let successful_calls = Arc::new(AtomicUsize::new(0));
7124
7125        // Create observer that will panic
7126        let _panic_id = prop
7127            .subscribe(Arc::new(|_, _| {
7128                panic!("Async panic observer");
7129            }))
7130            .expect("Failed to subscribe async panic observer");
7131
7132        // Create observer that should succeed
7133        let count = successful_calls.clone();
7134        let _success_id = prop
7135            .subscribe(Arc::new(move |_, _| {
7136                count.fetch_add(1, Ordering::SeqCst);
7137            }))
7138            .expect("Failed to subscribe async success observer");
7139
7140        // Use async set to trigger observers in background threads
7141        prop.set_async(42).expect("Failed to set property value asynchronously");
7142
7143        // Wait for async observers to complete
7144        thread::sleep(Duration::from_millis(100));
7145
7146        // The successful observer should have been called despite the panic
7147        assert_eq!(successful_calls.load(Ordering::SeqCst), 1);
7148    }
7149
7150    #[test]
7151    fn test_very_large_observer_count() {
7152        let prop = ObservableProperty::new(0);
7153        let total_calls = Arc::new(AtomicUsize::new(0));
7154        let observer_count = 1000;
7155
7156        // Subscribe many observers
7157        let mut observer_ids = Vec::with_capacity(observer_count);
7158        for i in 0..observer_count {
7159            let count = total_calls.clone();
7160            let id = prop
7161                .subscribe(Arc::new(move |old, new| {
7162                    count.fetch_add(1, Ordering::SeqCst);
7163                    // Verify we got the right values
7164                    assert_eq!(*old, 0);
7165                    assert_eq!(*new, i + 1);
7166                }))
7167                .expect("Failed to subscribe large observer count test observer");
7168            observer_ids.push(id);
7169        }
7170
7171        // Trigger all observers
7172        prop.set(observer_count).expect("Failed to set property value for large observer count test");
7173
7174        // All observers should have been called
7175        assert_eq!(total_calls.load(Ordering::SeqCst), observer_count);
7176
7177        // Clean up
7178        for id in observer_ids {
7179            prop.unsubscribe(id).expect("Failed to unsubscribe observer in large count test");
7180        }
7181    }
7182
7183    #[test]
7184    fn test_observer_with_mutable_state() {
7185        let prop = ObservableProperty::new(0);
7186        let call_history = Arc::new(RwLock::new(Vec::new()));
7187
7188        let history = call_history.clone();
7189        let observer_id = prop
7190            .subscribe(Arc::new(move |old, new| {
7191                if let Ok(mut hist) = history.write() {
7192                    hist.push((*old, *new));
7193                }
7194            }))
7195            .expect("Failed to subscribe mutable state observer");
7196
7197        // Make several changes
7198        prop.set(1).expect("Failed to set property to 1");
7199        prop.set(2).expect("Failed to set property to 2");
7200        prop.set(3).expect("Failed to set property to 3");
7201
7202        // Verify the history was recorded correctly
7203        let history = call_history.read().expect("Failed to read call history");
7204        assert_eq!(history.len(), 3);
7205        assert_eq!(history[0], (0, 1));
7206        assert_eq!(history[1], (1, 2));
7207        assert_eq!(history[2], (2, 3));
7208
7209        prop.unsubscribe(observer_id).expect("Failed to unsubscribe mutable state observer");
7210    }
7211
7212    #[test]
7213    fn test_subscription_automatic_cleanup() {
7214        let prop = ObservableProperty::new(0);
7215        let call_count = Arc::new(AtomicUsize::new(0));
7216
7217        // Test that subscription automatically cleans up when dropped
7218        {
7219            let count = call_count.clone();
7220            let _subscription = prop
7221                .subscribe_with_subscription(Arc::new(move |_, _| {
7222                    count.fetch_add(1, Ordering::SeqCst);
7223                }))
7224                .expect("Failed to create subscription for automatic cleanup test");
7225
7226            // Observer should be active while subscription is in scope
7227            prop.set(1).expect("Failed to set property value in subscription test");
7228            assert_eq!(call_count.load(Ordering::SeqCst), 1);
7229
7230            // Subscription goes out of scope here and should auto-cleanup
7231        }
7232
7233        // Observer should no longer be active after subscription dropped
7234        prop.set(2).expect("Failed to set property value after subscription dropped");
7235        assert_eq!(call_count.load(Ordering::SeqCst), 1); // No additional calls
7236    }
7237
7238    #[test]
7239    fn test_subscription_explicit_drop() {
7240        let prop = ObservableProperty::new(0);
7241        let call_count = Arc::new(AtomicUsize::new(0));
7242
7243        let count = call_count.clone();
7244        let subscription = prop
7245            .subscribe_with_subscription(Arc::new(move |_, _| {
7246                count.fetch_add(1, Ordering::SeqCst);
7247            }))
7248            .expect("Failed to create subscription for explicit drop test");
7249
7250        // Observer should be active
7251        prop.set(1).expect("Failed to set property value before explicit drop");
7252        assert_eq!(call_count.load(Ordering::SeqCst), 1);
7253
7254        // Explicitly drop the subscription
7255        drop(subscription);
7256
7257        // Observer should no longer be active
7258        prop.set(2).expect("Failed to set property value after explicit drop");
7259        assert_eq!(call_count.load(Ordering::SeqCst), 1);
7260    }
7261
7262    #[test]
7263    fn test_multiple_subscriptions_with_cleanup() {
7264        let prop = ObservableProperty::new(0);
7265        let call_count1 = Arc::new(AtomicUsize::new(0));
7266        let call_count2 = Arc::new(AtomicUsize::new(0));
7267        let call_count3 = Arc::new(AtomicUsize::new(0));
7268
7269        let count1 = call_count1.clone();
7270        let count2 = call_count2.clone();
7271        let count3 = call_count3.clone();
7272
7273        let subscription1 = prop
7274            .subscribe_with_subscription(Arc::new(move |_, _| {
7275                count1.fetch_add(1, Ordering::SeqCst);
7276            }))
7277            .expect("Failed to create first subscription");
7278
7279        let subscription2 = prop
7280            .subscribe_with_subscription(Arc::new(move |_, _| {
7281                count2.fetch_add(1, Ordering::SeqCst);
7282            }))
7283            .expect("Failed to create second subscription");
7284
7285        let subscription3 = prop
7286            .subscribe_with_subscription(Arc::new(move |_, _| {
7287                count3.fetch_add(1, Ordering::SeqCst);
7288            }))
7289            .expect("Failed to create third subscription");
7290
7291        // All observers should be active
7292        prop.set(1).expect("Failed to set property value with all subscriptions");
7293        assert_eq!(call_count1.load(Ordering::SeqCst), 1);
7294        assert_eq!(call_count2.load(Ordering::SeqCst), 1);
7295        assert_eq!(call_count3.load(Ordering::SeqCst), 1);
7296
7297        // Drop second subscription
7298        drop(subscription2);
7299
7300        // Only first and third should be active
7301        prop.set(2).expect("Failed to set property value with partial subscriptions");
7302        assert_eq!(call_count1.load(Ordering::SeqCst), 2);
7303        assert_eq!(call_count2.load(Ordering::SeqCst), 1); // No change
7304        assert_eq!(call_count3.load(Ordering::SeqCst), 2);
7305
7306        // Drop remaining subscriptions
7307        drop(subscription1);
7308        drop(subscription3);
7309
7310        // No observers should be active
7311        prop.set(3).expect("Failed to set property value with no subscriptions");
7312        assert_eq!(call_count1.load(Ordering::SeqCst), 2);
7313        assert_eq!(call_count2.load(Ordering::SeqCst), 1);
7314        assert_eq!(call_count3.load(Ordering::SeqCst), 2);
7315    }
7316
7317    #[test]
7318    fn test_subscription_drop_with_poisoned_lock() {
7319        let prop = Arc::new(ObservableProperty::new(0));
7320        let prop_clone = prop.clone();
7321
7322        // Create a subscription
7323        let call_count = Arc::new(AtomicUsize::new(0));
7324        let count = call_count.clone();
7325        let subscription = prop
7326            .subscribe_with_subscription(Arc::new(move |_, _| {
7327                count.fetch_add(1, Ordering::SeqCst);
7328            }))
7329            .expect("Failed to create subscription for poisoned lock test");
7330
7331        // Poison the lock by panicking while holding a write lock
7332        let poison_thread = thread::spawn(move || {
7333            let _guard = prop_clone.inner.write().expect("Failed to acquire write lock for poisoning test");
7334            panic!("Deliberate panic to poison the lock");
7335        });
7336        let _ = poison_thread.join(); // Ignore the panic result
7337
7338        // Dropping the subscription should not panic even with poisoned lock
7339        // This tests that the Drop implementation handles poisoned locks gracefully
7340        drop(subscription); // Should complete without panic
7341
7342        // Test passes if we reach here without panicking
7343    }
7344
7345    #[test]
7346    fn test_subscription_vs_manual_unsubscribe() {
7347        let prop = ObservableProperty::new(0);
7348        let auto_count = Arc::new(AtomicUsize::new(0));
7349        let manual_count = Arc::new(AtomicUsize::new(0));
7350
7351        // Manual subscription
7352        let manual_count_clone = manual_count.clone();
7353        let manual_id = prop
7354            .subscribe(Arc::new(move |_, _| {
7355                manual_count_clone.fetch_add(1, Ordering::SeqCst);
7356            }))
7357            .expect("Failed to create manual subscription");
7358
7359        // Automatic subscription
7360        let auto_count_clone = auto_count.clone();
7361        let _auto_subscription = prop
7362            .subscribe_with_subscription(Arc::new(move |_, _| {
7363                auto_count_clone.fetch_add(1, Ordering::SeqCst);
7364            }))
7365            .expect("Failed to create automatic subscription");
7366
7367        // Both should be active
7368        prop.set(1).expect("Failed to set property value with both subscriptions");
7369        assert_eq!(manual_count.load(Ordering::SeqCst), 1);
7370        assert_eq!(auto_count.load(Ordering::SeqCst), 1);
7371
7372        // Manual unsubscribe
7373        prop.unsubscribe(manual_id).expect("Failed to manually unsubscribe");
7374
7375        // Only automatic subscription should be active
7376        prop.set(2).expect("Failed to set property value after manual unsubscribe");
7377        assert_eq!(manual_count.load(Ordering::SeqCst), 1); // No change
7378        assert_eq!(auto_count.load(Ordering::SeqCst), 2);
7379
7380        // Auto subscription goes out of scope here and cleans up automatically
7381    }
7382
7383    #[test]
7384    fn test_subscribe_with_subscription_error_handling() {
7385        let prop = Arc::new(ObservableProperty::new(0));
7386        let prop_clone = prop.clone();
7387
7388        // Poison the lock
7389        let poison_thread = thread::spawn(move || {
7390            let _guard = prop_clone.inner.write().expect("Failed to acquire write lock for poisoning test");
7391            panic!("Deliberate panic to poison the lock");
7392        });
7393        let _ = poison_thread.join();
7394
7395        // With graceful degradation, subscribe_with_subscription should succeed
7396        let result = prop.subscribe_with_subscription(Arc::new(|_, _| {}));
7397        assert!(result.is_ok(), "subscribe_with_subscription should succeed with graceful degradation");
7398    }
7399
7400    #[test]
7401    fn test_subscription_with_property_cloning() {
7402        let prop1 = ObservableProperty::new(0);
7403        let prop2 = prop1.clone();
7404        let call_count = Arc::new(AtomicUsize::new(0));
7405
7406        // Subscribe to prop1
7407        let count = call_count.clone();
7408        let _subscription = prop1
7409            .subscribe_with_subscription(Arc::new(move |_, _| {
7410                count.fetch_add(1, Ordering::SeqCst);
7411            }))
7412            .expect("Failed to create subscription for cloned property test");
7413
7414        // Changes through prop2 should trigger the observer subscribed to prop1
7415        prop2.set(42).expect("Failed to set property value through prop2");
7416        assert_eq!(call_count.load(Ordering::SeqCst), 1);
7417
7418        // Changes through prop1 should also trigger the observer
7419        prop1.set(100).expect("Failed to set property value through prop1");
7420        assert_eq!(call_count.load(Ordering::SeqCst), 2);
7421    }
7422
7423    #[test]
7424    fn test_subscription_in_conditional_blocks() {
7425        let prop = ObservableProperty::new(0);
7426        let call_count = Arc::new(AtomicUsize::new(0));
7427
7428        let should_subscribe = true;
7429
7430        if should_subscribe {
7431            let count = call_count.clone();
7432            let _subscription = prop
7433                .subscribe_with_subscription(Arc::new(move |_, _| {
7434                    count.fetch_add(1, Ordering::SeqCst);
7435                }))
7436                .expect("Failed to create subscription in conditional block");
7437
7438            // Observer active within this block
7439            prop.set(1).expect("Failed to set property value in conditional block");
7440            assert_eq!(call_count.load(Ordering::SeqCst), 1);
7441
7442            // Subscription dropped when exiting this block
7443        }
7444
7445        // Observer should be inactive now
7446        prop.set(2).expect("Failed to set property value after conditional block");
7447        assert_eq!(call_count.load(Ordering::SeqCst), 1);
7448    }
7449
7450    #[test]
7451    fn test_subscription_with_early_return() {
7452        fn test_function(
7453            prop: &ObservableProperty<i32>,
7454            should_return_early: bool,
7455        ) -> Result<(), PropertyError> {
7456            let call_count = Arc::new(AtomicUsize::new(0));
7457            let count = call_count.clone();
7458
7459            let _subscription = prop.subscribe_with_subscription(Arc::new(move |_, _| {
7460                count.fetch_add(1, Ordering::SeqCst);
7461            }))?;
7462
7463            prop.set(1)?;
7464            assert_eq!(call_count.load(Ordering::SeqCst), 1);
7465
7466            if should_return_early {
7467                return Ok(()); // Subscription should be cleaned up here
7468            }
7469
7470            prop.set(2)?;
7471            assert_eq!(call_count.load(Ordering::SeqCst), 2);
7472
7473            Ok(())
7474            // Subscription cleaned up when function exits normally
7475        }
7476
7477        let prop = ObservableProperty::new(0);
7478
7479        // Test early return
7480        test_function(&prop, true).expect("Failed to test early return");
7481
7482        // Verify observer is no longer active after early return
7483        prop.set(10).expect("Failed to set property value after early return");
7484
7485        // Test normal exit
7486        test_function(&prop, false).expect("Failed to test normal exit");
7487
7488        // Verify observer is no longer active after normal exit
7489        prop.set(20).expect("Failed to set property value after normal exit");
7490    }
7491
7492    #[test]
7493    fn test_subscription_move_semantics() {
7494        let prop = ObservableProperty::new(0);
7495        let call_count = Arc::new(AtomicUsize::new(0));
7496
7497        let count = call_count.clone();
7498        let subscription = prop
7499            .subscribe_with_subscription(Arc::new(move |_, _| {
7500                count.fetch_add(1, Ordering::SeqCst);
7501            }))
7502            .expect("Failed to create subscription for move semantics test");
7503
7504        // Observer should be active
7505        prop.set(1).expect("Failed to set property value before move");
7506        assert_eq!(call_count.load(Ordering::SeqCst), 1);
7507
7508        // Move subscription to a new variable
7509        let moved_subscription = subscription;
7510
7511        // Observer should still be active after move
7512        prop.set(2).expect("Failed to set property value after move");
7513        assert_eq!(call_count.load(Ordering::SeqCst), 2);
7514
7515        // Drop the moved subscription
7516        drop(moved_subscription);
7517
7518        // Observer should now be inactive
7519        prop.set(3).expect("Failed to set property value after moved subscription drop");
7520        assert_eq!(call_count.load(Ordering::SeqCst), 2);
7521    }
7522
7523    #[test]
7524    fn test_filtered_subscription_automatic_cleanup() {
7525        let prop = ObservableProperty::new(0);
7526        let call_count = Arc::new(AtomicUsize::new(0));
7527
7528        {
7529            let count = call_count.clone();
7530            let _subscription = prop
7531                .subscribe_filtered_with_subscription(
7532                    Arc::new(move |_, _| {
7533                        count.fetch_add(1, Ordering::SeqCst);
7534                    }),
7535                    |old, new| new > old, // Only trigger on increases
7536                )
7537                .expect("Failed to create filtered subscription");
7538
7539            // Should trigger (0 -> 5)
7540            prop.set(5).expect("Failed to set property value to 5 in filtered test");
7541            assert_eq!(call_count.load(Ordering::SeqCst), 1);
7542
7543            // Should NOT trigger (5 -> 3)
7544            prop.set(3).expect("Failed to set property value to 3 in filtered test");
7545            assert_eq!(call_count.load(Ordering::SeqCst), 1);
7546
7547            // Should trigger (3 -> 10)
7548            prop.set(10).expect("Failed to set property value to 10 in filtered test");
7549            assert_eq!(call_count.load(Ordering::SeqCst), 2);
7550
7551            // Subscription goes out of scope here
7552        }
7553
7554        // Observer should be inactive after subscription cleanup
7555        prop.set(20).expect("Failed to set property value after filtered subscription cleanup");
7556        assert_eq!(call_count.load(Ordering::SeqCst), 2);
7557    }
7558
7559    #[test]
7560    fn test_multiple_filtered_subscriptions() {
7561        let prop = ObservableProperty::new(10);
7562        let increase_count = Arc::new(AtomicUsize::new(0));
7563        let decrease_count = Arc::new(AtomicUsize::new(0));
7564        let significant_change_count = Arc::new(AtomicUsize::new(0));
7565
7566        let inc_count = increase_count.clone();
7567        let dec_count = decrease_count.clone();
7568        let sig_count = significant_change_count.clone();
7569
7570        let _increase_sub = prop
7571            .subscribe_filtered_with_subscription(
7572                Arc::new(move |_, _| {
7573                    inc_count.fetch_add(1, Ordering::SeqCst);
7574                }),
7575                |old, new| new > old,
7576            )
7577            .expect("Failed to create increase subscription");
7578
7579        let _decrease_sub = prop
7580            .subscribe_filtered_with_subscription(
7581                Arc::new(move |_, _| {
7582                    dec_count.fetch_add(1, Ordering::SeqCst);
7583                }),
7584                |old, new| new < old,
7585            )
7586            .expect("Failed to create decrease subscription");
7587
7588        let _significant_sub = prop
7589            .subscribe_filtered_with_subscription(
7590                Arc::new(move |_, _| {
7591                    sig_count.fetch_add(1, Ordering::SeqCst);
7592                }),
7593                |old, new| ((*new as i32) - (*old as i32)).abs() > 5,
7594            )
7595            .expect("Failed to create significant change subscription");
7596
7597        // Test increases
7598        prop.set(15).expect("Failed to set property to 15 in multiple filtered test"); // +5: triggers increase, not significant
7599        assert_eq!(increase_count.load(Ordering::SeqCst), 1);
7600        assert_eq!(decrease_count.load(Ordering::SeqCst), 0);
7601        assert_eq!(significant_change_count.load(Ordering::SeqCst), 0);
7602
7603        // Test significant increase
7604        prop.set(25).expect("Failed to set property to 25 in multiple filtered test"); // +10: triggers increase and significant
7605        assert_eq!(increase_count.load(Ordering::SeqCst), 2);
7606        assert_eq!(decrease_count.load(Ordering::SeqCst), 0);
7607        assert_eq!(significant_change_count.load(Ordering::SeqCst), 1);
7608
7609        // Test significant decrease
7610        prop.set(5).expect("Failed to set property to 5 in multiple filtered test"); // -20: triggers decrease and significant
7611        assert_eq!(increase_count.load(Ordering::SeqCst), 2);
7612        assert_eq!(decrease_count.load(Ordering::SeqCst), 1);
7613        assert_eq!(significant_change_count.load(Ordering::SeqCst), 2);
7614
7615        // Test small decrease
7616        prop.set(3).expect("Failed to set property to 3 in multiple filtered test"); // -2: triggers decrease, not significant
7617        assert_eq!(increase_count.load(Ordering::SeqCst), 2);
7618        assert_eq!(decrease_count.load(Ordering::SeqCst), 2);
7619        assert_eq!(significant_change_count.load(Ordering::SeqCst), 2);
7620
7621        // All subscriptions auto-cleanup when they go out of scope
7622    }
7623
7624    #[test]
7625    fn test_filtered_subscription_complex_filter() {
7626        let prop = ObservableProperty::new(0.0f64);
7627        let call_count = Arc::new(AtomicUsize::new(0));
7628        let values_received = Arc::new(RwLock::new(Vec::new()));
7629
7630        let count = call_count.clone();
7631        let values = values_received.clone();
7632        let _subscription = prop
7633            .subscribe_filtered_with_subscription(
7634                Arc::new(move |old, new| {
7635                    count.fetch_add(1, Ordering::SeqCst);
7636                    if let Ok(mut v) = values.write() {
7637                        v.push((*old, *new));
7638                    }
7639                }),
7640                |old, new| {
7641                    // Complex filter: trigger only when crossing integer boundaries
7642                    // and the change is significant (> 0.5)
7643                    let old_int = old.floor() as i32;
7644                    let new_int = new.floor() as i32;
7645                    old_int != new_int && (new - old).abs() > 0.5_f64
7646                },
7647            )
7648            .expect("Failed to create complex filtered subscription");
7649
7650        // Small changes within same integer - should not trigger
7651        prop.set(0.3).expect("Failed to set property to 0.3 in complex filter test");
7652        prop.set(0.7).expect("Failed to set property to 0.7 in complex filter test");
7653        assert_eq!(call_count.load(Ordering::SeqCst), 0);
7654
7655        // Cross integer boundary with significant change - should trigger
7656        prop.set(1.3).expect("Failed to set property to 1.3 in complex filter test"); // Change of 0.6, which is > 0.5
7657        assert_eq!(call_count.load(Ordering::SeqCst), 1);
7658
7659        // Small cross-boundary change - should not trigger
7660        prop.set(1.9).expect("Failed to set property to 1.9 in complex filter test");
7661        prop.set(2.1).expect("Failed to set property to 2.1 in complex filter test"); // Change of 0.2, less than 0.5
7662        assert_eq!(call_count.load(Ordering::SeqCst), 1);
7663
7664        // Large cross-boundary change - should trigger
7665        prop.set(3.5).expect("Failed to set property to 3.5 in complex filter test");
7666        assert_eq!(call_count.load(Ordering::SeqCst), 2);
7667
7668        // Verify received values
7669        let values = values_received.read().expect("Failed to read values in complex filter test");
7670        assert_eq!(values.len(), 2);
7671        assert_eq!(values[0], (0.7, 1.3));
7672        assert_eq!(values[1], (2.1, 3.5));
7673    }
7674
7675    #[test]
7676    fn test_filtered_subscription_error_handling() {
7677        let prop = Arc::new(ObservableProperty::new(0));
7678        let prop_clone = prop.clone();
7679
7680        // Poison the lock
7681        let poison_thread = thread::spawn(move || {
7682            let _guard = prop_clone.inner.write().expect("Failed to acquire write lock for filtered subscription poison test");
7683            panic!("Deliberate panic to poison the lock");
7684        });
7685        let _ = poison_thread.join();
7686
7687        // With graceful degradation, subscribe_filtered_with_subscription should succeed
7688        let result = prop.subscribe_filtered_with_subscription(Arc::new(|_, _| {}), |_, _| true);
7689        assert!(result.is_ok(), "subscribe_filtered_with_subscription should succeed with graceful degradation");
7690    }
7691
7692    #[test]
7693    fn test_filtered_subscription_vs_manual_filtered() {
7694        let prop = ObservableProperty::new(0);
7695        let auto_count = Arc::new(AtomicUsize::new(0));
7696        let manual_count = Arc::new(AtomicUsize::new(0));
7697
7698        // Manual filtered subscription
7699        let manual_count_clone = manual_count.clone();
7700        let manual_id = prop
7701            .subscribe_filtered(
7702                Arc::new(move |_, _| {
7703                    manual_count_clone.fetch_add(1, Ordering::SeqCst);
7704                }),
7705                |old, new| new > old,
7706            )
7707            .expect("Failed to create manual filtered subscription");
7708
7709        // Automatic filtered subscription
7710        let auto_count_clone = auto_count.clone();
7711        let _auto_subscription = prop
7712            .subscribe_filtered_with_subscription(
7713                Arc::new(move |_, _| {
7714                    auto_count_clone.fetch_add(1, Ordering::SeqCst);
7715                }),
7716                |old, new| new > old,
7717            )
7718            .expect("Failed to create automatic filtered subscription");
7719
7720        // Both should be triggered by increases
7721        prop.set(5).expect("Failed to set property to 5 in filtered vs manual test");
7722        assert_eq!(manual_count.load(Ordering::SeqCst), 1);
7723        assert_eq!(auto_count.load(Ordering::SeqCst), 1);
7724
7725        // Neither should be triggered by decreases
7726        prop.set(3).expect("Failed to set property to 3 in filtered vs manual test");
7727        assert_eq!(manual_count.load(Ordering::SeqCst), 1);
7728        assert_eq!(auto_count.load(Ordering::SeqCst), 1);
7729
7730        // Both should be triggered by increases again
7731        prop.set(10).expect("Failed to set property to 10 in filtered vs manual test");
7732        assert_eq!(manual_count.load(Ordering::SeqCst), 2);
7733        assert_eq!(auto_count.load(Ordering::SeqCst), 2);
7734
7735        // Manual cleanup
7736        prop.unsubscribe(manual_id).expect("Failed to unsubscribe manual filtered observer");
7737
7738        // Only automatic subscription should be active
7739        prop.set(15).expect("Failed to set property to 15 after manual cleanup");
7740        assert_eq!(manual_count.load(Ordering::SeqCst), 2); // No change
7741        assert_eq!(auto_count.load(Ordering::SeqCst), 3);
7742
7743        // Auto subscription cleaned up when it goes out of scope
7744    }
7745
7746    #[test]
7747    fn test_filtered_subscription_with_panicking_filter() {
7748        let prop = ObservableProperty::new(0);
7749        let call_count = Arc::new(AtomicUsize::new(0));
7750
7751        let count = call_count.clone();
7752        let _subscription = prop
7753            .subscribe_filtered_with_subscription(
7754                Arc::new(move |_, _| {
7755                    count.fetch_add(1, Ordering::SeqCst);
7756                }),
7757                |_, new| {
7758                    if *new == 42 {
7759                        panic!("Filter panic on 42");
7760                    }
7761                    true // Accept all other values
7762                },
7763            )
7764            .expect("Failed to create panicking filter subscription");
7765
7766        // Normal value should work
7767        prop.set(1).expect("Failed to set property to 1 in panicking filter test");
7768        assert_eq!(call_count.load(Ordering::SeqCst), 1);
7769
7770        // Value that causes filter to panic should be handled gracefully
7771        // The behavior here depends on how the filter panic is handled
7772        // In the current implementation, filter panics may cause the observer to not be called
7773        prop.set(42).expect("Failed to set property to 42 in panicking filter test");
7774
7775        // Observer should still work for subsequent normal values
7776        prop.set(2).expect("Failed to set property to 2 after filter panic");
7777        // Note: The exact count here depends on panic handling implementation
7778        // The important thing is that the system doesn't crash
7779    }
7780
7781    #[test]
7782    fn test_subscription_thread_safety() {
7783        let prop = Arc::new(ObservableProperty::new(0));
7784        let num_threads = 8;
7785        let operations_per_thread = 50;
7786        let total_calls = Arc::new(AtomicUsize::new(0));
7787
7788        let handles: Vec<_> = (0..num_threads)
7789            .map(|thread_id| {
7790                let prop_clone = prop.clone();
7791                let calls_clone = total_calls.clone();
7792
7793                thread::spawn(move || {
7794                    let mut local_subscriptions = Vec::new();
7795
7796                    for i in 0..operations_per_thread {
7797                        let calls = calls_clone.clone();
7798                        let subscription = prop_clone
7799                            .subscribe_with_subscription(Arc::new(move |old, new| {
7800                                calls.fetch_add(1, Ordering::SeqCst);
7801                                // Simulate some work
7802                                let _ = thread_id + i + old + new;
7803                            }))
7804                            .expect("Should be able to create subscription");
7805
7806                        local_subscriptions.push(subscription);
7807
7808                        // Trigger observers
7809                        prop_clone
7810                            .set(thread_id * 1000 + i)
7811                            .expect("Should be able to set value");
7812
7813                        // Occasionally drop some subscriptions
7814                        if i % 5 == 0 && !local_subscriptions.is_empty() {
7815                            local_subscriptions.remove(0); // Drop first subscription
7816                        }
7817                    }
7818
7819                    // All remaining subscriptions dropped when vector goes out of scope
7820                })
7821            })
7822            .collect();
7823
7824        // Wait for all threads to complete
7825        for handle in handles {
7826            handle.join().expect("Thread should complete successfully");
7827        }
7828
7829        // Property should still be functional after all the concurrent operations
7830        prop.set(9999).expect("Property should still work");
7831
7832        // We can't easily verify the exact call count due to the complex timing,
7833        // but we can verify that the system didn't crash and is still operational
7834        println!(
7835            "Total observer calls: {}",
7836            total_calls.load(Ordering::SeqCst)
7837        );
7838    }
7839
7840    #[test]
7841    fn test_subscription_cross_thread_drop() {
7842        let prop = Arc::new(ObservableProperty::new(0));
7843        let call_count = Arc::new(AtomicUsize::new(0));
7844
7845        // Create subscription in main thread
7846        let count = call_count.clone();
7847        let subscription = prop
7848            .subscribe_with_subscription(Arc::new(move |_, _| {
7849                count.fetch_add(1, Ordering::SeqCst);
7850            }))
7851            .expect("Failed to create subscription for cross-thread drop test");
7852
7853        // Verify observer is active
7854        prop.set(1).expect("Failed to set property value in cross-thread drop test");
7855        assert_eq!(call_count.load(Ordering::SeqCst), 1);
7856
7857        // Move subscription to another thread and drop it there
7858        let prop_clone = prop.clone();
7859        let call_count_clone = call_count.clone();
7860
7861        let handle = thread::spawn(move || {
7862            // Verify observer is still active in the other thread
7863            prop_clone.set(2).expect("Failed to set property value in other thread");
7864            assert_eq!(call_count_clone.load(Ordering::SeqCst), 2);
7865
7866            // Drop subscription in this thread
7867            drop(subscription);
7868
7869            // Verify observer is no longer active
7870            prop_clone.set(3).expect("Failed to set property value after drop in other thread");
7871            assert_eq!(call_count_clone.load(Ordering::SeqCst), 2); // No change
7872        });
7873
7874        handle.join().expect("Failed to join cross-thread drop test thread");
7875
7876        // Verify observer is still inactive in main thread
7877        prop.set(4).expect("Failed to set property value after thread join");
7878        assert_eq!(call_count.load(Ordering::SeqCst), 2);
7879    }
7880
7881    #[test]
7882    fn test_concurrent_subscription_creation_and_property_changes() {
7883        let prop = Arc::new(ObservableProperty::new(0));
7884        let total_notifications = Arc::new(AtomicUsize::new(0));
7885        let num_subscriber_threads = 4;
7886        let num_setter_threads = 2;
7887        let operations_per_thread = 25;
7888
7889        // Threads that create and destroy subscriptions
7890        let subscriber_handles: Vec<_> = (0..num_subscriber_threads)
7891            .map(|_| {
7892                let prop_clone = prop.clone();
7893                let notifications_clone = total_notifications.clone();
7894
7895                thread::spawn(move || {
7896                    for _ in 0..operations_per_thread {
7897                        let notifications = notifications_clone.clone();
7898                        let _subscription = prop_clone
7899                            .subscribe_with_subscription(Arc::new(move |_, _| {
7900                                notifications.fetch_add(1, Ordering::SeqCst);
7901                            }))
7902                            .expect("Should create subscription");
7903
7904                        // Keep subscription alive for a short time
7905                        thread::sleep(Duration::from_millis(1));
7906
7907                        // Subscription dropped when _subscription goes out of scope
7908                    }
7909                })
7910            })
7911            .collect();
7912
7913        // Threads that continuously change the property value
7914        let setter_handles: Vec<_> = (0..num_setter_threads)
7915            .map(|thread_id| {
7916                let prop_clone = prop.clone();
7917
7918                thread::spawn(move || {
7919                    for i in 0..operations_per_thread * 2 {
7920                        prop_clone
7921                            .set(thread_id * 10000 + i)
7922                            .expect("Should set value");
7923                        thread::sleep(Duration::from_millis(1));
7924                    }
7925                })
7926            })
7927            .collect();
7928
7929        // Wait for all threads to complete
7930        for handle in subscriber_handles
7931            .into_iter()
7932            .chain(setter_handles.into_iter())
7933        {
7934            handle.join().expect("Thread should complete");
7935        }
7936
7937        // System should be stable after concurrent operations
7938        prop.set(99999).expect("Property should still work");
7939
7940        println!(
7941            "Total notifications during concurrent test: {}",
7942            total_notifications.load(Ordering::SeqCst)
7943        );
7944    }
7945
7946    #[test]
7947    fn test_filtered_subscription_thread_safety() {
7948        let prop = Arc::new(ObservableProperty::new(0));
7949        let increase_notifications = Arc::new(AtomicUsize::new(0));
7950        let decrease_notifications = Arc::new(AtomicUsize::new(0));
7951        let num_threads = 6;
7952
7953        let handles: Vec<_> = (0..num_threads)
7954            .map(|thread_id| {
7955                let prop_clone = prop.clone();
7956                let inc_notifications = increase_notifications.clone();
7957                let dec_notifications = decrease_notifications.clone();
7958
7959                thread::spawn(move || {
7960                    // Create increase-only subscription
7961                    let inc_count = inc_notifications.clone();
7962                    let _inc_subscription = prop_clone
7963                        .subscribe_filtered_with_subscription(
7964                            Arc::new(move |_, _| {
7965                                inc_count.fetch_add(1, Ordering::SeqCst);
7966                            }),
7967                            |old, new| new > old,
7968                        )
7969                        .expect("Should create filtered subscription");
7970
7971                    // Create decrease-only subscription
7972                    let dec_count = dec_notifications.clone();
7973                    let _dec_subscription = prop_clone
7974                        .subscribe_filtered_with_subscription(
7975                            Arc::new(move |_, _| {
7976                                dec_count.fetch_add(1, Ordering::SeqCst);
7977                            }),
7978                            |old, new| new < old,
7979                        )
7980                        .expect("Should create filtered subscription");
7981
7982                    // Perform some property changes
7983                    let base_value = thread_id * 100;
7984                    for i in 0..20 {
7985                        let new_value = base_value + (i % 10); // Creates increases and decreases
7986                        prop_clone.set(new_value).expect("Should set value");
7987                        thread::sleep(Duration::from_millis(1));
7988                    }
7989
7990                    // Subscriptions automatically cleaned up when going out of scope
7991                })
7992            })
7993            .collect();
7994
7995        // Wait for all threads
7996        for handle in handles {
7997            handle.join().expect("Thread should complete");
7998        }
7999
8000        // Verify system is still operational
8001        let initial_inc = increase_notifications.load(Ordering::SeqCst);
8002        let initial_dec = decrease_notifications.load(Ordering::SeqCst);
8003
8004        prop.set(1000).expect("Property should still work");
8005        prop.set(2000).expect("Property should still work");
8006
8007        // No new notifications should occur (all subscriptions cleaned up)
8008        assert_eq!(increase_notifications.load(Ordering::SeqCst), initial_inc);
8009        assert_eq!(decrease_notifications.load(Ordering::SeqCst), initial_dec);
8010
8011        println!(
8012            "Increase notifications: {}, Decrease notifications: {}",
8013            initial_inc, initial_dec
8014        );
8015    }
8016
8017    #[test]
8018    fn test_subscription_with_async_property_changes() {
8019        let prop = Arc::new(ObservableProperty::new(0));
8020        let sync_notifications = Arc::new(AtomicUsize::new(0));
8021        let async_notifications = Arc::new(AtomicUsize::new(0));
8022
8023        // Subscription that tracks sync notifications
8024        let sync_count = sync_notifications.clone();
8025        let _sync_subscription = prop
8026            .subscribe_with_subscription(Arc::new(move |old, new| {
8027                sync_count.fetch_add(1, Ordering::SeqCst);
8028                // Simulate slow observer work
8029                thread::sleep(Duration::from_millis(5));
8030                println!("Sync observer: {} -> {}", old, new);
8031            }))
8032            .expect("Failed to create sync subscription");
8033
8034        // Subscription that tracks async notifications
8035        let async_count = async_notifications.clone();
8036        let _async_subscription = prop
8037            .subscribe_with_subscription(Arc::new(move |old, new| {
8038                async_count.fetch_add(1, Ordering::SeqCst);
8039                println!("Async observer: {} -> {}", old, new);
8040            }))
8041            .expect("Failed to create async subscription");
8042
8043        // Test sync property changes
8044        let start = std::time::Instant::now();
8045        prop.set(1).expect("Failed to set property value 1 in async test");
8046        prop.set(2).expect("Failed to set property value 2 in async test");
8047        let sync_duration = start.elapsed();
8048
8049        // Test async property changes
8050        let start = std::time::Instant::now();
8051        prop.set_async(3).expect("Failed to set property value 3 async");
8052        prop.set_async(4).expect("Failed to set property value 4 async");
8053        let async_duration = start.elapsed();
8054
8055        // Async should be much faster
8056        assert!(async_duration < sync_duration);
8057
8058        // Wait for async observers to complete
8059        thread::sleep(Duration::from_millis(50));
8060
8061        // All observers should have been called
8062        assert_eq!(sync_notifications.load(Ordering::SeqCst), 4);
8063        assert_eq!(async_notifications.load(Ordering::SeqCst), 4);
8064
8065        // Subscriptions auto-cleanup when going out of scope
8066    }
8067
8068    #[test]
8069    fn test_subscription_creation_with_poisoned_lock() {
8070        let prop = Arc::new(ObservableProperty::new(0));
8071        let prop_clone = prop.clone();
8072
8073        // Create a valid subscription before poisoning
8074        let call_count = Arc::new(AtomicUsize::new(0));
8075        let count = call_count.clone();
8076        let existing_subscription = prop
8077            .subscribe_with_subscription(Arc::new(move |_, _| {
8078                count.fetch_add(1, Ordering::SeqCst);
8079            }))
8080            .expect("Failed to create subscription before poisoning");
8081
8082        // Poison the lock
8083        let poison_thread = thread::spawn(move || {
8084            let _guard = prop_clone.inner.write().expect("Failed to acquire write lock for subscription poison test");
8085            panic!("Deliberate panic to poison the lock");
8086        });
8087        let _ = poison_thread.join();
8088
8089        // With graceful degradation, new subscription creation should succeed
8090        let result = prop.subscribe_with_subscription(Arc::new(|_, _| {}));
8091        assert!(result.is_ok(), "subscribe_with_subscription should succeed with graceful degradation");
8092
8093        // New filtered subscription creation should also succeed
8094        let filtered_result =
8095            prop.subscribe_filtered_with_subscription(Arc::new(|_, _| {}), |_, _| true);
8096        assert!(filtered_result.is_ok(), "subscribe_filtered_with_subscription should succeed with graceful degradation");
8097
8098        // Dropping existing subscription should not panic
8099        drop(existing_subscription);
8100    }
8101
8102    #[test]
8103    fn test_subscription_cleanup_behavior_with_poisoned_lock() {
8104        // This test specifically verifies that Drop doesn't panic with poisoned locks
8105        let prop = Arc::new(ObservableProperty::new(0));
8106        let call_count = Arc::new(AtomicUsize::new(0));
8107
8108        // Create subscription
8109        let count = call_count.clone();
8110        let subscription = prop
8111            .subscribe_with_subscription(Arc::new(move |_, _| {
8112                count.fetch_add(1, Ordering::SeqCst);
8113            }))
8114            .expect("Failed to create subscription for cleanup behavior test");
8115
8116        // Verify it works initially
8117        prop.set(1).expect("Failed to set property value in cleanup behavior test");
8118        assert_eq!(call_count.load(Ordering::SeqCst), 1);
8119
8120        // Poison the lock from another thread
8121        let prop_clone = prop.clone();
8122        let poison_thread = thread::spawn(move || {
8123            let _guard = prop_clone.inner.write().expect("Failed to acquire write lock for cleanup behavior poison test");
8124            panic!("Deliberate panic to poison the lock");
8125        });
8126        let _ = poison_thread.join();
8127
8128        // Now drop the subscription - this should NOT panic
8129        // The Drop implementation should handle the poisoned lock gracefully
8130        drop(subscription);
8131
8132        // Test succeeds if we reach this point without panicking
8133    }
8134
8135    #[test]
8136    fn test_multiple_subscription_cleanup_with_poisoned_lock() {
8137        let prop = Arc::new(ObservableProperty::new(0));
8138        let mut subscriptions = Vec::new();
8139
8140        // Create multiple subscriptions
8141        for i in 0..5 {
8142            let call_count = Arc::new(AtomicUsize::new(0));
8143            let count = call_count.clone();
8144            let subscription = prop
8145                .subscribe_with_subscription(Arc::new(move |old, new| {
8146                    count.fetch_add(1, Ordering::SeqCst);
8147                    println!("Observer {}: {} -> {}", i, old, new);
8148                }))
8149                .expect("Failed to create subscription in multiple cleanup test");
8150            subscriptions.push(subscription);
8151        }
8152
8153        // Verify they all work
8154        prop.set(42).expect("Failed to set property value in multiple cleanup test");
8155
8156        // Poison the lock
8157        let prop_clone = prop.clone();
8158        let poison_thread = thread::spawn(move || {
8159            let _guard = prop_clone.inner.write().expect("Failed to acquire write lock for multiple cleanup poison test");
8160            panic!("Deliberate panic to poison the lock");
8161        });
8162        let _ = poison_thread.join();
8163
8164        // Drop all subscriptions - none should panic
8165        for subscription in subscriptions {
8166            drop(subscription);
8167        }
8168
8169        // Test succeeds if no panics occurred
8170    }
8171
8172    #[test]
8173    fn test_subscription_behavior_before_and_after_poison() {
8174        let prop = Arc::new(ObservableProperty::new(0));
8175        let before_poison_count = Arc::new(AtomicUsize::new(0));
8176        let after_poison_count = Arc::new(AtomicUsize::new(0));
8177
8178        // Create subscription before poisoning
8179        let before_count = before_poison_count.clone();
8180        let before_subscription = prop
8181            .subscribe_with_subscription(Arc::new(move |_, _| {
8182                before_count.fetch_add(1, Ordering::SeqCst);
8183            }))
8184            .expect("Failed to create subscription before poison test");
8185
8186        // Verify it works
8187        prop.set(1).expect("Failed to set property value before poison test");
8188        assert_eq!(before_poison_count.load(Ordering::SeqCst), 1);
8189
8190        // Poison the lock
8191        let prop_clone = prop.clone();
8192        let poison_thread = thread::spawn(move || {
8193            let _guard = prop_clone.inner.write().expect("Failed to acquire write lock for before/after poison test");
8194            panic!("Deliberate panic to poison the lock");
8195        });
8196        let _ = poison_thread.join();
8197
8198        // With graceful degradation, subscription creation after poisoning should succeed
8199        let after_count = after_poison_count.clone();
8200        let after_result = prop.subscribe_with_subscription(Arc::new(move |_, _| {
8201            after_count.fetch_add(1, Ordering::SeqCst);
8202        }));
8203        assert!(after_result.is_ok(), "subscribe_with_subscription should succeed with graceful degradation");
8204        
8205        let _after_subscription = after_result.unwrap();
8206
8207        // Clean up the before-poison subscription - should not panic
8208        drop(before_subscription);
8209    }
8210
8211    #[test]
8212    fn test_concurrent_subscription_drops_with_poison() {
8213        let prop = Arc::new(ObservableProperty::new(0));
8214        let num_subscriptions = 10;
8215        let mut subscriptions = Vec::new();
8216
8217        // Create multiple subscriptions
8218        for i in 0..num_subscriptions {
8219            let call_count = Arc::new(AtomicUsize::new(0));
8220            let count = call_count.clone();
8221            let subscription = prop
8222                .subscribe_with_subscription(Arc::new(move |_, _| {
8223                    count.fetch_add(1, Ordering::SeqCst);
8224                    println!("Observer {}", i);
8225                }))
8226                .expect("Failed to create subscription in concurrent drops test");
8227            subscriptions.push(subscription);
8228        }
8229
8230        // Poison the lock
8231        let prop_clone = prop.clone();
8232        let poison_thread = thread::spawn(move || {
8233            let _guard = prop_clone.inner.write().expect("Failed to acquire write lock for concurrent drops poison test");
8234            panic!("Deliberate panic to poison the lock");
8235        });
8236        let _ = poison_thread.join();
8237
8238        // Drop subscriptions concurrently from multiple threads
8239        let handles: Vec<_> = subscriptions
8240            .into_iter()
8241            .enumerate()
8242            .map(|(i, subscription)| {
8243                thread::spawn(move || {
8244                    // Add some randomness to timing
8245                    thread::sleep(Duration::from_millis(i as u64 % 5));
8246                    drop(subscription);
8247                    println!("Dropped subscription {}", i);
8248                })
8249            })
8250            .collect();
8251
8252        // Wait for all drops to complete
8253        for handle in handles {
8254            handle
8255                .join()
8256                .expect("Drop thread should complete without panic");
8257        }
8258
8259        // Test succeeds if all threads completed successfully
8260    }
8261
8262    
8263    #[test]
8264    fn test_with_max_threads_creation() {
8265        // Test creation with various thread counts
8266        let prop1 = ObservableProperty::with_max_threads(42, 1);
8267        let prop2 = ObservableProperty::with_max_threads("test".to_string(), 8);
8268        let prop3 = ObservableProperty::with_max_threads(0.5_f64, 16);
8269
8270        // Verify initial values are correct
8271        assert_eq!(prop1.get().expect("Failed to get prop1 value"), 42);
8272        assert_eq!(prop2.get().expect("Failed to get prop2 value"), "test");
8273        assert_eq!(prop3.get().expect("Failed to get prop3 value"), 0.5);
8274    }
8275
8276    #[test]
8277    fn test_with_max_threads_zero_defaults_to_max_threads() {
8278        // Test that zero max_threads defaults to MAX_THREADS (4)
8279        let prop1 = ObservableProperty::with_max_threads(100, 0);
8280        let prop2 = ObservableProperty::new(100); // Uses default MAX_THREADS
8281
8282        // Both should have the same max_threads value
8283        // We can't directly access max_threads, but we can verify behavior is consistent
8284        assert_eq!(prop1.get().expect("Failed to get prop1 value"), 100);
8285        assert_eq!(prop2.get().expect("Failed to get prop2 value"), 100);
8286    }
8287
8288    #[test]
8289    fn test_with_max_threads_basic_functionality() {
8290        let prop = ObservableProperty::with_max_threads(0, 2);
8291        let call_count = Arc::new(AtomicUsize::new(0));
8292
8293        // Subscribe an observer
8294        let count = call_count.clone();
8295        let _subscription = prop
8296            .subscribe_with_subscription(Arc::new(move |old, new| {
8297                count.fetch_add(1, Ordering::SeqCst);
8298                assert_eq!(*old, 0);
8299                assert_eq!(*new, 42);
8300            }))
8301            .expect("Failed to create subscription for max_threads test");
8302
8303        // Test synchronous set
8304        prop.set(42).expect("Failed to set value synchronously");
8305        assert_eq!(call_count.load(Ordering::SeqCst), 1);
8306
8307        // Test asynchronous set
8308        prop.set_async(43).expect("Failed to set value asynchronously");
8309        
8310        // Wait for async observers to complete
8311        thread::sleep(Duration::from_millis(50));
8312        assert_eq!(call_count.load(Ordering::SeqCst), 2);
8313    }
8314
8315    #[test]
8316    fn test_with_max_threads_async_performance() {
8317        // Test that with_max_threads affects async performance
8318        let prop = ObservableProperty::with_max_threads(0, 1); // Single thread
8319        let slow_call_count = Arc::new(AtomicUsize::new(0));
8320
8321        // Add multiple slow observers
8322        let mut subscriptions = Vec::new();
8323        for _ in 0..4 {
8324            let count = slow_call_count.clone();
8325            let subscription = prop
8326                .subscribe_with_subscription(Arc::new(move |_, _| {
8327                    thread::sleep(Duration::from_millis(25)); // Simulate slow work
8328                    count.fetch_add(1, Ordering::SeqCst);
8329                }))
8330                .expect("Failed to create slow observer subscription");
8331            subscriptions.push(subscription);
8332        }
8333
8334        // Measure time for async notification
8335        let start = std::time::Instant::now();
8336        prop.set_async(42).expect("Failed to set value asynchronously");
8337        let async_duration = start.elapsed();
8338
8339        // Should return quickly even with slow observers
8340        assert!(async_duration.as_millis() < 50, "Async set should return quickly");
8341
8342        // Wait for all observers to complete
8343        thread::sleep(Duration::from_millis(200));
8344        assert_eq!(slow_call_count.load(Ordering::SeqCst), 4);
8345    }
8346
8347    #[test]
8348    fn test_with_max_threads_vs_regular_constructor() {
8349        let prop_regular = ObservableProperty::new(42);
8350        let prop_custom = ObservableProperty::with_max_threads(42, 4); // Same as default
8351
8352        let count_regular = Arc::new(AtomicUsize::new(0));
8353        let count_custom = Arc::new(AtomicUsize::new(0));
8354
8355        // Both should behave identically
8356        let count1 = count_regular.clone();
8357        let _sub1 = prop_regular
8358            .subscribe_with_subscription(Arc::new(move |_, _| {
8359                count1.fetch_add(1, Ordering::SeqCst);
8360            }))
8361            .expect("Failed to create regular subscription");
8362
8363        let count2 = count_custom.clone();
8364        let _sub2 = prop_custom
8365            .subscribe_with_subscription(Arc::new(move |_, _| {
8366                count2.fetch_add(1, Ordering::SeqCst);
8367            }))
8368            .expect("Failed to create custom subscription");
8369
8370        // Test sync behavior
8371        prop_regular.set(100).expect("Failed to set regular property");
8372        prop_custom.set(100).expect("Failed to set custom property");
8373
8374        assert_eq!(count_regular.load(Ordering::SeqCst), 1);
8375        assert_eq!(count_custom.load(Ordering::SeqCst), 1);
8376
8377        // Test async behavior
8378        prop_regular.set_async(200).expect("Failed to set regular property async");
8379        prop_custom.set_async(200).expect("Failed to set custom property async");
8380
8381        thread::sleep(Duration::from_millis(50));
8382        assert_eq!(count_regular.load(Ordering::SeqCst), 2);
8383        assert_eq!(count_custom.load(Ordering::SeqCst), 2);
8384    }
8385
8386    #[test]
8387    fn test_with_max_threads_large_values() {
8388        // Test with very large max_threads values
8389        let prop = ObservableProperty::with_max_threads(0, 1000);
8390        let call_count = Arc::new(AtomicUsize::new(0));
8391
8392        // Add a few observers
8393        let count = call_count.clone();
8394        let _subscription = prop
8395            .subscribe_with_subscription(Arc::new(move |_, _| {
8396                count.fetch_add(1, Ordering::SeqCst);
8397            }))
8398            .expect("Failed to create subscription for large max_threads test");
8399
8400        // Should work normally even with excessive thread limit
8401        prop.set_async(42).expect("Failed to set value with large max_threads");
8402
8403        thread::sleep(Duration::from_millis(50));
8404        assert_eq!(call_count.load(Ordering::SeqCst), 1);
8405    }
8406
8407    #[test]
8408    fn test_with_max_threads_clone_behavior() {
8409        let prop1 = ObservableProperty::with_max_threads(42, 2);
8410        let prop2 = prop1.clone();
8411
8412        let call_count1 = Arc::new(AtomicUsize::new(0));
8413        let call_count2 = Arc::new(AtomicUsize::new(0));
8414
8415        // Subscribe to both properties
8416        let count1 = call_count1.clone();
8417        let _sub1 = prop1
8418            .subscribe_with_subscription(Arc::new(move |_, _| {
8419                count1.fetch_add(1, Ordering::SeqCst);
8420            }))
8421            .expect("Failed to create subscription for cloned property test");
8422
8423        let count2 = call_count2.clone();
8424        let _sub2 = prop2
8425            .subscribe_with_subscription(Arc::new(move |_, _| {
8426                count2.fetch_add(1, Ordering::SeqCst);
8427            }))
8428            .expect("Failed to create subscription for original property test");
8429
8430        // Changes through either property should trigger both observers
8431        prop1.set_async(100).expect("Failed to set value through prop1");
8432        thread::sleep(Duration::from_millis(50));
8433        
8434        assert_eq!(call_count1.load(Ordering::SeqCst), 1);
8435        assert_eq!(call_count2.load(Ordering::SeqCst), 1);
8436
8437        prop2.set_async(200).expect("Failed to set value through prop2");
8438        thread::sleep(Duration::from_millis(50));
8439        
8440        assert_eq!(call_count1.load(Ordering::SeqCst), 2);
8441        assert_eq!(call_count2.load(Ordering::SeqCst), 2);
8442    }
8443
8444    #[test]
8445    fn test_with_max_threads_thread_safety() {
8446        let prop = Arc::new(ObservableProperty::with_max_threads(0, 3));
8447        let call_count = Arc::new(AtomicUsize::new(0));
8448
8449        // Add observers from multiple threads
8450        let handles: Vec<_> = (0..5)
8451            .map(|thread_id| {
8452                let prop_clone = prop.clone();
8453                let count_clone = call_count.clone();
8454
8455                thread::spawn(move || {
8456                    let count = count_clone.clone();
8457                    let _subscription = prop_clone
8458                        .subscribe_with_subscription(Arc::new(move |_, _| {
8459                            count.fetch_add(1, Ordering::SeqCst);
8460                        }))
8461                        .expect("Failed to create thread-safe subscription");
8462
8463                    // Trigger async notifications from this thread
8464                    prop_clone
8465                        .set_async(thread_id * 10)
8466                        .expect("Failed to set value from thread");
8467                        
8468                    thread::sleep(Duration::from_millis(10));
8469                })
8470            })
8471            .collect();
8472
8473        // Wait for all threads to complete
8474        for handle in handles {
8475            handle.join().expect("Thread should complete successfully");
8476        }
8477
8478        // Wait for all async operations to complete
8479        thread::sleep(Duration::from_millis(100));
8480
8481        // Each set_async should trigger all active observers at that time
8482        // The exact count depends on timing, but should be > 0
8483        assert!(call_count.load(Ordering::SeqCst) > 0);
8484    }
8485
8486    #[test]
8487    fn test_with_max_threads_error_handling() {
8488        let prop = ObservableProperty::with_max_threads(42, 2);
8489        
8490        // Test that error handling works the same as regular properties
8491        let _subscription = prop
8492            .subscribe_with_subscription(Arc::new(|_, _| {
8493                // Normal observer
8494            }))
8495            .expect("Failed to create subscription for error handling test");
8496
8497        // Should handle errors gracefully
8498        assert!(prop.set(100).is_ok());
8499        assert!(prop.set_async(200).is_ok());
8500        assert_eq!(prop.get().expect("Failed to get value after error test"), 200);
8501    }
8502
8503    #[test]
8504    fn test_observer_limit_enforcement() {
8505        let prop = ObservableProperty::new(0);
8506        let mut observer_ids = Vec::new();
8507
8508        // Add observers up to the limit (using a small test to avoid slow tests)
8509        // In reality, MAX_OBSERVERS is 10,000, but we'll test the mechanism
8510        // by adding a reasonable number and then checking the error message
8511        for i in 0..100 {
8512            let result = prop.subscribe(Arc::new(move |_, _| {
8513                let _ = i; // Use the capture to make each observer unique
8514            }));
8515            assert!(result.is_ok(), "Should be able to add observer {}", i);
8516            observer_ids.push(result.unwrap());
8517        }
8518
8519        // Verify all observers were added
8520        assert_eq!(observer_ids.len(), 100);
8521
8522        // Verify we can still add more (we're well under the 10,000 limit)
8523        let result = prop.subscribe(Arc::new(|_, _| {}));
8524        assert!(result.is_ok());
8525    }
8526
8527    #[test]
8528    fn test_observer_limit_error_message() {
8529        let prop = ObservableProperty::new(0);
8530        
8531        // We can't easily test hitting the actual 10,000 limit in a unit test
8532        // (it would be too slow), but we can verify the error type exists
8533        // and the subscribe method has the check
8534        
8535        // Add a few observers successfully
8536        for _ in 0..10 {
8537            assert!(prop.subscribe(Arc::new(|_, _| {})).is_ok());
8538        }
8539
8540        // The mechanism is in place - the limit check happens before insertion
8541        // In production, if 10,000 observers are added, the 10,001st will fail
8542    }
8543
8544    #[test]
8545    fn test_observer_limit_with_unsubscribe() {
8546        let prop = ObservableProperty::new(0);
8547        
8548        // Add observers
8549        let mut ids = Vec::new();
8550        for _ in 0..50 {
8551            ids.push(prop.subscribe(Arc::new(|_, _| {})).expect("Failed to subscribe"));
8552        }
8553
8554        // Remove half of them
8555        for id in ids.iter().take(25) {
8556            assert!(prop.unsubscribe(*id).expect("Failed to unsubscribe"));
8557        }
8558
8559        // Should be able to add more observers after unsubscribing
8560        for _ in 0..30 {
8561            assert!(prop.subscribe(Arc::new(|_, _| {})).is_ok());
8562        }
8563    }
8564
8565    #[test]
8566    fn test_observer_limit_with_raii_subscriptions() {
8567        let prop = ObservableProperty::new(0);
8568        
8569        // Create RAII subscriptions
8570        let mut subscriptions = Vec::new();
8571        for _ in 0..50 {
8572            subscriptions.push(
8573                prop.subscribe_with_subscription(Arc::new(|_, _| {}))
8574                    .expect("Failed to create subscription")
8575            );
8576        }
8577
8578        // Drop half of them (automatic cleanup)
8579        subscriptions.truncate(25);
8580
8581        // Should be able to add more after RAII cleanup
8582        for _ in 0..30 {
8583            let _sub = prop.subscribe_with_subscription(Arc::new(|_, _| {}))
8584                .expect("Failed to create subscription after RAII cleanup");
8585        }
8586    }
8587
8588    #[test]
8589    fn test_filtered_subscription_respects_observer_limit() {
8590        let prop = ObservableProperty::new(0);
8591        
8592        // Add regular and filtered observers
8593        for i in 0..50 {
8594            if i % 2 == 0 {
8595                assert!(prop.subscribe(Arc::new(|_, _| {})).is_ok());
8596            } else {
8597                assert!(prop.subscribe_filtered(Arc::new(|_, _| {}), |_, _| true).is_ok());
8598            }
8599        }
8600
8601        // Both types count toward the limit
8602        // Should still be well under the 10,000 limit
8603        assert!(prop.subscribe_filtered(Arc::new(|_, _| {}), |_, _| true).is_ok());
8604    }
8605
8606    #[test]
8607    fn test_observer_limit_concurrent_subscriptions() {
8608        let prop = Arc::new(ObservableProperty::new(0));
8609        let success_count = Arc::new(AtomicUsize::new(0));
8610
8611        // Try to add observers from multiple threads
8612        let handles: Vec<_> = (0..10)
8613            .map(|_| {
8614                let prop_clone = prop.clone();
8615                let count_clone = success_count.clone();
8616                
8617                thread::spawn(move || {
8618                    for _ in 0..10 {
8619                        if prop_clone.subscribe(Arc::new(|_, _| {})).is_ok() {
8620                            count_clone.fetch_add(1, Ordering::SeqCst);
8621                        }
8622                        thread::sleep(Duration::from_micros(10));
8623                    }
8624                })
8625            })
8626            .collect();
8627
8628        for handle in handles {
8629            handle.join().expect("Thread should complete");
8630        }
8631
8632        // All 100 subscriptions should succeed (well under limit)
8633        assert_eq!(success_count.load(Ordering::SeqCst), 100);
8634    }
8635
8636    #[test]
8637    fn test_notify_observers_batch_releases_lock_early() {
8638        use std::sync::atomic::AtomicBool;
8639        
8640        let prop = Arc::new(ObservableProperty::new(0));
8641        let call_count = Arc::new(AtomicUsize::new(0));
8642        let started = Arc::new(AtomicBool::new(false));
8643        
8644        // Subscribe with a slow observer
8645        let started_clone = started.clone();
8646        let count_clone = call_count.clone();
8647        prop.subscribe(Arc::new(move |_, _| {
8648            started_clone.store(true, Ordering::SeqCst);
8649            count_clone.fetch_add(1, Ordering::SeqCst);
8650            // Simulate slow observer
8651            thread::sleep(Duration::from_millis(50));
8652        })).expect("Failed to subscribe");
8653        
8654        // Start batch notification in background
8655        let prop_clone = prop.clone();
8656        let batch_handle = thread::spawn(move || {
8657            prop_clone.notify_observers_batch(vec![(0, 1), (1, 2)]).expect("Failed to notify batch");
8658        });
8659        
8660        // Wait for observer to start
8661        while !started.load(Ordering::SeqCst) {
8662            thread::sleep(Duration::from_millis(1));
8663        }
8664        
8665        // Now verify we can still subscribe while observer is running
8666        // This proves the lock was released before observer execution
8667        let subscribe_result = prop.subscribe(Arc::new(|_, _| {
8668            // New observer
8669        }));
8670        
8671        assert!(subscribe_result.is_ok(), "Should be able to subscribe while batch notification is in progress");
8672        
8673        // Wait for batch to complete
8674        batch_handle.join().expect("Batch thread should complete");
8675        
8676        // Verify observers were called (2 changes in batch)
8677        assert_eq!(call_count.load(Ordering::SeqCst), 2);
8678    }
8679
8680    #[test]
8681    fn test_notify_observers_batch_panic_isolation() {
8682        let prop = ObservableProperty::new(0);
8683        let good_observer_count = Arc::new(AtomicUsize::new(0));
8684        let count_clone = good_observer_count.clone();
8685        
8686        // First observer that panics
8687        prop.subscribe(Arc::new(|_, _| {
8688            panic!("Deliberate panic in batch observer");
8689        })).expect("Failed to subscribe panicking observer");
8690        
8691        // Second observer that should still be called
8692        prop.subscribe(Arc::new(move |_, _| {
8693            count_clone.fetch_add(1, Ordering::SeqCst);
8694        })).expect("Failed to subscribe good observer");
8695        
8696        // Batch notification should not fail despite panic
8697        let result = prop.notify_observers_batch(vec![(0, 1), (1, 2), (2, 3)]);
8698        assert!(result.is_ok());
8699        
8700        // Second observer should have been called for all 3 changes
8701        assert_eq!(good_observer_count.load(Ordering::SeqCst), 3);
8702    }
8703
8704    #[test]
8705    fn test_notify_observers_batch_multiple_changes() {
8706        let prop = ObservableProperty::new(0);
8707        let received_changes = Arc::new(RwLock::new(Vec::new()));
8708        let changes_clone = received_changes.clone();
8709        
8710        prop.subscribe(Arc::new(move |old, new| {
8711            if let Ok(mut changes) = changes_clone.write() {
8712                changes.push((*old, *new));
8713            }
8714        })).expect("Failed to subscribe");
8715        
8716        // Send multiple changes
8717        prop.notify_observers_batch(vec![
8718            (0, 10),
8719            (10, 20),
8720            (20, 30),
8721            (30, 40),
8722        ]).expect("Failed to notify batch");
8723        
8724        let changes = received_changes.read().expect("Failed to read changes");
8725        assert_eq!(changes.len(), 4);
8726        assert_eq!(changes[0], (0, 10));
8727        assert_eq!(changes[1], (10, 20));
8728        assert_eq!(changes[2], (20, 30));
8729        assert_eq!(changes[3], (30, 40));
8730    }
8731
8732    #[test]
8733    fn test_notify_observers_batch_empty() {
8734        let prop = ObservableProperty::new(0);
8735        let call_count = Arc::new(AtomicUsize::new(0));
8736        let count_clone = call_count.clone();
8737        
8738        prop.subscribe(Arc::new(move |_, _| {
8739            count_clone.fetch_add(1, Ordering::SeqCst);
8740        })).expect("Failed to subscribe");
8741        
8742        // Empty batch should succeed without calling observers
8743        prop.notify_observers_batch(vec![]).expect("Failed with empty batch");
8744        
8745        assert_eq!(call_count.load(Ordering::SeqCst), 0);
8746    }
8747
8748    // ========================================================================
8749    // Debouncing Tests
8750    // ========================================================================
8751
8752    #[test]
8753    fn test_debounced_observer_basic() {
8754        let prop = ObservableProperty::new(0);
8755        let notification_count = Arc::new(AtomicUsize::new(0));
8756        let last_value = Arc::new(RwLock::new(0));
8757        
8758        let count_clone = notification_count.clone();
8759        let value_clone = last_value.clone();
8760        
8761        prop.subscribe_debounced(
8762            Arc::new(move |_old, new| {
8763                count_clone.fetch_add(1, Ordering::SeqCst);
8764                if let Ok(mut val) = value_clone.write() {
8765                    *val = *new;
8766                }
8767            }),
8768            Duration::from_millis(100)
8769        ).expect("Failed to subscribe debounced observer");
8770        
8771        // Make a single change
8772        prop.set(42).expect("Failed to set value");
8773        
8774        // Immediately after, should not have been notified yet
8775        assert_eq!(notification_count.load(Ordering::SeqCst), 0);
8776        
8777        // Wait for debounce period
8778        thread::sleep(Duration::from_millis(150));
8779        
8780        // Now should have been notified exactly once
8781        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
8782        assert_eq!(*last_value.read().unwrap(), 42);
8783    }
8784
8785    #[test]
8786    fn test_debounced_observer_rapid_changes() {
8787        let prop = ObservableProperty::new(0);
8788        let notification_count = Arc::new(AtomicUsize::new(0));
8789        let last_value = Arc::new(RwLock::new(0));
8790        
8791        let count_clone = notification_count.clone();
8792        let value_clone = last_value.clone();
8793        
8794        prop.subscribe_debounced(
8795            Arc::new(move |_old, new| {
8796                count_clone.fetch_add(1, Ordering::SeqCst);
8797                if let Ok(mut val) = value_clone.write() {
8798                    *val = *new;
8799                }
8800            }),
8801            Duration::from_millis(100)
8802        ).expect("Failed to subscribe debounced observer");
8803        
8804        // Make rapid changes
8805        for i in 1..=10 {
8806            prop.set(i).expect("Failed to set value");
8807            thread::sleep(Duration::from_millis(20)); // Changes every 20ms
8808        }
8809        
8810        // Should not have been notified yet
8811        assert_eq!(notification_count.load(Ordering::SeqCst), 0);
8812        
8813        // Wait for debounce period after last change
8814        thread::sleep(Duration::from_millis(150));
8815        
8816        // Should have been notified exactly once with the final value
8817        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
8818        assert_eq!(*last_value.read().unwrap(), 10);
8819    }
8820
8821    #[test]
8822    fn test_debounced_observer_multiple_sequences() {
8823        let prop = ObservableProperty::new(0);
8824        let notification_count = Arc::new(AtomicUsize::new(0));
8825        let values = Arc::new(RwLock::new(Vec::new()));
8826        
8827        let count_clone = notification_count.clone();
8828        let values_clone = values.clone();
8829        
8830        prop.subscribe_debounced(
8831            Arc::new(move |_old, new| {
8832                count_clone.fetch_add(1, Ordering::SeqCst);
8833                if let Ok(mut vals) = values_clone.write() {
8834                    vals.push(*new);
8835                }
8836            }),
8837            Duration::from_millis(100)
8838        ).expect("Failed to subscribe debounced observer");
8839        
8840        // First sequence of changes
8841        prop.set(1).expect("Failed to set value");
8842        prop.set(2).expect("Failed to set value");
8843        prop.set(3).expect("Failed to set value");
8844        
8845        // Wait for debounce
8846        thread::sleep(Duration::from_millis(150));
8847        
8848        // Second sequence of changes
8849        prop.set(4).expect("Failed to set value");
8850        prop.set(5).expect("Failed to set value");
8851        
8852        // Wait for debounce
8853        thread::sleep(Duration::from_millis(150));
8854        
8855        // Should have been notified twice, once for each sequence
8856        assert_eq!(notification_count.load(Ordering::SeqCst), 2);
8857        let vals = values.read().unwrap();
8858        assert_eq!(vals.len(), 2);
8859        assert_eq!(vals[0], 3); // Last value from first sequence
8860        assert_eq!(vals[1], 5); // Last value from second sequence
8861    }
8862
8863    #[test]
8864    fn test_debounced_observer_with_string() {
8865        let prop = ObservableProperty::new("".to_string());
8866        let notification_count = Arc::new(AtomicUsize::new(0));
8867        let last_value = Arc::new(RwLock::new(String::new()));
8868        
8869        let count_clone = notification_count.clone();
8870        let value_clone = last_value.clone();
8871        
8872        prop.subscribe_debounced(
8873            Arc::new(move |_old, new| {
8874                count_clone.fetch_add(1, Ordering::SeqCst);
8875                if let Ok(mut val) = value_clone.write() {
8876                    *val = new.clone();
8877                }
8878            }),
8879            Duration::from_millis(100)
8880        ).expect("Failed to subscribe debounced observer");
8881        
8882        // Simulate typing
8883        prop.set("H".to_string()).expect("Failed to set value");
8884        thread::sleep(Duration::from_millis(30));
8885        prop.set("He".to_string()).expect("Failed to set value");
8886        thread::sleep(Duration::from_millis(30));
8887        prop.set("Hel".to_string()).expect("Failed to set value");
8888        thread::sleep(Duration::from_millis(30));
8889        prop.set("Hell".to_string()).expect("Failed to set value");
8890        thread::sleep(Duration::from_millis(30));
8891        prop.set("Hello".to_string()).expect("Failed to set value");
8892        
8893        // Should not have been notified during typing
8894        assert_eq!(notification_count.load(Ordering::SeqCst), 0);
8895        
8896        // Wait for debounce period
8897        thread::sleep(Duration::from_millis(150));
8898        
8899        // Should have been notified once with final value
8900        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
8901        assert_eq!(*last_value.read().unwrap(), "Hello");
8902    }
8903
8904    #[test]
8905    fn test_debounced_observer_zero_duration() {
8906        let prop = ObservableProperty::new(0);
8907        let notification_count = Arc::new(AtomicUsize::new(0));
8908        
8909        let count_clone = notification_count.clone();
8910        
8911        prop.subscribe_debounced(
8912            Arc::new(move |_old, _new| {
8913                count_clone.fetch_add(1, Ordering::SeqCst);
8914            }),
8915            Duration::from_millis(0)
8916        ).expect("Failed to subscribe debounced observer");
8917        
8918        prop.set(1).expect("Failed to set value");
8919        
8920        // Even with zero duration, thread needs time to execute
8921        thread::sleep(Duration::from_millis(10));
8922        
8923        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
8924    }
8925
8926    // ========================================================================
8927    // Throttling Tests
8928    // ========================================================================
8929
8930    #[test]
8931    fn test_throttled_observer_basic() {
8932        let prop = ObservableProperty::new(0);
8933        let notification_count = Arc::new(AtomicUsize::new(0));
8934        let values = Arc::new(RwLock::new(Vec::new()));
8935        
8936        let count_clone = notification_count.clone();
8937        let values_clone = values.clone();
8938        
8939        prop.subscribe_throttled(
8940            Arc::new(move |_old, new| {
8941                count_clone.fetch_add(1, Ordering::SeqCst);
8942                if let Ok(mut vals) = values_clone.write() {
8943                    vals.push(*new);
8944                }
8945            }),
8946            Duration::from_millis(100)
8947        ).expect("Failed to subscribe throttled observer");
8948        
8949        // First change should trigger immediately
8950        prop.set(1).expect("Failed to set value");
8951        thread::sleep(Duration::from_millis(10));
8952        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
8953        
8954        // Second change within throttle period should be delayed
8955        prop.set(2).expect("Failed to set value");
8956        thread::sleep(Duration::from_millis(10));
8957        // Still only 1 notification
8958        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
8959        
8960        // Wait for throttle period
8961        thread::sleep(Duration::from_millis(100));
8962        
8963        // Now should have 2 notifications
8964        assert_eq!(notification_count.load(Ordering::SeqCst), 2);
8965        let vals = values.read().unwrap();
8966        assert_eq!(vals.len(), 2);
8967        assert_eq!(vals[0], 1);
8968        assert_eq!(vals[1], 2);
8969    }
8970
8971    #[test]
8972    fn test_throttled_observer_continuous_changes() {
8973        let prop = ObservableProperty::new(0);
8974        let notification_count = Arc::new(AtomicUsize::new(0));
8975        
8976        let count_clone = notification_count.clone();
8977        
8978        prop.subscribe_throttled(
8979            Arc::new(move |_old, _new| {
8980                count_clone.fetch_add(1, Ordering::SeqCst);
8981            }),
8982            Duration::from_millis(100)
8983        ).expect("Failed to subscribe throttled observer");
8984        
8985        // Make changes every 20ms for 500ms total
8986        for i in 1..=25 {
8987            prop.set(i).expect("Failed to set value");
8988            thread::sleep(Duration::from_millis(20));
8989        }
8990        
8991        // Wait for any pending notifications
8992        thread::sleep(Duration::from_millis(150));
8993        
8994        let count = notification_count.load(Ordering::SeqCst);
8995        // Should have been notified multiple times (roughly every 100ms)
8996        // Expecting around 5-6 notifications over 500ms
8997        assert!(count >= 4, "Expected at least 4 notifications, got {}", count);
8998        assert!(count <= 10, "Expected at most 10 notifications, got {}", count);
8999    }
9000
9001    #[test]
9002    fn test_throttled_observer_rate_limiting() {
9003        let prop = ObservableProperty::new(0);
9004        let notification_count = Arc::new(AtomicUsize::new(0));
9005        let values = Arc::new(RwLock::new(Vec::new()));
9006        
9007        let count_clone = notification_count.clone();
9008        let values_clone = values.clone();
9009        
9010        prop.subscribe_throttled(
9011            Arc::new(move |_old, new| {
9012                count_clone.fetch_add(1, Ordering::SeqCst);
9013                if let Ok(mut vals) = values_clone.write() {
9014                    vals.push(*new);
9015                }
9016            }),
9017            Duration::from_millis(200)
9018        ).expect("Failed to subscribe throttled observer");
9019        
9020        // Rapid-fire 20 changes
9021        for i in 1..=20 {
9022            prop.set(i).expect("Failed to set value");
9023            thread::sleep(Duration::from_millis(10));
9024        }
9025        
9026        // Wait for any pending notifications
9027        thread::sleep(Duration::from_millis(250));
9028        
9029        let count = notification_count.load(Ordering::SeqCst);
9030        // With 200ms throttle and changes every 10ms (200ms total duration),
9031        // should get 2 notifications maximum (1 immediate + 1 delayed)
9032        assert!(count >= 1, "Expected at least 1 notification, got {}", count);
9033        assert!(count <= 3, "Expected at most 3 notifications, got {}", count);
9034    }
9035
9036    #[test]
9037    fn test_throttled_observer_first_change_immediate() {
9038        let prop = ObservableProperty::new(0);
9039        let notification_count = Arc::new(AtomicUsize::new(0));
9040        let first_value = Arc::new(RwLock::new(None));
9041        
9042        let count_clone = notification_count.clone();
9043        let value_clone = first_value.clone();
9044        
9045        prop.subscribe_throttled(
9046            Arc::new(move |_old, new| {
9047                count_clone.fetch_add(1, Ordering::SeqCst);
9048                if let Ok(mut val) = value_clone.write() {
9049                    if val.is_none() {
9050                        *val = Some(*new);
9051                    }
9052                }
9053            }),
9054            Duration::from_millis(100)
9055        ).expect("Failed to subscribe throttled observer");
9056        
9057        // First change
9058        prop.set(42).expect("Failed to set value");
9059        
9060        // Should be notified immediately (no sleep needed)
9061        thread::sleep(Duration::from_millis(10));
9062        
9063        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
9064        assert_eq!(*first_value.read().unwrap(), Some(42));
9065    }
9066
9067    #[test]
9068    fn test_throttled_vs_debounced_behavior() {
9069        let prop = ObservableProperty::new(0);
9070        let throttle_count = Arc::new(AtomicUsize::new(0));
9071        let debounce_count = Arc::new(AtomicUsize::new(0));
9072        
9073        let throttle_clone = throttle_count.clone();
9074        let debounce_clone = debounce_count.clone();
9075        
9076        prop.subscribe_throttled(
9077            Arc::new(move |_old, _new| {
9078                throttle_clone.fetch_add(1, Ordering::SeqCst);
9079            }),
9080            Duration::from_millis(100)
9081        ).expect("Failed to subscribe throttled observer");
9082        
9083        prop.subscribe_debounced(
9084            Arc::new(move |_old, _new| {
9085                debounce_clone.fetch_add(1, Ordering::SeqCst);
9086            }),
9087            Duration::from_millis(100)
9088        ).expect("Failed to subscribe debounced observer");
9089        
9090        // Make continuous changes for 300ms
9091        for i in 1..=30 {
9092            prop.set(i).expect("Failed to set value");
9093            thread::sleep(Duration::from_millis(10));
9094        }
9095        
9096        // Wait for debounce to complete
9097        thread::sleep(Duration::from_millis(150));
9098        
9099        let throttle_notifications = throttle_count.load(Ordering::SeqCst);
9100        let debounce_notifications = debounce_count.load(Ordering::SeqCst);
9101        
9102        // Throttled: Multiple notifications during the period
9103        assert!(throttle_notifications >= 2, 
9104            "Throttled should have multiple notifications, got {}", throttle_notifications);
9105        
9106        // Debounced: Single notification after changes stopped
9107        assert_eq!(debounce_notifications, 1, 
9108            "Debounced should have exactly 1 notification, got {}", debounce_notifications);
9109        
9110        // Throttled should have more notifications than debounced
9111        assert!(throttle_notifications > debounce_notifications,
9112            "Throttled ({}) should have more notifications than debounced ({})",
9113            throttle_notifications, debounce_notifications);
9114    }
9115
9116    #[test]
9117    fn test_throttled_observer_with_long_interval() {
9118        let prop = ObservableProperty::new(0);
9119        let notification_count = Arc::new(AtomicUsize::new(0));
9120        
9121        let count_clone = notification_count.clone();
9122        
9123        prop.subscribe_throttled(
9124            Arc::new(move |_old, _new| {
9125                count_clone.fetch_add(1, Ordering::SeqCst);
9126            }),
9127            Duration::from_secs(1)
9128        ).expect("Failed to subscribe throttled observer");
9129        
9130        // First change - immediate
9131        prop.set(1).expect("Failed to set value");
9132        thread::sleep(Duration::from_millis(10));
9133        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
9134        
9135        // Multiple changes within the throttle period
9136        for i in 2..=5 {
9137            prop.set(i).expect("Failed to set value");
9138            thread::sleep(Duration::from_millis(50));
9139        }
9140        
9141        // Still only 1 notification (throttle period hasn't expired)
9142        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
9143        
9144        // Wait for throttle period to expire
9145        thread::sleep(Duration::from_millis(1100));
9146        
9147        // Should now have 2 notifications (initial + delayed for last change)
9148        let final_count = notification_count.load(Ordering::SeqCst);
9149        assert!(final_count >= 1 && final_count <= 2,
9150            "Expected 1-2 notifications, got {}", final_count);
9151    }
9152
9153    #[test]
9154    fn test_debounced_and_throttled_combined() {
9155        let prop = ObservableProperty::new(0);
9156        let debounce_values = Arc::new(RwLock::new(Vec::new()));
9157        let throttle_values = Arc::new(RwLock::new(Vec::new()));
9158        
9159        let debounce_clone = debounce_values.clone();
9160        let throttle_clone = throttle_values.clone();
9161        
9162        prop.subscribe_debounced(
9163            Arc::new(move |_old, new| {
9164                if let Ok(mut vals) = debounce_clone.write() {
9165                    vals.push(*new);
9166                }
9167            }),
9168            Duration::from_millis(100)
9169        ).expect("Failed to subscribe debounced");
9170        
9171        prop.subscribe_throttled(
9172            Arc::new(move |_old, new| {
9173                if let Ok(mut vals) = throttle_clone.write() {
9174                    vals.push(*new);
9175                }
9176            }),
9177            Duration::from_millis(100)
9178        ).expect("Failed to subscribe throttled");
9179        
9180        // Make a series of changes
9181        for i in 1..=10 {
9182            prop.set(i).expect("Failed to set value");
9183            thread::sleep(Duration::from_millis(25));
9184        }
9185        
9186        // Wait for both to complete
9187        thread::sleep(Duration::from_millis(200));
9188        
9189        let debounce_vals = debounce_values.read().unwrap();
9190        let throttle_vals = throttle_values.read().unwrap();
9191        
9192        // Debounced should have 1 value (the last one)
9193        assert_eq!(debounce_vals.len(), 1);
9194        assert_eq!(debounce_vals[0], 10);
9195        
9196        // Throttled should have multiple values
9197        assert!(throttle_vals.len() >= 2, 
9198            "Throttled should have at least 2 values, got {}", throttle_vals.len());
9199    }
9200
9201    // ========================================================================
9202    // Computed Properties Tests
9203    // ========================================================================
9204
9205    #[test]
9206    fn test_computed_basic() {
9207        let a = Arc::new(ObservableProperty::new(5));
9208        let b = Arc::new(ObservableProperty::new(10));
9209
9210        let sum = computed(
9211            vec![a.clone(), b.clone()],
9212            |values| values[0] + values[1]
9213        ).expect("Failed to create computed property");
9214
9215        assert_eq!(sum.get().unwrap(), 15);
9216
9217        a.set(7).expect("Failed to set a");
9218        thread::sleep(Duration::from_millis(10));
9219        assert_eq!(sum.get().unwrap(), 17);
9220
9221        b.set(3).expect("Failed to set b");
9222        thread::sleep(Duration::from_millis(10));
9223        assert_eq!(sum.get().unwrap(), 10);
9224    }
9225
9226    #[test]
9227    fn test_computed_with_observer() {
9228        let width = Arc::new(ObservableProperty::new(10));
9229        let height = Arc::new(ObservableProperty::new(5));
9230
9231        let area = computed(
9232            vec![width.clone(), height.clone()],
9233            |values| values[0] * values[1]
9234        ).expect("Failed to create computed property");
9235
9236        let notification_count = Arc::new(AtomicUsize::new(0));
9237        let count_clone = notification_count.clone();
9238
9239        area.subscribe(Arc::new(move |_old, _new| {
9240            count_clone.fetch_add(1, Ordering::SeqCst);
9241        })).expect("Failed to subscribe");
9242
9243        assert_eq!(area.get().unwrap(), 50);
9244
9245        width.set(20).expect("Failed to set width");
9246        thread::sleep(Duration::from_millis(10));
9247        assert_eq!(area.get().unwrap(), 100);
9248        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
9249
9250        height.set(8).expect("Failed to set height");
9251        thread::sleep(Duration::from_millis(10));
9252        assert_eq!(area.get().unwrap(), 160);
9253        assert_eq!(notification_count.load(Ordering::SeqCst), 2);
9254    }
9255
9256    #[test]
9257    fn test_computed_string_concatenation() {
9258        let first = Arc::new(ObservableProperty::new("Hello".to_string()));
9259        let last = Arc::new(ObservableProperty::new("World".to_string()));
9260
9261        let full = computed(
9262            vec![first.clone(), last.clone()],
9263            |values| format!("{} {}", values[0], values[1])
9264        ).expect("Failed to create computed property");
9265
9266        assert_eq!(full.get().unwrap(), "Hello World");
9267
9268        first.set("Goodbye".to_string()).expect("Failed to set first");
9269        thread::sleep(Duration::from_millis(10));
9270        assert_eq!(full.get().unwrap(), "Goodbye World");
9271
9272        last.set("Rust".to_string()).expect("Failed to set last");
9273        thread::sleep(Duration::from_millis(10));
9274        assert_eq!(full.get().unwrap(), "Goodbye Rust");
9275    }
9276
9277    #[test]
9278    fn test_computed_chaining() {
9279        let celsius = Arc::new(ObservableProperty::new(0.0));
9280
9281        let fahrenheit = computed(
9282            vec![celsius.clone()],
9283            |values| values[0] * 9.0 / 5.0 + 32.0
9284        ).expect("Failed to create fahrenheit");
9285
9286        let kelvin = computed(
9287            vec![celsius.clone()],
9288            |values| values[0] + 273.15
9289        ).expect("Failed to create kelvin");
9290
9291        assert_eq!(celsius.get().unwrap(), 0.0);
9292        assert_eq!(fahrenheit.get().unwrap(), 32.0);
9293        assert_eq!(kelvin.get().unwrap(), 273.15);
9294
9295        celsius.set(100.0).expect("Failed to set celsius");
9296        thread::sleep(Duration::from_millis(10));
9297        assert_eq!(fahrenheit.get().unwrap(), 212.0);
9298        assert_eq!(kelvin.get().unwrap(), 373.15);
9299    }
9300
9301    #[test]
9302    fn test_computed_multiple_dependencies() {
9303        let a = Arc::new(ObservableProperty::new(1));
9304        let b = Arc::new(ObservableProperty::new(2));
9305        let c = Arc::new(ObservableProperty::new(3));
9306
9307        let result = computed(
9308            vec![a.clone(), b.clone(), c.clone()],
9309            |values| values[0] + values[1] * values[2]
9310        ).expect("Failed to create computed property");
9311
9312        // Initial: 1 + 2 * 3 = 7
9313        assert_eq!(result.get().unwrap(), 7);
9314
9315        a.set(5).expect("Failed to set a");
9316        thread::sleep(Duration::from_millis(10));
9317        // 5 + 2 * 3 = 11
9318        assert_eq!(result.get().unwrap(), 11);
9319
9320        b.set(4).expect("Failed to set b");
9321        thread::sleep(Duration::from_millis(10));
9322        // 5 + 4 * 3 = 17
9323        assert_eq!(result.get().unwrap(), 17);
9324
9325        c.set(2).expect("Failed to set c");
9326        thread::sleep(Duration::from_millis(10));
9327        // 5 + 4 * 2 = 13
9328        assert_eq!(result.get().unwrap(), 13);
9329    }
9330
9331    #[test]
9332    fn test_computed_single_dependency() {
9333        let number = Arc::new(ObservableProperty::new(5));
9334
9335        let doubled = computed(
9336            vec![number.clone()],
9337            |values| values[0] * 2
9338        ).expect("Failed to create computed property");
9339
9340        assert_eq!(doubled.get().unwrap(), 10);
9341
9342        number.set(7).expect("Failed to set number");
9343        thread::sleep(Duration::from_millis(10));
9344        assert_eq!(doubled.get().unwrap(), 14);
9345    }
9346
9347    #[test]
9348    fn test_computed_complex_calculation() {
9349        let base = Arc::new(ObservableProperty::new(10.0_f64));
9350        let rate = Arc::new(ObservableProperty::new(0.05_f64));
9351        let years = Arc::new(ObservableProperty::new(2.0_f64));
9352
9353        // Compound interest formula: A = P(1 + r)^t
9354        let amount = computed(
9355            vec![base.clone(), rate.clone(), years.clone()],
9356            |values| values[0] * (1.0 + values[1]).powf(values[2])
9357        ).expect("Failed to create computed property");
9358
9359        // 10 * (1.05)^2 ≈ 11.025
9360        let initial_amount = amount.get().unwrap();
9361        assert!((initial_amount - 11.025_f64).abs() < 0.001);
9362
9363        base.set(100.0_f64).expect("Failed to set base");
9364        thread::sleep(Duration::from_millis(10));
9365        // 100 * (1.05)^2 ≈ 110.25
9366        let new_amount = amount.get().unwrap();
9367        assert!((new_amount - 110.25_f64).abs() < 0.001);
9368
9369        years.set(5.0_f64).expect("Failed to set years");
9370        thread::sleep(Duration::from_millis(10));
9371        // 100 * (1.05)^5 ≈ 127.628
9372        let final_amount = amount.get().unwrap();
9373        assert!((final_amount - 127.628_f64).abs() < 0.001);
9374    }
9375
9376    #[test]
9377    fn test_update_batch_basic() {
9378        let prop = ObservableProperty::new(0);
9379        let notifications = Arc::new(RwLock::new(Vec::new()));
9380        let notifications_clone = notifications.clone();
9381
9382        prop.subscribe(Arc::new(move |old, new| {
9383            if let Ok(mut notifs) = notifications_clone.write() {
9384                notifs.push((*old, *new));
9385            }
9386        })).expect("Failed to subscribe");
9387
9388        prop.update_batch(|_current| {
9389            vec![10, 20, 30]
9390        }).expect("Failed to update_batch");
9391
9392        let notifs = notifications.read().unwrap();
9393        assert_eq!(notifs.len(), 3);
9394        assert_eq!(notifs[0], (0, 10));
9395        assert_eq!(notifs[1], (10, 20));
9396        assert_eq!(notifs[2], (20, 30));
9397        assert_eq!(prop.get().unwrap(), 30);
9398    }
9399
9400    #[test]
9401    fn test_update_batch_empty_vec() {
9402        let prop = ObservableProperty::new(42);
9403        let notification_count = Arc::new(AtomicUsize::new(0));
9404        let count_clone = notification_count.clone();
9405
9406        prop.subscribe(Arc::new(move |_, _| {
9407            count_clone.fetch_add(1, Ordering::SeqCst);
9408        })).expect("Failed to subscribe");
9409
9410        prop.update_batch(|current| {
9411            *current = 100; // This should be ignored
9412            Vec::new()
9413        }).expect("Failed to update_batch");
9414
9415        assert_eq!(notification_count.load(Ordering::SeqCst), 0);
9416        assert_eq!(prop.get().unwrap(), 42); // Value unchanged
9417    }
9418
9419    #[test]
9420    fn test_update_batch_single_state() {
9421        let prop = ObservableProperty::new(5);
9422        let notifications = Arc::new(RwLock::new(Vec::new()));
9423        let notifications_clone = notifications.clone();
9424
9425        prop.subscribe(Arc::new(move |old, new| {
9426            if let Ok(mut notifs) = notifications_clone.write() {
9427                notifs.push((*old, *new));
9428            }
9429        })).expect("Failed to subscribe");
9430
9431        prop.update_batch(|_current| {
9432            vec![10]
9433        }).expect("Failed to update_batch");
9434
9435        let notifs = notifications.read().unwrap();
9436        assert_eq!(notifs.len(), 1);
9437        assert_eq!(notifs[0], (5, 10));
9438        assert_eq!(prop.get().unwrap(), 10);
9439    }
9440
9441    #[test]
9442    fn test_update_batch_string_transformation() {
9443        let prop = ObservableProperty::new(String::from("hello"));
9444        let notifications = Arc::new(RwLock::new(Vec::new()));
9445        let notifications_clone = notifications.clone();
9446
9447        prop.subscribe(Arc::new(move |old, new| {
9448            if let Ok(mut notifs) = notifications_clone.write() {
9449                notifs.push((old.clone(), new.clone()));
9450            }
9451        })).expect("Failed to subscribe");
9452
9453        prop.update_batch(|current| {
9454            let step1 = current.to_uppercase();
9455            let step2 = format!("{}!", step1);
9456            let step3 = format!("{} WORLD", step2);
9457            vec![step1, step2, step3]
9458        }).expect("Failed to update_batch");
9459
9460        let notifs = notifications.read().unwrap();
9461        assert_eq!(notifs.len(), 3);
9462        assert_eq!(notifs[0].0, "hello");
9463        assert_eq!(notifs[0].1, "HELLO");
9464        assert_eq!(notifs[1].0, "HELLO");
9465        assert_eq!(notifs[1].1, "HELLO!");
9466        assert_eq!(notifs[2].0, "HELLO!");
9467        assert_eq!(notifs[2].1, "HELLO! WORLD");
9468        assert_eq!(prop.get().unwrap(), "HELLO! WORLD");
9469    }
9470
9471    #[test]
9472    fn test_update_batch_multiple_observers() {
9473        let prop = ObservableProperty::new(0);
9474        let count1 = Arc::new(AtomicUsize::new(0));
9475        let count2 = Arc::new(AtomicUsize::new(0));
9476        
9477        let count1_clone = count1.clone();
9478        let count2_clone = count2.clone();
9479
9480        prop.subscribe(Arc::new(move |_, _| {
9481            count1_clone.fetch_add(1, Ordering::SeqCst);
9482        })).expect("Failed to subscribe observer 1");
9483
9484        prop.subscribe(Arc::new(move |_, _| {
9485            count2_clone.fetch_add(1, Ordering::SeqCst);
9486        })).expect("Failed to subscribe observer 2");
9487
9488        prop.update_batch(|_current| {
9489            vec![1, 2, 3, 4, 5]
9490        }).expect("Failed to update_batch");
9491
9492        assert_eq!(count1.load(Ordering::SeqCst), 5);
9493        assert_eq!(count2.load(Ordering::SeqCst), 5);
9494        assert_eq!(prop.get().unwrap(), 5);
9495    }
9496
9497    #[test]
9498    fn test_update_batch_with_panicking_observer() {
9499        let prop = ObservableProperty::new(0);
9500        let good_observer_count = Arc::new(AtomicUsize::new(0));
9501        let count_clone = good_observer_count.clone();
9502
9503        // Observer that panics
9504        prop.subscribe(Arc::new(|old, _new| {
9505            if *old == 1 {
9506                panic!("Observer panic!");
9507            }
9508        })).expect("Failed to subscribe panicking observer");
9509
9510        // Observer that should still work
9511        prop.subscribe(Arc::new(move |_, _| {
9512            count_clone.fetch_add(1, Ordering::SeqCst);
9513        })).expect("Failed to subscribe good observer");
9514
9515        // Should not panic, good observer should still be notified
9516        prop.update_batch(|_current| {
9517            vec![1, 2, 3]
9518        }).expect("Failed to update_batch");
9519
9520        // Good observer should have been called for all 3 states
9521        assert_eq!(good_observer_count.load(Ordering::SeqCst), 3);
9522        assert_eq!(prop.get().unwrap(), 3);
9523    }
9524
9525    #[test]
9526    fn test_update_batch_thread_safety() {
9527        let prop = Arc::new(ObservableProperty::new(0));
9528        let notification_count = Arc::new(AtomicUsize::new(0));
9529        let count_clone = notification_count.clone();
9530
9531        prop.subscribe(Arc::new(move |_, _| {
9532            count_clone.fetch_add(1, Ordering::SeqCst);
9533        })).expect("Failed to subscribe");
9534
9535        let handles: Vec<_> = (0..5).map(|i| {
9536            let prop_clone = prop.clone();
9537            thread::spawn(move || {
9538                prop_clone.update_batch(|_current| {
9539                    vec![i * 10 + 1, i * 10 + 2, i * 10 + 3]
9540                }).expect("Failed to update_batch in thread");
9541            })
9542        }).collect();
9543
9544        for handle in handles {
9545            handle.join().unwrap();
9546        }
9547
9548        // 5 threads * 3 states each = 15 notifications
9549        assert_eq!(notification_count.load(Ordering::SeqCst), 15);
9550    }
9551
9552    #[test]
9553    fn test_update_batch_with_weak_observers() {
9554        let prop = ObservableProperty::new(0);
9555        let notification_count = Arc::new(AtomicUsize::new(0));
9556        let count_clone = notification_count.clone();
9557
9558        let observer: Arc<dyn Fn(&i32, &i32) + Send + Sync> = Arc::new(move |_, _| {
9559            count_clone.fetch_add(1, Ordering::SeqCst);
9560        });
9561
9562        prop.subscribe_weak(Arc::downgrade(&observer))
9563            .expect("Failed to subscribe weak observer");
9564
9565        // Observer is alive, should get notifications
9566        prop.update_batch(|_current| {
9567            vec![1, 2, 3]
9568        }).expect("Failed to update_batch");
9569
9570        assert_eq!(notification_count.load(Ordering::SeqCst), 3);
9571
9572        // Drop the observer
9573        drop(observer);
9574
9575        // Observer is dead, should not get notifications
9576        prop.update_batch(|_current| {
9577            vec![4, 5, 6]
9578        }).expect("Failed to update_batch");
9579
9580        // Count should still be 3 (no new notifications)
9581        assert_eq!(notification_count.load(Ordering::SeqCst), 3);
9582        assert_eq!(prop.get().unwrap(), 6);
9583    }
9584
9585    #[test]
9586    fn test_change_coalescing_basic() {
9587        let prop = ObservableProperty::new(0);
9588        let notification_count = Arc::new(AtomicUsize::new(0));
9589        let last_old = Arc::new(RwLock::new(0));
9590        let last_new = Arc::new(RwLock::new(0));
9591
9592        let count_clone = notification_count.clone();
9593        let old_clone = last_old.clone();
9594        let new_clone = last_new.clone();
9595
9596        prop.subscribe(Arc::new(move |old, new| {
9597            count_clone.fetch_add(1, Ordering::SeqCst);
9598            *old_clone.write().unwrap() = *old;
9599            *new_clone.write().unwrap() = *new;
9600        }))
9601        .expect("Failed to subscribe");
9602
9603        // Begin batch update
9604        prop.begin_update().expect("Failed to begin update");
9605
9606        // Multiple changes - should not trigger notifications
9607        prop.set(10).expect("Failed to set value");
9608        prop.set(20).expect("Failed to set value");
9609        prop.set(30).expect("Failed to set value");
9610
9611        // No notifications yet
9612        assert_eq!(notification_count.load(Ordering::SeqCst), 0);
9613        assert_eq!(prop.get().unwrap(), 30);
9614
9615        // End batch - should trigger single notification
9616        prop.end_update().expect("Failed to end update");
9617
9618        // Should have exactly one notification from 0 to 30
9619        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
9620        assert_eq!(*last_old.read().unwrap(), 0);
9621        assert_eq!(*last_new.read().unwrap(), 30);
9622    }
9623
9624    #[test]
9625    fn test_change_coalescing_nested() {
9626        let prop = ObservableProperty::new(100);
9627        let notification_count = Arc::new(AtomicUsize::new(0));
9628        let count_clone = notification_count.clone();
9629
9630        prop.subscribe(Arc::new(move |_, _| {
9631            count_clone.fetch_add(1, Ordering::SeqCst);
9632        }))
9633        .expect("Failed to subscribe");
9634
9635        // Start outer batch
9636        prop.begin_update().expect("Failed to begin update");
9637        prop.set(110).expect("Failed to set");
9638
9639        // Start inner batch
9640        prop.begin_update().expect("Failed to begin nested update");
9641        prop.set(120).expect("Failed to set");
9642        prop.set(130).expect("Failed to set");
9643        prop.end_update().expect("Failed to end nested update");
9644
9645        // Still no notifications (outer batch still active)
9646        assert_eq!(notification_count.load(Ordering::SeqCst), 0);
9647
9648        prop.set(140).expect("Failed to set");
9649        prop.end_update().expect("Failed to end outer update");
9650
9651        // Should have exactly one notification
9652        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
9653        assert_eq!(prop.get().unwrap(), 140);
9654    }
9655
9656    #[test]
9657    fn test_change_coalescing_without_begin() {
9658        let prop = ObservableProperty::new(0);
9659
9660        // Calling end_update without begin_update should fail
9661        let result = prop.end_update();
9662        assert!(result.is_err());
9663
9664        if let Err(PropertyError::InvalidConfiguration { reason }) = result {
9665            assert!(reason.contains("without matching begin_update"));
9666        } else {
9667            panic!("Expected InvalidConfiguration error");
9668        }
9669    }
9670
9671    #[test]
9672    fn test_change_coalescing_multiple_cycles() {
9673        let prop = ObservableProperty::new(0);
9674        let notification_count = Arc::new(AtomicUsize::new(0));
9675        let count_clone = notification_count.clone();
9676
9677        prop.subscribe(Arc::new(move |_, _| {
9678            count_clone.fetch_add(1, Ordering::SeqCst);
9679        }))
9680        .expect("Failed to subscribe");
9681
9682        // First batch
9683        prop.begin_update().expect("Failed to begin update 1");
9684        prop.set(10).expect("Failed to set");
9685        prop.set(20).expect("Failed to set");
9686        prop.end_update().expect("Failed to end update 1");
9687
9688        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
9689
9690        // Second batch
9691        prop.begin_update().expect("Failed to begin update 2");
9692        prop.set(30).expect("Failed to set");
9693        prop.set(40).expect("Failed to set");
9694        prop.end_update().expect("Failed to end update 2");
9695
9696        assert_eq!(notification_count.load(Ordering::SeqCst), 2);
9697
9698        // Regular set (not batched)
9699        prop.set(50).expect("Failed to set");
9700
9701        // Should trigger immediate notification
9702        assert_eq!(notification_count.load(Ordering::SeqCst), 3);
9703    }
9704
9705    #[test]
9706    fn test_change_coalescing_with_async() {
9707        let prop = ObservableProperty::new(0);
9708        let notification_count = Arc::new(AtomicUsize::new(0));
9709        let count_clone = notification_count.clone();
9710
9711        prop.subscribe(Arc::new(move |_, _| {
9712            count_clone.fetch_add(1, Ordering::SeqCst);
9713        }))
9714        .expect("Failed to subscribe");
9715
9716        // Batched update suppresses both sync and async notifications
9717        prop.begin_update().expect("Failed to begin update");
9718        prop.set(10).expect("Failed to set");
9719        prop.set_async(20).expect("Failed to set async");
9720        prop.set(30).expect("Failed to set");
9721
9722        // No notifications yet
9723        assert_eq!(notification_count.load(Ordering::SeqCst), 0);
9724
9725        prop.end_update().expect("Failed to end update");
9726
9727        // Should have one notification
9728        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
9729    }
9730
9731    #[test]
9732    fn test_change_coalescing_thread_safety() {
9733        use std::thread;
9734
9735        let prop = Arc::new(ObservableProperty::new(0));
9736        let notification_count = Arc::new(AtomicUsize::new(0));
9737        let count_clone = notification_count.clone();
9738
9739        prop.subscribe(Arc::new(move |_, _| {
9740            count_clone.fetch_add(1, Ordering::SeqCst);
9741        }))
9742        .expect("Failed to subscribe");
9743
9744        let prop_clone = prop.clone();
9745        let handle = thread::spawn(move || {
9746            prop_clone.begin_update().expect("Failed to begin update");
9747            prop_clone.set(10).expect("Failed to set");
9748            prop_clone.set(20).expect("Failed to set");
9749            prop_clone.end_update().expect("Failed to end update");
9750        });
9751
9752        handle.join().expect("Thread panicked");
9753
9754        // Should have one notification
9755        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
9756        assert_eq!(prop.get().unwrap(), 20);
9757    }
9758
9759    // ========================================================================
9760    // Validation Tests
9761    // ========================================================================
9762
9763    #[test]
9764    fn test_with_validator_basic() {
9765        let result = ObservableProperty::with_validator(25, |age| {
9766            if *age <= 150 {
9767                Ok(())
9768            } else {
9769                Err(format!("Age must be at most 150, got {}", age))
9770            }
9771        });
9772
9773        assert!(result.is_ok());
9774        let prop = result.unwrap();
9775        assert_eq!(prop.get().unwrap(), 25);
9776
9777        // Valid update
9778        assert!(prop.set(30).is_ok());
9779        assert_eq!(prop.get().unwrap(), 30);
9780
9781        // Invalid update
9782        let invalid_result = prop.set(200);
9783        assert!(invalid_result.is_err());
9784        assert_eq!(prop.get().unwrap(), 30); // Value unchanged
9785    }
9786
9787    #[test]
9788    fn test_with_validator_rejects_invalid_initial_value() {
9789        let result = ObservableProperty::with_validator(200, |age| {
9790            if *age <= 150 {
9791                Ok(())
9792            } else {
9793                Err(format!("Age must be at most 150, got {}", age))
9794            }
9795        });
9796
9797        assert!(result.is_err());
9798        match result {
9799            Err(PropertyError::ValidationError { reason }) => {
9800                assert!(reason.contains("200"));
9801            }
9802            _ => panic!("Expected ValidationError"),
9803        }
9804    }
9805
9806    #[test]
9807    fn test_with_validator_string_validation() {
9808        let result = ObservableProperty::with_validator("alice".to_string(), |name| {
9809            if name.is_empty() {
9810                return Err("Username cannot be empty".to_string());
9811            }
9812            if name.len() < 3 {
9813                return Err(format!("Username must be at least 3 characters, got {}", name.len()));
9814            }
9815            if !name.chars().all(|c| c.is_alphanumeric() || c == '_') {
9816                return Err("Username can only contain letters, numbers, and underscores".to_string());
9817            }
9818            Ok(())
9819        });
9820
9821        assert!(result.is_ok());
9822        let prop = result.unwrap();
9823
9824        assert!(prop.set("bob".to_string()).is_ok());
9825        assert!(prop.set("ab".to_string()).is_err()); // Too short
9826        assert!(prop.set("user@123".to_string()).is_err()); // Invalid chars
9827        assert_eq!(prop.get().unwrap(), "bob");
9828    }
9829
9830    #[test]
9831    fn test_with_validator_with_observers() {
9832        let prop = ObservableProperty::with_validator(10, |val| {
9833            if *val >= 0 && *val <= 100 {
9834                Ok(())
9835            } else {
9836                Err(format!("Value must be between 0 and 100, got {}", val))
9837            }
9838        }).unwrap();
9839
9840        let notification_count = Arc::new(AtomicUsize::new(0));
9841        let count_clone = notification_count.clone();
9842
9843        prop.subscribe(Arc::new(move |_, _| {
9844            count_clone.fetch_add(1, Ordering::SeqCst);
9845        })).unwrap();
9846
9847        // Valid update - should notify
9848        prop.set(50).unwrap();
9849        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
9850
9851        // Invalid update - should not notify
9852        prop.set(150).unwrap_err();
9853        assert_eq!(notification_count.load(Ordering::SeqCst), 1); // No additional notification
9854    }
9855
9856    // ========================================================================
9857    // Custom Equality Tests
9858    // ========================================================================
9859
9860    #[test]
9861    fn test_with_equality_basic() {
9862        // Create property where values within 5 are considered equal
9863        let prop = ObservableProperty::with_equality(10i32, |a, b| (a - b).abs() <= 5);
9864        let notification_count = Arc::new(AtomicUsize::new(0));
9865        let count_clone = notification_count.clone();
9866
9867        prop.subscribe(Arc::new(move |_, _| {
9868            count_clone.fetch_add(1, Ordering::SeqCst);
9869        })).unwrap();
9870
9871        // Small change - should not notify
9872        prop.set(12).unwrap();
9873        assert_eq!(notification_count.load(Ordering::SeqCst), 0);
9874
9875        // Large change - should notify
9876        prop.set(20).unwrap();
9877        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
9878    }
9879
9880    #[test]
9881    fn test_with_equality_string_case_insensitive() {
9882        let prop = ObservableProperty::with_equality("Hello".to_string(), |a, b| {
9883            a.to_lowercase() == b.to_lowercase()
9884        });
9885
9886        let notification_count = Arc::new(AtomicUsize::new(0));
9887        let count_clone = notification_count.clone();
9888
9889        prop.subscribe(Arc::new(move |_, _| {
9890            count_clone.fetch_add(1, Ordering::SeqCst);
9891        })).unwrap();
9892
9893        // Same case-insensitive - should not notify
9894        prop.set("hello".to_string()).unwrap();
9895        assert_eq!(notification_count.load(Ordering::SeqCst), 0);
9896
9897        // Different case-insensitive - should notify
9898        prop.set("World".to_string()).unwrap();
9899        assert_eq!(notification_count.load(Ordering::SeqCst), 1);
9900    }
9901
9902    // ========================================================================
9903    // History Tests
9904    // ========================================================================
9905
9906    #[test]
9907    fn test_with_history_basic() {
9908        let prop = ObservableProperty::with_history(0, 5);
9909
9910        prop.set(10).unwrap();
9911        prop.set(20).unwrap();
9912        prop.set(30).unwrap();
9913
9914        assert_eq!(prop.get().unwrap(), 30);
9915
9916        // Undo
9917        prop.undo().unwrap();
9918        assert_eq!(prop.get().unwrap(), 20);
9919
9920        prop.undo().unwrap();
9921        assert_eq!(prop.get().unwrap(), 10);
9922
9923        prop.undo().unwrap();
9924        assert_eq!(prop.get().unwrap(), 0);
9925    }
9926
9927    #[test]
9928    fn test_with_history_get_history() {
9929        let prop = ObservableProperty::with_history("start".to_string(), 3);
9930
9931        prop.set("second".to_string()).unwrap();
9932        prop.set("third".to_string()).unwrap();
9933        prop.set("fourth".to_string()).unwrap();
9934
9935        let history = prop.get_history();
9936        assert_eq!(history.len(), 3);
9937        assert_eq!(history[0], "start");
9938        assert_eq!(history[1], "second");
9939        assert_eq!(history[2], "third");
9940        assert_eq!(prop.get().unwrap(), "fourth");
9941    }
9942
9943    #[test]
9944    fn test_with_history_bounded_buffer() {
9945        let prop = ObservableProperty::with_history(1, 2);
9946
9947        prop.set(2).unwrap();
9948        prop.set(3).unwrap();
9949        prop.set(4).unwrap();
9950
9951        let history = prop.get_history();
9952        assert_eq!(history.len(), 2);
9953        assert_eq!(history[0], 2);
9954        assert_eq!(history[1], 3);
9955        assert_eq!(prop.get().unwrap(), 4);
9956    }
9957
9958    #[test]
9959    fn test_undo_no_history() {
9960        let prop = ObservableProperty::new(42);
9961        let result = prop.undo();
9962        assert!(result.is_err());
9963    }
9964
9965    #[test]
9966    fn test_undo_empty_history() {
9967        let prop = ObservableProperty::with_history(42, 5);
9968        let result = prop.undo();
9969        assert!(result.is_err());
9970    }
9971
9972    #[test]
9973    fn test_history_with_observers() {
9974        let prop = ObservableProperty::with_history(0, 5);
9975        let notifications = Arc::new(RwLock::new(Vec::new()));
9976        let notifs_clone = notifications.clone();
9977
9978        prop.subscribe(Arc::new(move |old, new| {
9979            if let Ok(mut notifs) = notifs_clone.write() {
9980                notifs.push((*old, *new));
9981            }
9982        })).unwrap();
9983
9984        prop.set(10).unwrap();
9985        prop.set(20).unwrap();
9986        prop.undo().unwrap(); // Should notify: 20 -> 10
9987
9988        let notifs = notifications.read().unwrap();
9989        assert_eq!(notifs.len(), 3);
9990        assert_eq!(notifs[0], (0, 10));
9991        assert_eq!(notifs[1], (10, 20));
9992        assert_eq!(notifs[2], (20, 10)); // Undo notification
9993    }
9994
9995    // ========================================================================
9996    // Event Logging Tests
9997    // ========================================================================
9998
9999    #[test]
10000    fn test_with_event_log_basic() {
10001        let counter = ObservableProperty::with_event_log(0, 0);
10002
10003        counter.set(1).unwrap();
10004        counter.set(2).unwrap();
10005        counter.set(3).unwrap();
10006
10007        let events = counter.get_event_log();
10008        assert_eq!(events.len(), 3);
10009
10010        assert_eq!(events[0].old_value, 0);
10011        assert_eq!(events[0].new_value, 1);
10012        assert_eq!(events[0].event_number, 0);
10013
10014        assert_eq!(events[2].old_value, 2);
10015        assert_eq!(events[2].new_value, 3);
10016        assert_eq!(events[2].event_number, 2);
10017    }
10018
10019    #[test]
10020    fn test_with_event_log_bounded() {
10021        let prop = ObservableProperty::with_event_log(100, 3);
10022
10023        prop.set(101).unwrap();
10024        prop.set(102).unwrap();
10025        prop.set(103).unwrap();
10026        prop.set(104).unwrap();
10027
10028        let events = prop.get_event_log();
10029        assert_eq!(events.len(), 3);
10030        assert_eq!(events[0].old_value, 101);
10031        assert_eq!(events[2].new_value, 104);
10032    }
10033
10034    #[test]
10035    fn test_event_log_timestamps() {
10036        let prop = ObservableProperty::with_event_log(0, 0);
10037
10038        let before = Instant::now();
10039        thread::sleep(Duration::from_millis(10));
10040        prop.set(1).unwrap();
10041        thread::sleep(Duration::from_millis(10));
10042        prop.set(2).unwrap();
10043        let after = Instant::now();
10044
10045        let events = prop.get_event_log();
10046        assert_eq!(events.len(), 2);
10047        assert!(events[0].timestamp >= before);
10048        assert!(events[1].timestamp <= after);
10049        assert!(events[1].timestamp >= events[0].timestamp);
10050    }
10051
10052    // ========================================================================
10053    // Property Transformation Tests (map)
10054    // ========================================================================
10055
10056    #[test]
10057    fn test_map_basic() {
10058        let celsius = ObservableProperty::new(20.0);
10059        let fahrenheit = celsius.map(|c| c * 9.0 / 5.0 + 32.0).unwrap();
10060
10061        assert_eq!(fahrenheit.get().unwrap(), 68.0);
10062
10063        celsius.set(25.0).unwrap();
10064        thread::sleep(Duration::from_millis(10)); // Give observer time to fire
10065        assert_eq!(fahrenheit.get().unwrap(), 77.0);
10066
10067        celsius.set(0.0).unwrap();
10068        thread::sleep(Duration::from_millis(10));
10069        assert_eq!(fahrenheit.get().unwrap(), 32.0);
10070    }
10071
10072    #[test]
10073    fn test_map_string_formatting() {
10074        let count = ObservableProperty::new(42);
10075        let message = count.map(|n| format!("Count: {}", n)).unwrap();
10076
10077        assert_eq!(message.get().unwrap(), "Count: 42");
10078
10079        count.set(100).unwrap();
10080        thread::sleep(Duration::from_millis(10));
10081        assert_eq!(message.get().unwrap(), "Count: 100");
10082    }
10083
10084    #[test]
10085    fn test_map_chaining() {
10086        let base = ObservableProperty::new(10);
10087        let doubled = base.map(|x| x * 2).unwrap();
10088        let squared = doubled.map(|x| x * x).unwrap();
10089
10090        assert_eq!(squared.get().unwrap(), 400); // (10 * 2)^2 = 400
10091
10092        base.set(5).unwrap();
10093        thread::sleep(Duration::from_millis(20));
10094        assert_eq!(squared.get().unwrap(), 100); // (5 * 2)^2 = 100
10095    }
10096
10097    #[test]
10098    fn test_map_type_conversion() {
10099        let integer = ObservableProperty::new(42);
10100        let float_value = integer.map(|i| *i as f64).unwrap();
10101        let is_even = integer.map(|i| i % 2 == 0).unwrap();
10102
10103        assert_eq!(float_value.get().unwrap(), 42.0);
10104        assert_eq!(is_even.get().unwrap(), true);
10105
10106        integer.set(43).unwrap();
10107        thread::sleep(Duration::from_millis(10));
10108        assert_eq!(is_even.get().unwrap(), false);
10109    }
10110
10111    // ========================================================================
10112    // Modify Tests
10113    // ========================================================================
10114
10115    #[test]
10116    fn test_modify_basic() {
10117        let counter = ObservableProperty::new(0);
10118        let notifications = Arc::new(RwLock::new(Vec::new()));
10119        let notifs_clone = notifications.clone();
10120
10121        counter.subscribe(Arc::new(move |old, new| {
10122            if let Ok(mut notifs) = notifs_clone.write() {
10123                notifs.push((*old, *new));
10124            }
10125        })).unwrap();
10126
10127        counter.modify(|value| *value += 1).unwrap();
10128        assert_eq!(counter.get().unwrap(), 1);
10129
10130        counter.modify(|value| *value *= 2).unwrap();
10131        assert_eq!(counter.get().unwrap(), 2);
10132
10133        let notifs = notifications.read().unwrap();
10134        assert_eq!(notifs.len(), 2);
10135        assert_eq!(notifs[0], (0, 1));
10136        assert_eq!(notifs[1], (1, 2));
10137    }
10138
10139    #[test]
10140    fn test_modify_with_validator() {
10141        let prop = ObservableProperty::with_validator(10, |val| {
10142            if *val >= 0 && *val <= 100 {
10143                Ok(())
10144            } else {
10145                Err("Value must be between 0 and 100".to_string())
10146            }
10147        }).unwrap();
10148
10149        // Valid modification
10150        assert!(prop.modify(|v| *v += 5).is_ok());
10151        assert_eq!(prop.get().unwrap(), 15);
10152
10153        // Invalid modification
10154        let result = prop.modify(|v| *v += 100);
10155        assert!(result.is_err());
10156        assert_eq!(prop.get().unwrap(), 15); // Value unchanged
10157    }
10158
10159    #[test]
10160    fn test_modify_string() {
10161        let text = ObservableProperty::new("hello".to_string());
10162
10163        text.modify(|s| {
10164            *s = s.to_uppercase();
10165        }).unwrap();
10166
10167        assert_eq!(text.get().unwrap(), "HELLO");
10168
10169        text.modify(|s| {
10170            s.push_str(" WORLD");
10171        }).unwrap();
10172
10173        assert_eq!(text.get().unwrap(), "HELLO WORLD");
10174    }
10175
10176    // ========================================================================
10177    // Bidirectional Binding Tests
10178    // ========================================================================
10179
10180    #[test]
10181    fn test_bind_bidirectional_basic() {
10182        let prop1 = Arc::new(ObservableProperty::new(10));
10183        let prop2 = Arc::new(ObservableProperty::new(10)); // Start with same value
10184
10185        prop1.bind_bidirectional(&prop2).unwrap();
10186
10187        // Change prop1 - prop2 should update
10188        prop1.set(30).unwrap();
10189        thread::sleep(Duration::from_millis(20));
10190        assert_eq!(prop2.get().unwrap(), 30);
10191
10192        // Change prop2 - prop1 should update
10193        prop2.set(40).unwrap();
10194        thread::sleep(Duration::from_millis(20));
10195        assert_eq!(prop1.get().unwrap(), 40);
10196    }
10197
10198    #[test]
10199    fn test_bind_bidirectional_strings() {
10200        let prop1 = Arc::new(ObservableProperty::new("first".to_string()));
10201        let prop2 = Arc::new(ObservableProperty::new("first".to_string())); // Start with same value
10202
10203        prop1.bind_bidirectional(&prop2).unwrap();
10204
10205        // Change prop2
10206        prop2.set("updated".to_string()).unwrap();
10207        thread::sleep(Duration::from_millis(20));
10208        assert_eq!(prop1.get().unwrap(), "updated");
10209
10210        // Change prop1
10211        prop1.set("final".to_string()).unwrap();
10212        thread::sleep(Duration::from_millis(20));
10213        assert_eq!(prop2.get().unwrap(), "final");
10214    }
10215
10216    // ========================================================================
10217    // Comprehensive Metrics Tests
10218    // ========================================================================
10219
10220    #[test]
10221    fn test_get_metrics_basic() {
10222        let prop = ObservableProperty::new(0);
10223        
10224        prop.subscribe(Arc::new(|_, _| {
10225            thread::sleep(Duration::from_millis(5));
10226        })).unwrap();
10227
10228        prop.set(1).unwrap();
10229        prop.set(2).unwrap();
10230        prop.set(3).unwrap();
10231
10232        let metrics = prop.get_metrics().unwrap();
10233        assert_eq!(metrics.total_changes, 3);
10234        assert_eq!(metrics.observer_calls, 3);
10235        assert!(metrics.avg_notification_time.as_millis() >= 4);
10236    }
10237
10238    #[test]
10239    fn test_metrics_multiple_observers() {
10240        let prop = ObservableProperty::new(0);
10241        
10242        for _ in 0..3 {
10243            prop.subscribe(Arc::new(|_, _| {})).unwrap();
10244        }
10245
10246        prop.set(1).unwrap();
10247        prop.set(2).unwrap();
10248
10249        let metrics = prop.get_metrics().unwrap();
10250        assert_eq!(metrics.total_changes, 2);
10251        assert_eq!(metrics.observer_calls, 6); // 2 changes * 3 observers
10252    }
10253
10254    #[test]
10255    fn test_metrics_no_observers() {
10256        let prop = ObservableProperty::new(0);
10257
10258        prop.set(1).unwrap();
10259        prop.set(2).unwrap();
10260
10261        let metrics = prop.get_metrics().unwrap();
10262        assert_eq!(metrics.total_changes, 2);
10263        assert_eq!(metrics.observer_calls, 0);
10264    }
10265
10266    // ========================================================================
10267    // Debug Logging Tests (when debug feature is enabled)
10268    // ========================================================================
10269
10270    #[test]
10271    #[cfg(feature = "debug")]
10272    fn test_enable_disable_change_logging() {
10273        let prop = ObservableProperty::new(0);
10274
10275        prop.enable_change_logging();
10276        prop.set(1).unwrap();
10277        prop.set(2).unwrap();
10278        prop.disable_change_logging();
10279        prop.set(3).unwrap();
10280
10281        // With debug feature enabled, change logs should be recorded
10282        // This is a basic compile test to ensure the methods exist
10283    }
10284
10285    // ========================================================================
10286    // Async Features Tests
10287    // ========================================================================
10288
10289    // Note: The following async tests are disabled because the wait_for() implementation
10290    // needs to be refactored to use an async-compatible channel (tokio::sync::mpsc)
10291    // instead of std::sync::mpsc to properly integrate with async runtimes.
10292    // The current implementation returns Poll::Pending without registering a waker,
10293    // causing the tests to hang indefinitely.
10294    // TODO: Fix in version 0.5.0 by adding tokio as optional dependency for async feature
10295
10296    /*
10297    #[cfg(feature = "async")]
10298    #[tokio::test]
10299    async fn test_wait_for_basic() {
10300        let prop = Arc::new(ObservableProperty::new(0));
10301        let prop_clone = prop.clone();
10302
10303        // Spawn task to change value after delay
10304        tokio::spawn(async move {
10305            tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
10306            prop_clone.set(42).unwrap();
10307        });
10308
10309        // Wait for value to become 42
10310        prop.wait_for(|v| *v == 42).await;
10311        assert_eq!(prop.get().unwrap(), 42);
10312    }
10313
10314    #[cfg(feature = "async")]
10315    #[tokio::test]
10316    async fn test_wait_for_already_true() {
10317        let prop = ObservableProperty::new(100);
10318
10319        // Predicate is already true
10320        prop.wait_for(|v| *v >= 50).await;
10321        assert_eq!(prop.get().unwrap(), 100);
10322    }
10323    */
10324
10325    // ========================================================================
10326    // Observer Count Tests
10327    // ========================================================================
10328
10329    #[test]
10330    fn test_observer_count() {
10331        let prop = ObservableProperty::new(0);
10332        assert_eq!(prop.observer_count(), 0);
10333
10334        let _id1 = prop.subscribe(Arc::new(|_, _| {})).unwrap();
10335        assert_eq!(prop.observer_count(), 1);
10336
10337        let _id2 = prop.subscribe(Arc::new(|_, _| {})).unwrap();
10338        assert_eq!(prop.observer_count(), 2);
10339
10340        let _id3 = prop.subscribe(Arc::new(|_, _| {})).unwrap();
10341        assert_eq!(prop.observer_count(), 3);
10342    }
10343
10344    #[test]
10345    fn test_observer_count_after_unsubscribe() {
10346        let prop = ObservableProperty::new(0);
10347
10348        let id1 = prop.subscribe(Arc::new(|_, _| {})).unwrap();
10349        let id2 = prop.subscribe(Arc::new(|_, _| {})).unwrap();
10350        assert_eq!(prop.observer_count(), 2);
10351
10352        prop.unsubscribe(id1).unwrap();
10353        assert_eq!(prop.observer_count(), 1);
10354
10355        prop.unsubscribe(id2).unwrap();
10356        assert_eq!(prop.observer_count(), 0);
10357    }
10358
10359    // ========================================================================
10360    // With Config Tests
10361    // ========================================================================
10362
10363    #[test]
10364    fn test_with_config_custom_limits() {
10365        let prop = ObservableProperty::with_config(42, 2, 5);
10366
10367        // Should be able to add up to 5 observers
10368        for _ in 0..5 {
10369            assert!(prop.subscribe(Arc::new(|_, _| {})).is_ok());
10370        }
10371
10372        assert_eq!(prop.observer_count(), 5);
10373
10374        // 6th observer should fail due to limit
10375        let result = prop.subscribe(Arc::new(|_, _| {}));
10376        assert!(result.is_err());
10377    }
10378
10379    #[test]
10380    fn test_with_config_max_threads() {
10381        let prop = ObservableProperty::with_config(0, 1, 100);
10382        let call_count = Arc::new(AtomicUsize::new(0));
10383
10384        for _ in 0..4 {
10385            let count = call_count.clone();
10386            prop.subscribe(Arc::new(move |_, _| {
10387                thread::sleep(Duration::from_millis(25));
10388                count.fetch_add(1, Ordering::SeqCst);
10389            })).unwrap();
10390        }
10391
10392        // With max_threads = 1, async should still work but sequentially
10393        let start = Instant::now();
10394        prop.set_async(42).unwrap();
10395        let duration = start.elapsed();
10396
10397        // Should return quickly
10398        assert!(duration.as_millis() < 50);
10399
10400        // Wait for observers
10401        thread::sleep(Duration::from_millis(150));
10402        assert_eq!(call_count.load(Ordering::SeqCst), 4);
10403    }
10404
10405    // ========================================================================
10406    // Persistence Tests
10407    // ========================================================================
10408
10409    struct MockPersistence {
10410        data: Arc<RwLock<Option<i32>>>,
10411    }
10412
10413    impl PropertyPersistence for MockPersistence {
10414        type Value = i32;
10415
10416        fn load(&self) -> Result<Self::Value, Box<dyn std::error::Error + Send + Sync>> {
10417            self.data
10418                .read()
10419                .unwrap()
10420                .ok_or_else(|| "No data".into())
10421        }
10422
10423        fn save(&self, value: &Self::Value) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
10424            *self.data.write().unwrap() = Some(*value);
10425            Ok(())
10426        }
10427    }
10428
10429    #[test]
10430    fn test_with_persistence_load_success() {
10431        let storage = Arc::new(RwLock::new(Some(42)));
10432        let persistence = MockPersistence { data: storage.clone() };
10433
10434        let prop = ObservableProperty::with_persistence(0, persistence);
10435        
10436        // Should load 42 from persistence
10437        assert_eq!(prop.get().unwrap(), 42);
10438    }
10439
10440    #[test]
10441    fn test_with_persistence_auto_save() {
10442        let storage = Arc::new(RwLock::new(None));
10443        let persistence = MockPersistence { data: storage.clone() };
10444
10445        let prop = ObservableProperty::with_persistence(10, persistence);
10446        
10447        // Change value - should auto-save
10448        prop.set(20).unwrap();
10449        thread::sleep(Duration::from_millis(10));
10450
10451        // Check storage
10452        assert_eq!(*storage.read().unwrap(), Some(20));
10453
10454        prop.set(30).unwrap();
10455        thread::sleep(Duration::from_millis(10));
10456        assert_eq!(*storage.read().unwrap(), Some(30));
10457    }
10458
10459    #[test]
10460    fn test_with_persistence_load_failure_uses_default() {
10461        let storage = Arc::new(RwLock::new(None));
10462        let persistence = MockPersistence { data: storage };
10463
10464        // Load will fail, should use initial_value
10465        let prop = ObservableProperty::with_persistence(99, persistence);
10466        assert_eq!(prop.get().unwrap(), 99);
10467    }
10468
10469    // ========================================================================
10470    // Computed Properties Tests (additional coverage)
10471    // ========================================================================
10472
10473    #[test]
10474    fn test_computed_updates_immediately() {
10475        let a = Arc::new(ObservableProperty::new(5));
10476        let b = Arc::new(ObservableProperty::new(10));
10477
10478        let sum = computed(
10479            vec![a.clone(), b.clone()],
10480            |values| values[0] + values[1]
10481        ).unwrap();
10482
10483        assert_eq!(sum.get().unwrap(), 15);
10484
10485        a.set(7).unwrap();
10486        thread::sleep(Duration::from_millis(10));
10487        assert_eq!(sum.get().unwrap(), 17);
10488    }
10489
10490    #[test]
10491    fn test_computed_with_string() {
10492        let first = Arc::new(ObservableProperty::new("Hello".to_string()));
10493        let last = Arc::new(ObservableProperty::new("World".to_string()));
10494
10495        let full = computed(
10496            vec![first.clone(), last.clone()],
10497            |values| format!("{} {}", values[0], values[1])
10498        ).unwrap();
10499
10500        assert_eq!(full.get().unwrap(), "Hello World");
10501
10502        first.set("Goodbye".to_string()).unwrap();
10503        thread::sleep(Duration::from_millis(10));
10504        assert_eq!(full.get().unwrap(), "Goodbye World");
10505    }
10506}
10507