scirs2_metrics/visualization/advanced_interactive/
collaboration.rs

1//! Collaboration features for interactive visualization
2//!
3//! This module provides multi-user collaboration capabilities for
4//! shared dashboard editing and real-time synchronization.
5
6#![allow(clippy::too_many_arguments)]
7#![allow(dead_code)]
8
9use super::core::{CollaborationConfig, PermissionLevel};
10use crate::error::{MetricsError, Result};
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use std::collections::HashMap;
14use std::time::{Duration, Instant};
15
16/// Collaboration manager for multi-user dashboards
17#[derive(Debug)]
18pub struct CollaborationManager {
19    /// Configuration
20    config: CollaborationConfig,
21    /// Active sessions
22    sessions: HashMap<String, UserSession>,
23    /// Shared state
24    shared_state: SharedState,
25    /// Operation history
26    operation_history: Vec<Operation>,
27    /// Conflict resolver
28    conflict_resolver: ConflictResolver,
29}
30
31/// User session information
32#[derive(Debug, Clone)]
33pub struct UserSession {
34    /// Session ID
35    pub session_id: String,
36    /// User ID
37    pub user_id: String,
38    /// User name
39    pub user_name: String,
40    /// Permission level
41    pub permissions: PermissionLevel,
42    /// Last activity timestamp
43    pub last_activity: Instant,
44    /// Current cursor position
45    pub cursor_position: Option<CursorPosition>,
46    /// Active selections
47    pub selections: Vec<Selection>,
48}
49
50/// Cursor position in the dashboard
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct CursorPosition {
53    /// X coordinate
54    pub x: f64,
55    /// Y coordinate
56    pub y: f64,
57    /// Widget ID (if hovering over widget)
58    pub widget_id: Option<String>,
59}
60
61/// Selection in the dashboard
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct Selection {
64    /// Selection ID
65    pub id: String,
66    /// Selected widget IDs
67    pub widget_ids: Vec<String>,
68    /// Selection bounds
69    pub bounds: SelectionBounds,
70    /// Selection type
71    pub selection_type: SelectionType,
72}
73
74/// Selection bounds
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct SelectionBounds {
77    /// Top-left X coordinate
78    pub x: f64,
79    /// Top-left Y coordinate
80    pub y: f64,
81    /// Width
82    pub width: f64,
83    /// Height
84    pub height: f64,
85}
86
87/// Selection type enumeration
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub enum SelectionType {
90    /// Single widget selection
91    Single,
92    /// Multiple widget selection
93    Multiple,
94    /// Area selection
95    Area,
96    /// Text selection
97    Text,
98}
99
100/// Shared state for collaborative editing
101#[derive(Debug, Clone)]
102pub struct SharedState {
103    /// Dashboard state
104    pub dashboard_state: Value,
105    /// Widget states
106    pub widget_states: HashMap<String, Value>,
107    /// Global settings
108    pub global_settings: HashMap<String, Value>,
109    /// Version vector for consistency
110    pub version_vector: HashMap<String, u64>,
111}
112
113/// Collaborative operation
114#[derive(Debug, Clone)]
115pub struct Operation {
116    /// Operation ID
117    pub id: String,
118    /// Operation type
119    pub operation_type: OperationType,
120    /// User ID who performed the operation
121    pub user_id: String,
122    /// Timestamp
123    pub timestamp: Instant,
124    /// Operation data
125    pub data: Value,
126    /// Target (widget ID, etc.)
127    pub target: String,
128    /// Dependencies (operation IDs)
129    pub dependencies: Vec<String>,
130}
131
132/// Operation type enumeration
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub enum OperationType {
135    /// Create widget
136    Create,
137    /// Update widget
138    Update,
139    /// Delete widget
140    Delete,
141    /// Move widget
142    Move,
143    /// Resize widget
144    Resize,
145    /// Change style
146    StyleChange,
147    /// Data update
148    DataUpdate,
149    /// Custom operation
150    Custom(String),
151}
152
153/// Conflict resolver for handling concurrent operations
154#[derive(Debug)]
155pub struct ConflictResolver {
156    /// Resolution strategy
157    strategy: ConflictResolutionStrategy,
158    /// Pending conflicts
159    pending_conflicts: Vec<Conflict>,
160}
161
162/// Conflict resolution strategy
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub enum ConflictResolutionStrategy {
165    /// Last writer wins
166    LastWriterWins,
167    /// First writer wins
168    FirstWriterWins,
169    /// Operational transform
170    OperationalTransform,
171    /// Manual resolution
172    Manual,
173    /// Custom strategy
174    Custom(String),
175}
176
177/// Conflict between operations
178#[derive(Debug, Clone)]
179pub struct Conflict {
180    /// Conflict ID
181    pub id: String,
182    /// Conflicting operations
183    pub operations: Vec<Operation>,
184    /// Conflict type
185    pub conflict_type: ConflictType,
186    /// Resolution status
187    pub status: ConflictStatus,
188    /// Proposed resolution
189    pub proposed_resolution: Option<Operation>,
190}
191
192/// Conflict type enumeration
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub enum ConflictType {
195    /// Concurrent modifications
196    ConcurrentModification,
197    /// Version mismatch
198    VersionMismatch,
199    /// Permission conflict
200    PermissionConflict,
201    /// Data integrity conflict
202    DataIntegrity,
203    /// Custom conflict
204    Custom(String),
205}
206
207/// Conflict status enumeration
208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
209pub enum ConflictStatus {
210    /// Pending resolution
211    Pending,
212    /// Automatically resolved
213    AutoResolved,
214    /// Manually resolved
215    ManuallyResolved,
216    /// Ignored
217    Ignored,
218}
219
220impl CollaborationManager {
221    /// Create new collaboration manager
222    pub fn new(config: CollaborationConfig) -> Self {
223        Self {
224            config,
225            sessions: HashMap::new(),
226            shared_state: SharedState::new(),
227            operation_history: Vec::new(),
228            conflict_resolver: ConflictResolver::new(),
229        }
230    }
231
232    /// Add user session
233    pub fn add_session(&mut self, session: UserSession) -> Result<()> {
234        if self.sessions.len() >= self.config.max_collaborators as usize {
235            return Err(MetricsError::ComputationError(
236                "Maximum number of collaborators reached".to_string(),
237            ));
238        }
239
240        self.sessions.insert(session.session_id.clone(), session);
241        Ok(())
242    }
243
244    /// Remove user session
245    pub fn remove_session(&mut self, session_id: &str) -> Result<()> {
246        self.sessions.remove(session_id);
247        Ok(())
248    }
249
250    /// Apply operation
251    pub fn apply_operation(&mut self, operation: Operation) -> Result<()> {
252        // Check permissions
253        if let Some(session) = self.sessions.get(&operation.user_id) {
254            if !self.has_permission(&session.permissions, &operation.operation_type) {
255                return Err(MetricsError::InvalidInput(
256                    "Insufficient permissions".to_string(),
257                ));
258            }
259        }
260
261        // Check for conflicts
262        if let Some(conflict) = self
263            .conflict_resolver
264            .detect_conflict(&operation, &self.operation_history)
265        {
266            self.conflict_resolver.pending_conflicts.push(conflict);
267            return Ok(()); // Don't apply yet, needs resolution
268        }
269
270        // Apply operation to shared state
271        self.apply_operation_to_state(&operation)?;
272
273        // Add to history
274        self.operation_history.push(operation);
275
276        Ok(())
277    }
278
279    /// Apply operation to shared state
280    fn apply_operation_to_state(&mut self, operation: &Operation) -> Result<()> {
281        match operation.operation_type {
282            OperationType::Create => {
283                self.shared_state
284                    .widget_states
285                    .insert(operation.target.clone(), operation.data.clone());
286            }
287            OperationType::Update => {
288                if let Some(widget_state) =
289                    self.shared_state.widget_states.get_mut(&operation.target)
290                {
291                    // Merge update data
292                    if let (Value::Object(current), Value::Object(update)) =
293                        (widget_state, &operation.data)
294                    {
295                        for (key, value) in update {
296                            current.insert(key.clone(), value.clone());
297                        }
298                    }
299                }
300            }
301            OperationType::Delete => {
302                self.shared_state.widget_states.remove(&operation.target);
303            }
304            _ => {
305                // Handle other operation types
306            }
307        }
308
309        // Update version vector
310        self.shared_state
311            .version_vector
312            .entry(operation.user_id.clone())
313            .and_modify(|v| *v += 1)
314            .or_insert(1);
315
316        Ok(())
317    }
318
319    /// Check if user has permission for operation
320    fn has_permission(&self, permission: &PermissionLevel, operation: &OperationType) -> bool {
321        match (permission, operation) {
322            (PermissionLevel::Admin, _) => true,
323            (
324                PermissionLevel::Edit,
325                OperationType::Create | OperationType::Update | OperationType::Delete,
326            ) => true,
327            (PermissionLevel::Comment, _) => false, // Comments not implemented in this example
328            (PermissionLevel::ReadOnly, _) => false,
329            _ => false,
330        }
331    }
332
333    /// Get active sessions
334    pub fn get_active_sessions(&self) -> Vec<&UserSession> {
335        self.sessions.values().collect()
336    }
337
338    /// Update user cursor position
339    pub fn update_cursor(&mut self, session_id: &str, position: CursorPosition) -> Result<()> {
340        if let Some(session) = self.sessions.get_mut(session_id) {
341            session.cursor_position = Some(position);
342            session.last_activity = Instant::now();
343        }
344        Ok(())
345    }
346
347    /// Get shared state
348    pub fn get_shared_state(&self) -> &SharedState {
349        &self.shared_state
350    }
351
352    /// Resolve conflicts using the conflict resolver
353    pub fn resolve_conflicts(&mut self) -> Result<Vec<Operation>> {
354        self.conflict_resolver.resolve_conflicts()
355    }
356}
357
358impl SharedState {
359    /// Create new shared state
360    pub fn new() -> Self {
361        Self {
362            dashboard_state: Value::Object(serde_json::Map::new()),
363            widget_states: HashMap::new(),
364            global_settings: HashMap::new(),
365            version_vector: HashMap::new(),
366        }
367    }
368}
369
370impl ConflictResolver {
371    /// Create new conflict resolver
372    pub fn new() -> Self {
373        Self {
374            strategy: ConflictResolutionStrategy::LastWriterWins,
375            pending_conflicts: Vec::new(),
376        }
377    }
378
379    /// Detect conflict between operation and history
380    pub fn detect_conflict(
381        &self,
382        operation: &Operation,
383        history: &[Operation],
384    ) -> Option<Conflict> {
385        // Simplified conflict detection
386        for existing_op in history.iter().rev().take(10) {
387            if existing_op.target == operation.target
388                && existing_op.timestamp
389                    > operation
390                        .timestamp
391                        .checked_sub(Duration::from_secs(5))
392                        .unwrap_or(operation.timestamp)
393            {
394                return Some(Conflict {
395                    id: format!("conflict_{}", scirs2_core::random::random::<u64>()),
396                    operations: vec![existing_op.clone(), operation.clone()],
397                    conflict_type: ConflictType::ConcurrentModification,
398                    status: ConflictStatus::Pending,
399                    proposed_resolution: None,
400                });
401            }
402        }
403        None
404    }
405
406    /// Resolve pending conflicts
407    pub fn resolve_conflicts(&mut self) -> Result<Vec<Operation>> {
408        let mut resolved_operations = Vec::new();
409
410        for conflict in &mut self.pending_conflicts {
411            match self.strategy {
412                ConflictResolutionStrategy::LastWriterWins => {
413                    if let Some(latest_op) =
414                        conflict.operations.iter().max_by_key(|op| op.timestamp)
415                    {
416                        resolved_operations.push(latest_op.clone());
417                        conflict.status = ConflictStatus::AutoResolved;
418                    }
419                }
420                ConflictResolutionStrategy::FirstWriterWins => {
421                    if let Some(earliest_op) =
422                        conflict.operations.iter().min_by_key(|op| op.timestamp)
423                    {
424                        resolved_operations.push(earliest_op.clone());
425                        conflict.status = ConflictStatus::AutoResolved;
426                    }
427                }
428                _ => {
429                    // Other strategies would be implemented here
430                }
431            }
432        }
433
434        // Remove resolved conflicts
435        self.pending_conflicts
436            .retain(|conflict| conflict.status == ConflictStatus::Pending);
437
438        Ok(resolved_operations)
439    }
440}
441
442impl Default for SharedState {
443    fn default() -> Self {
444        Self::new()
445    }
446}