rvoip_client_core/client/
controls.rs

1//! Call control operations for the client-core library
2//! 
3//! This module provides comprehensive call control functionality for managing active VoIP calls.
4//! It includes operations for call hold/resume, DTMF transmission, call transfer (both blind
5//! and attended), and capability management.
6//!
7//! # Call Control Features
8//!
9//! ## Hold and Resume Operations
10//! - **Hold Call**: Put a call on hold (mute audio, send hold indication)
11//! - **Resume Call**: Resume a call from hold state
12//! - **Hold Status**: Check if a call is currently on hold
13//!
14//! ## DTMF (Dual-Tone Multi-Frequency) Support
15//! - **Send DTMF**: Transmit dial tones during active calls
16//! - **Validation**: Ensure DTMF digits are valid (0-9, A-D, *, #)
17//! - **History Tracking**: Maintain DTMF transmission history
18//!
19//! ## Call Transfer Operations
20//! - **Blind Transfer**: Transfer call directly to destination without consultation
21//! - **Attended Transfer**: Consultation-based transfer with hold and release
22//! - **URI Validation**: Ensure transfer targets are valid SIP or TEL URIs
23//!
24//! ## Capability Management
25//! - **Dynamic Capabilities**: Determine available operations based on call state
26//! - **State-Aware**: Operations adapt to current call conditions
27//! - **Permission Checking**: Validate operations before execution
28//!
29//! # Architecture
30//!
31//! ```text
32//! ┌─────────────────────────┐
33//! │   Application Layer     │
34//! └───────────┬─────────────┘
35//!             │
36//! ┌───────────▼─────────────┐
37//! │   Call Controls         │ ◄── This Module
38//! │ ┌─────────────────────┐ │
39//! │ │ Hold/Resume        │ │  • State management
40//! │ │ DTMF Transmission  │ │  • Session coordination
41//! │ │ Call Transfer      │ │  • Event notification
42//! │ │ Capabilities       │ │  • Error handling
43//! │ └─────────────────────┘ │
44//! └───────────┬─────────────┘
45//!             │
46//! ┌───────────▼─────────────┐
47//! │    session-core         │
48//! │  SessionControl API     │
49//! └─────────────────────────┘
50//! ```
51//!
52//! # Usage Examples
53//!
54//! ## Basic Call Hold and Resume
55//!
56//! ```rust
57//! use rvoip_client_core::{ClientManager, ClientConfig, CallId};
58//! 
59//! async fn hold_resume_example() -> Result<(), Box<dyn std::error::Error>> {
60//!     let config = ClientConfig::new()
61//!         .with_sip_addr("127.0.0.1:5060".parse()?);
62//!     let client = ClientManager::new(config).await?;
63//!     client.start().await?;
64//!     
65//!     // Assume we have an active call
66//!     let call_id = CallId::new_v4();
67//!     
68//!     // Check capabilities first
69//!     if let Ok(caps) = client.get_call_capabilities(&call_id).await {
70//!         if caps.can_hold {
71//!             // Put call on hold
72//!             if let Err(e) = client.hold_call(&call_id).await {
73//!                 println!("Hold failed: {}", e);
74//!             }
75//!             
76//!             // Check hold status
77//!             if let Ok(on_hold) = client.is_call_on_hold(&call_id).await {
78//!                 println!("Call on hold: {}", on_hold);
79//!             }
80//!             
81//!             // Resume call
82//!             if let Err(e) = client.resume_call(&call_id).await {
83//!                 println!("Resume failed: {}", e);
84//!             }
85//!         }
86//!     }
87//!     
88//!     Ok(())
89//! }
90//! ```
91//!
92//! ## DTMF Transmission
93//!
94//! ```rust
95//! use rvoip_client_core::{ClientManager, ClientConfig, CallId};
96//! 
97//! async fn dtmf_example() -> Result<(), Box<dyn std::error::Error>> {
98//!     let config = ClientConfig::new()
99//!         .with_sip_addr("127.0.0.1:5061".parse()?);
100//!     let client = ClientManager::new(config).await?;
101//!     client.start().await?;
102//!     
103//!     let call_id = CallId::new_v4();
104//!     
105//!     // Send individual DTMF digits
106//!     if let Err(e) = client.send_dtmf(&call_id, "1").await {
107//!         println!("DTMF failed: {}", e);
108//!     }
109//!     
110//!     // Send multiple digits
111//!     if let Err(e) = client.send_dtmf(&call_id, "123*456#").await {
112//!         println!("DTMF sequence failed: {}", e);
113//!     }
114//!     
115//!     // Send extended DTMF (including A-D)
116//!     if let Err(e) = client.send_dtmf(&call_id, "123A456B").await {
117//!         println!("Extended DTMF failed: {}", e);
118//!     }
119//!     
120//!     Ok(())
121//! }
122//! ```
123//!
124//! ## Call Transfer Operations
125//!
126//! ```rust
127//! use rvoip_client_core::{ClientManager, ClientConfig, CallId};
128//! 
129//! async fn transfer_example() -> Result<(), Box<dyn std::error::Error>> {
130//!     let config = ClientConfig::new()
131//!         .with_sip_addr("127.0.0.1:5062".parse()?);
132//!     let client = ClientManager::new(config).await?;
133//!     client.start().await?;
134//!     
135//!     let call_id1 = CallId::new_v4();
136//!     let call_id2 = CallId::new_v4();
137//!     
138//!     // Blind transfer to SIP URI
139//!     if let Err(e) = client.transfer_call(&call_id1, "sip:transfer@example.com").await {
140//!         println!("Blind transfer failed: {}", e);
141//!     }
142//!     
143//!     // Attended transfer between two calls
144//!     if let Err(e) = client.attended_transfer(&call_id1, &call_id2).await {
145//!         println!("Attended transfer failed: {}", e);
146//!     }
147//!     
148//!     Ok(())
149//! }
150//! ```
151
152use chrono::Utc;
153
154// Import session-core APIs
155use rvoip_session_core::api::{
156    SessionControl,
157};
158
159// Import client-core types
160use crate::{
161    ClientResult, ClientError,
162    call::CallId,
163};
164
165use crate::client::types::*;
166
167/// Call control operations implementation for ClientManager
168impl super::manager::ClientManager {
169    // ===== PRIORITY 3.2: CALL CONTROL OPERATIONS =====
170    
171    /// Put an active call on hold
172    /// 
173    /// This method places a call in hold state, which typically mutes the audio stream
174    /// and may play hold music to the remote party. The call remains connected but
175    /// media transmission is suspended until the call is resumed.
176    /// 
177    /// # Arguments
178    /// 
179    /// * `call_id` - The unique identifier of the call to put on hold
180    /// 
181    /// # Returns
182    /// 
183    /// Returns `Ok(())` if the call was successfully placed on hold.
184    /// 
185    /// # Errors
186    /// 
187    /// * `ClientError::CallNotFound` - If no call exists with the given ID
188    /// * `ClientError::InvalidCallState` - If the call is not in a holdable state
189    /// * `ClientError::CallSetupFailed` - If the hold operation fails
190    /// 
191    /// # State Requirements
192    /// 
193    /// The call must be in the `Connected` state to be placed on hold. Calls in
194    /// other states (such as `Ringing`, `Terminated`, etc.) cannot be held.
195    /// 
196    /// # Examples
197    /// 
198    /// ## Basic Hold Operation
199    /// 
200    /// ```rust
201    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
202    /// 
203    /// async fn hold_active_call() -> Result<(), Box<dyn std::error::Error>> {
204    ///     let config = ClientConfig::new()
205    ///         .with_sip_addr("127.0.0.1:5060".parse()?);
206    ///     let client = ClientManager::new(config).await?;
207    ///     client.start().await?;
208    ///     
209    ///     let call_id = CallId::new_v4();
210    ///     
211    ///     // Put call on hold
212    ///     match client.hold_call(&call_id).await {
213    ///         Ok(()) => {
214    ///             println!("Call {} successfully placed on hold", call_id);
215    ///             
216    ///             // Verify hold status
217    ///             if let Ok(on_hold) = client.is_call_on_hold(&call_id).await {
218    ///                 assert!(on_hold);
219    ///             }
220    ///         }
221    ///         Err(e) => {
222    ///             eprintln!("Failed to hold call: {}", e);
223    ///         }
224    ///     }
225    ///     
226    ///     Ok(())
227    /// }
228    /// ```
229    /// 
230    /// ## Hold with Capability Check
231    /// 
232    /// ```rust
233    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
234    /// 
235    /// async fn safe_hold_call() -> Result<(), Box<dyn std::error::Error>> {
236    ///     let config = ClientConfig::new()
237    ///         .with_sip_addr("127.0.0.1:5061".parse()?);
238    ///     let client = ClientManager::new(config).await?;
239    ///     client.start().await?;
240    ///     
241    ///     let call_id = CallId::new_v4();
242    ///     
243    ///     // Check if call can be held before attempting
244    ///     if let Ok(capabilities) = client.get_call_capabilities(&call_id).await {
245    ///         if capabilities.can_hold {
246    ///             client.hold_call(&call_id).await?;
247    ///             println!("Call successfully held");
248    ///         } else {
249    ///             println!("Call cannot be held in current state");
250    ///         }
251    ///     }
252    ///     
253    ///     Ok(())
254    /// }
255    /// ```
256    /// 
257    /// ## Error Handling
258    /// 
259    /// ```rust
260    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId, ClientError};
261    /// 
262    /// async fn hold_with_error_handling() -> Result<(), Box<dyn std::error::Error>> {
263    ///     let config = ClientConfig::new()
264    ///         .with_sip_addr("127.0.0.1:5062".parse()?);
265    ///     let client = ClientManager::new(config).await?;
266    ///     client.start().await?;
267    ///     
268    ///     let call_id = CallId::new_v4();
269    ///     
270    ///     match client.hold_call(&call_id).await {
271    ///         Ok(()) => {
272    ///             println!("✅ Call placed on hold successfully");
273    ///         }
274    ///         Err(ClientError::CallNotFound { .. }) => {
275    ///             println!("❌ Call not found - may have been terminated");
276    ///         }
277    ///         Err(ClientError::InvalidCallState { current_state, .. }) => {
278    ///             println!("❌ Cannot hold call in state: {:?}", current_state);
279    ///         }
280    ///         Err(e) => {
281    ///             println!("❌ Hold operation failed: {}", e);
282    ///         }
283    ///     }
284    ///     
285    ///     Ok(())
286    /// }
287    /// ```
288    /// 
289    /// # Side Effects
290    /// 
291    /// - Updates call metadata with hold status and timestamp
292    /// - Triggers media events for hold state change
293    /// - May play hold music to the remote party (depending on server configuration)
294    /// - Audio transmission is suspended for the local party
295    pub async fn hold_call(&self, call_id: &CallId) -> ClientResult<()> {
296        let session_id = self.session_mapping.get(call_id)
297            .ok_or(ClientError::CallNotFound { call_id: *call_id })?
298            .clone();
299            
300        // Validate call state
301        if let Some(call_info) = self.call_info.get(call_id) {
302            match call_info.state {
303                crate::call::CallState::Connected => {
304                    // OK to hold
305                }
306                crate::call::CallState::Terminated | 
307                crate::call::CallState::Failed | 
308                crate::call::CallState::Cancelled => {
309                    return Err(ClientError::InvalidCallState { 
310                        call_id: *call_id, 
311                        current_state: call_info.state.clone() 
312                    });
313                }
314                _ => {
315                    return Err(ClientError::InvalidCallStateGeneric { 
316                        expected: "Connected".to_string(),
317                        actual: format!("{:?}", call_info.state)
318                    });
319                }
320            }
321        }
322            
323        // Use session-core hold functionality
324        SessionControl::hold_session(&self.coordinator, &session_id)
325            .await
326            .map_err(|e| ClientError::CallSetupFailed { 
327                reason: format!("Failed to hold call: {}", e) 
328            })?;
329            
330        // Update call metadata
331        if let Some(mut call_info) = self.call_info.get_mut(call_id) {
332            call_info.metadata.insert("on_hold".to_string(), "true".to_string());
333            call_info.metadata.insert("hold_initiated_at".to_string(), Utc::now().to_rfc3339());
334        }
335        
336        // Emit MediaEvent for hold state change
337        if let Some(handler) = self.call_handler.client_event_handler.read().await.as_ref() {
338            let media_event = crate::events::MediaEventInfo {
339                call_id: *call_id,
340                event_type: crate::events::MediaEventType::HoldStateChanged { on_hold: true },
341                timestamp: Utc::now(),
342                metadata: {
343                    let mut metadata = std::collections::HashMap::new();
344                    metadata.insert("session_id".to_string(), session_id.0.clone());
345                    metadata
346                },
347            };
348            handler.on_media_event(media_event).await;
349        }
350        
351        tracing::info!("Put call {} on hold", call_id);
352        Ok(())
353    }
354    
355    /// Resume a call from hold state
356    /// 
357    /// This method resumes a previously held call, restoring audio transmission
358    /// and returning the call to its active connected state. The call must have
359    /// been previously placed on hold using `hold_call()`.
360    /// 
361    /// # Arguments
362    /// 
363    /// * `call_id` - The unique identifier of the call to resume
364    /// 
365    /// # Returns
366    /// 
367    /// Returns `Ok(())` if the call was successfully resumed from hold.
368    /// 
369    /// # Errors
370    /// 
371    /// * `ClientError::CallNotFound` - If no call exists with the given ID
372    /// * `ClientError::CallSetupFailed` - If the resume operation fails
373    /// 
374    /// # State Requirements
375    /// 
376    /// The call should be in a held state to be resumed. However, this method
377    /// will attempt to resume any call that exists, as the session-core layer
378    /// handles the actual state validation.
379    /// 
380    /// # Examples
381    /// 
382    /// ## Basic Resume Operation
383    /// 
384    /// ```rust
385    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
386    /// 
387    /// async fn resume_held_call() -> Result<(), Box<dyn std::error::Error>> {
388    ///     let config = ClientConfig::new()
389    ///         .with_sip_addr("127.0.0.1:5063".parse()?);
390    ///     let client = ClientManager::new(config).await?;
391    ///     client.start().await?;
392    ///     
393    ///     let call_id = CallId::new_v4();
394    ///     
395    ///     // First put call on hold (would normally be done earlier)
396    ///     if let Err(e) = client.hold_call(&call_id).await {
397    ///         println!("Hold failed: {}", e);
398    ///         return Ok(());
399    ///     }
400    ///     
401    ///     // Verify call is on hold
402    ///     if let Ok(on_hold) = client.is_call_on_hold(&call_id).await {
403    ///         println!("Call on hold: {}", on_hold);
404    ///     }
405    ///     
406    ///     // Resume the call
407    ///     match client.resume_call(&call_id).await {
408    ///         Ok(()) => {
409    ///             println!("Call {} successfully resumed", call_id);
410    ///             
411    ///             // Verify call is no longer on hold
412    ///             if let Ok(on_hold) = client.is_call_on_hold(&call_id).await {
413    ///                 assert!(!on_hold);
414    ///             }
415    ///         }
416    ///         Err(e) => {
417    ///             eprintln!("Failed to resume call: {}", e);
418    ///         }
419    ///     }
420    ///     
421    ///     Ok(())
422    /// }
423    /// ```
424    /// 
425    /// ## Resume with Capability Check
426    /// 
427    /// ```rust
428    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
429    /// 
430    /// async fn safe_resume_call() -> Result<(), Box<dyn std::error::Error>> {
431    ///     let config = ClientConfig::new()
432    ///         .with_sip_addr("127.0.0.1:5064".parse()?);
433    ///     let client = ClientManager::new(config).await?;
434    ///     client.start().await?;
435    ///     
436    ///     let call_id = CallId::new_v4();
437    ///     
438    ///     // Check if call can be resumed before attempting
439    ///     if let Ok(capabilities) = client.get_call_capabilities(&call_id).await {
440    ///         if capabilities.can_resume {
441    ///             client.resume_call(&call_id).await?;
442    ///             println!("Call successfully resumed");
443    ///         } else {
444    ///             println!("Call cannot be resumed (not on hold or wrong state)");
445    ///         }
446    ///     }
447    ///     
448    ///     Ok(())
449    /// }
450    /// ```
451    /// 
452    /// ## Hold/Resume Cycle
453    /// 
454    /// ```rust
455    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
456    /// use tokio::time::{sleep, Duration};
457    /// 
458    /// async fn hold_resume_cycle() -> Result<(), Box<dyn std::error::Error>> {
459    ///     let config = ClientConfig::new()
460    ///         .with_sip_addr("127.0.0.1:5065".parse()?);
461    ///     let client = ClientManager::new(config).await?;
462    ///     client.start().await?;
463    ///     
464    ///     let call_id = CallId::new_v4();
465    ///     
466    ///     // Put call on hold
467    ///     if client.hold_call(&call_id).await.is_ok() {
468    ///         println!("Call placed on hold");
469    ///         
470    ///         // Wait briefly (in real app, this might be much longer)
471    ///         sleep(Duration::from_millis(100)).await;
472    ///         
473    ///         // Resume the call
474    ///         if client.resume_call(&call_id).await.is_ok() {
475    ///             println!("Call resumed from hold");
476    ///             
477    ///             // Verify final state
478    ///             if let Ok(on_hold) = client.is_call_on_hold(&call_id).await {
479    ///                 println!("Final hold state: {}", on_hold);
480    ///             }
481    ///         }
482    ///     }
483    ///     
484    ///     Ok(())
485    /// }
486    /// ```
487    /// 
488    /// # Side Effects
489    /// 
490    /// - Updates call metadata to remove hold status and add resume timestamp
491    /// - Triggers media events for hold state change (on_hold: false)
492    /// - Resumes audio transmission between parties
493    /// - May stop hold music playback (if configured)
494    pub async fn resume_call(&self, call_id: &CallId) -> ClientResult<()> {
495        let session_id = self.session_mapping.get(call_id)
496            .ok_or(ClientError::CallNotFound { call_id: *call_id })?
497            .clone();
498            
499        // Use session-core resume functionality
500        SessionControl::resume_session(&self.coordinator, &session_id)
501            .await
502            .map_err(|e| ClientError::CallSetupFailed { 
503                reason: format!("Failed to resume call: {}", e) 
504            })?;
505            
506        // Update call metadata
507        if let Some(mut call_info) = self.call_info.get_mut(call_id) {
508            call_info.metadata.insert("on_hold".to_string(), "false".to_string());
509            call_info.metadata.insert("resumed_at".to_string(), Utc::now().to_rfc3339());
510        }
511        
512        // Emit MediaEvent for hold state change
513        if let Some(handler) = self.call_handler.client_event_handler.read().await.as_ref() {
514            let media_event = crate::events::MediaEventInfo {
515                call_id: *call_id,
516                event_type: crate::events::MediaEventType::HoldStateChanged { on_hold: false },
517                timestamp: Utc::now(),
518                metadata: {
519                    let mut metadata = std::collections::HashMap::new();
520                    metadata.insert("session_id".to_string(), session_id.0.clone());
521                    metadata
522                },
523            };
524            handler.on_media_event(media_event).await;
525        }
526        
527        tracing::info!("Resumed call {} from hold", call_id);
528        Ok(())
529    }
530    
531    /// Check if a call is currently on hold
532    /// 
533    /// This method queries the hold status of a call by examining its metadata.
534    /// It returns `true` if the call is currently on hold, `false` if active,
535    /// or an error if the call doesn't exist.
536    /// 
537    /// # Arguments
538    /// 
539    /// * `call_id` - The unique identifier of the call to check
540    /// 
541    /// # Returns
542    /// 
543    /// Returns `Ok(true)` if the call is on hold, `Ok(false)` if active.
544    /// 
545    /// # Errors
546    /// 
547    /// * `ClientError::CallNotFound` - If no call exists with the given ID
548    /// 
549    /// # Examples
550    /// 
551    /// ## Basic Hold Status Check
552    /// 
553    /// ```rust
554    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
555    /// 
556    /// async fn check_hold_status() -> Result<(), Box<dyn std::error::Error>> {
557    ///     let config = ClientConfig::new()
558    ///         .with_sip_addr("127.0.0.1:5066".parse()?);
559    ///     let client = ClientManager::new(config).await?;
560    ///     client.start().await?;
561    ///     
562    ///     let call_id = CallId::new_v4();
563    ///     
564    ///     // Check initial status
565    ///     match client.is_call_on_hold(&call_id).await {
566    ///         Ok(on_hold) => {
567    ///             println!("Call on hold: {}", on_hold);
568    ///             assert!(!on_hold); // Should be false initially
569    ///         }
570    ///         Err(e) => {
571    ///             println!("Error checking hold status: {}", e);
572    ///         }
573    ///     }
574    ///     
575    ///     Ok(())
576    /// }
577    /// ```
578    /// 
579    /// ## Hold Status Monitoring
580    /// 
581    /// ```rust
582    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
583    /// 
584    /// async fn monitor_hold_status() -> Result<(), Box<dyn std::error::Error>> {
585    ///     let config = ClientConfig::new()
586    ///         .with_sip_addr("127.0.0.1:5067".parse()?);
587    ///     let client = ClientManager::new(config).await?;
588    ///     client.start().await?;
589    ///     
590    ///     let call_id = CallId::new_v4();
591    ///     
592    ///     // Check status before hold
593    ///     if let Ok(status) = client.is_call_on_hold(&call_id).await {
594    ///         println!("Before hold: {}", status);
595    ///     }
596    ///     
597    ///     // Put call on hold (ignore errors for doc test)
598    ///     let _ = client.hold_call(&call_id).await;
599    ///     
600    ///     // Check status after hold
601    ///     if let Ok(status) = client.is_call_on_hold(&call_id).await {
602    ///         println!("After hold: {}", status);
603    ///         if status {
604    ///             println!("✅ Call is now on hold");
605    ///         }
606    ///     }
607    ///     
608    ///     // Resume call (ignore errors for doc test)
609    ///     let _ = client.resume_call(&call_id).await;
610    ///     
611    ///     // Check status after resume
612    ///     if let Ok(status) = client.is_call_on_hold(&call_id).await {
613    ///         println!("After resume: {}", status);
614    ///         if !status {
615    ///             println!("✅ Call is now active");
616    ///         }
617    ///     }
618    ///     
619    ///     Ok(())
620    /// }
621    /// ```
622    /// 
623    /// ## Conditional Operations Based on Hold Status
624    /// 
625    /// ```rust
626    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
627    /// 
628    /// async fn conditional_operations() -> Result<(), Box<dyn std::error::Error>> {
629    ///     let config = ClientConfig::new()
630    ///         .with_sip_addr("127.0.0.1:5068".parse()?);
631    ///     let client = ClientManager::new(config).await?;
632    ///     client.start().await?;
633    ///     
634    ///     let call_id = CallId::new_v4();
635    ///     
636    ///     // Perform different actions based on hold status
637    ///     match client.is_call_on_hold(&call_id).await {
638    ///         Ok(true) => {
639    ///             println!("Call is on hold - offering to resume");
640    ///             // Could resume the call
641    ///             // client.resume_call(&call_id).await?;
642    ///         }
643    ///         Ok(false) => {
644    ///             println!("Call is active - offering to hold");
645    ///             // Could put call on hold
646    ///             // client.hold_call(&call_id).await?;
647    ///         }
648    ///         Err(e) => {
649    ///             println!("Cannot check hold status: {}", e);
650    ///         }
651    ///     }
652    ///     
653    ///     Ok(())
654    /// }
655    /// ```
656    /// 
657    /// # Implementation Notes
658    /// 
659    /// This method checks the call's metadata for an "on_hold" field that is
660    /// set to "true" when a call is placed on hold and "false" when resumed.
661    /// If the metadata field doesn't exist, the call is considered not on hold.
662    pub async fn is_call_on_hold(&self, call_id: &CallId) -> ClientResult<bool> {
663        if let Some(call_info) = self.call_info.get(call_id) {
664            // Check metadata for hold status
665            let on_hold = call_info.metadata.get("on_hold")
666                .map(|s| s == "true")
667                .unwrap_or(false);
668            Ok(on_hold)
669        } else {
670            Err(ClientError::CallNotFound { call_id: *call_id })
671        }
672    }
673    
674    /// Send DTMF (Dual-Tone Multi-Frequency) digits during an active call
675    /// 
676    /// This method transmits DTMF tones to the remote party during a connected call.
677    /// DTMF is commonly used for navigating phone menus, entering PINs, or other
678    /// interactive voice response (IVR) interactions.
679    /// 
680    /// # Arguments
681    /// 
682    /// * `call_id` - The unique identifier of the call to send DTMF to
683    /// * `digits` - A string containing valid DTMF characters to transmit
684    /// 
685    /// # Valid DTMF Characters
686    /// 
687    /// - **Digits**: `0-9` (standard numeric keypad)
688    /// - **Letters**: `A-D` (extended DTMF for special applications)
689    /// - **Symbols**: `*` (star) and `#` (pound/hash)
690    /// 
691    /// # Returns
692    /// 
693    /// Returns `Ok(())` if the DTMF digits were successfully transmitted.
694    /// 
695    /// # Errors
696    /// 
697    /// * `ClientError::CallNotFound` - If no call exists with the given ID
698    /// * `ClientError::InvalidCallState` - If the call is not in a connected state
699    /// * `ClientError::InvalidConfiguration` - If digits are empty or contain invalid characters
700    /// * `ClientError::CallSetupFailed` - If the DTMF transmission fails
701    /// 
702    /// # State Requirements
703    /// 
704    /// The call must be in the `Connected` state to send DTMF. Calls that are
705    /// ringing, terminated, or in other states cannot transmit DTMF tones.
706    /// 
707    /// # Examples
708    /// 
709    /// ## Basic DTMF Transmission
710    /// 
711    /// ```rust
712    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
713    /// 
714    /// async fn send_basic_dtmf() -> Result<(), Box<dyn std::error::Error>> {
715    ///     let config = ClientConfig::new()
716    ///         .with_sip_addr("127.0.0.1:5069".parse()?);
717    ///     let client = ClientManager::new(config).await?;
718    ///     client.start().await?;
719    ///     
720    ///     let call_id = CallId::new_v4();
721    ///     
722    ///     // Send individual digit
723    ///     match client.send_dtmf(&call_id, "1").await {
724    ///         Ok(()) => println!("✅ Sent DTMF digit '1'"),
725    ///         Err(e) => println!("❌ DTMF failed: {}", e),
726    ///     }
727    ///     
728    ///     // Send multiple digits
729    ///     if client.send_dtmf(&call_id, "123").await.is_ok() {
730    ///         println!("✅ Sent DTMF sequence '123'");
731    ///     }
732    ///     
733    ///     Ok(())
734    /// }
735    /// ```
736    /// 
737    /// ## Interactive Menu Navigation
738    /// 
739    /// ```rust
740    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
741    /// use tokio::time::{sleep, Duration};
742    /// 
743    /// async fn navigate_menu() -> Result<(), Box<dyn std::error::Error>> {
744    ///     let config = ClientConfig::new()
745    ///         .with_sip_addr("127.0.0.1:5070".parse()?);
746    ///     let client = ClientManager::new(config).await?;
747    ///     client.start().await?;
748    ///     
749    ///     let call_id = CallId::new_v4();
750    ///     
751    ///     // Navigate through a typical phone menu
752    ///     let menu_sequence = [
753    ///         ("1", "Select English"),
754    ///         ("2", "Customer Service"),
755    ///         ("3", "Account Information"),
756    ///         ("*", "Return to previous menu"),
757    ///         ("#", "End menu navigation"),
758    ///     ];
759    ///     
760    ///     for (digit, description) in menu_sequence {
761    ///         if client.send_dtmf(&call_id, digit).await.is_ok() {
762    ///             println!("📞 Sent '{}' - {}", digit, description);
763    ///             
764    ///             // Wait between menu selections
765    ///             sleep(Duration::from_millis(50)).await;
766    ///         }
767    ///     }
768    ///     
769    ///     Ok(())
770    /// }
771    /// ```
772    /// 
773    /// ## PIN Entry with Validation
774    /// 
775    /// ```rust
776    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
777    /// 
778    /// async fn enter_pin() -> Result<(), Box<dyn std::error::Error>> {
779    ///     let config = ClientConfig::new()
780    ///         .with_sip_addr("127.0.0.1:5071".parse()?);
781    ///     let client = ClientManager::new(config).await?;
782    ///     client.start().await?;
783    ///     
784    ///     let call_id = CallId::new_v4();
785    ///     
786    ///     // Example PIN entry
787    ///     let pin = "1234";
788    ///     
789    ///     // Validate PIN before sending
790    ///     for ch in pin.chars() {
791    ///         if !ch.is_ascii_digit() {
792    ///             println!("❌ Invalid PIN character: {}", ch);
793    ///             return Ok(());
794    ///         }
795    ///     }
796    ///     
797    ///     // Send PIN digits
798    ///     match client.send_dtmf(&call_id, pin).await {
799    ///         Ok(()) => {
800    ///             println!("✅ PIN entered successfully");
801    ///             
802    ///             // Send confirmation tone
803    ///             if client.send_dtmf(&call_id, "#").await.is_ok() {
804    ///                 println!("✅ PIN confirmed with #");
805    ///             }
806    ///         }
807    ///         Err(e) => {
808    ///             println!("❌ PIN entry failed: {}", e);
809    ///         }
810    ///     }
811    ///     
812    ///     Ok(())
813    /// }
814    /// ```
815    /// 
816    /// ## Extended DTMF Usage
817    /// 
818    /// ```rust
819    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
820    /// 
821    /// async fn extended_dtmf() -> Result<(), Box<dyn std::error::Error>> {
822    ///     let config = ClientConfig::new()
823    ///         .with_sip_addr("127.0.0.1:5072".parse()?);
824    ///     let client = ClientManager::new(config).await?;
825    ///     client.start().await?;
826    ///     
827    ///     let call_id = CallId::new_v4();
828    ///     
829    ///     // Use extended DTMF characters (A-D)
830    ///     let extended_sequence = "123A456B789C0*#D";
831    ///     
832    ///     match client.send_dtmf(&call_id, extended_sequence).await {
833    ///         Ok(()) => {
834    ///             println!("✅ Extended DTMF sequence sent");
835    ///             println!("Sequence: {}", extended_sequence);
836    ///         }
837    ///         Err(e) => {
838    ///             println!("❌ Extended DTMF failed: {}", e);
839    ///         }
840    ///     }
841    ///     
842    ///     // Test individual extended characters
843    ///     for digit in ['A', 'B', 'C', 'D'] {
844    ///         let digit_str = digit.to_string();
845    ///         if client.send_dtmf(&call_id, &digit_str).await.is_ok() {
846    ///             println!("✅ Sent extended DTMF: {}", digit);
847    ///         }
848    ///     }
849    ///     
850    ///     Ok(())
851    /// }
852    /// ```
853    /// 
854    /// # Side Effects
855    /// 
856    /// - Updates call metadata with DTMF history and timestamps
857    /// - Triggers media events for DTMF transmission
858    /// - Transmits actual audio tones to the remote party
859    /// - Maintains a history of all DTMF transmissions for the call
860    /// 
861    /// # Implementation Notes
862    /// 
863    /// The method validates DTMF characters before transmission and maintains
864    /// a history of all DTMF sequences sent during the call. Both uppercase
865    /// and lowercase letters (A-D, a-d) are accepted and normalized.
866    pub async fn send_dtmf(&self, call_id: &CallId, digits: &str) -> ClientResult<()> {
867        let session_id = self.session_mapping.get(call_id)
868            .ok_or(ClientError::CallNotFound { call_id: *call_id })?
869            .clone();
870            
871        // Validate call state
872        if let Some(call_info) = self.call_info.get(call_id) {
873            match call_info.state {
874                crate::call::CallState::Connected => {
875                    // OK to send DTMF
876                }
877                crate::call::CallState::Terminated | 
878                crate::call::CallState::Failed | 
879                crate::call::CallState::Cancelled => {
880                    return Err(ClientError::InvalidCallState { 
881                        call_id: *call_id, 
882                        current_state: call_info.state.clone() 
883                    });
884                }
885                _ => {
886                    return Err(ClientError::InvalidCallStateGeneric { 
887                        expected: "Connected".to_string(),
888                        actual: format!("{:?}", call_info.state)
889                    });
890                }
891            }
892        }
893        
894        // Validate DTMF digits
895        if digits.is_empty() {
896            return Err(ClientError::InvalidConfiguration { 
897                field: "dtmf_digits".to_string(),
898                reason: "DTMF digits cannot be empty".to_string() 
899            });
900        }
901        
902        // Check for valid DTMF characters (0-9, A-D, *, #)
903        for ch in digits.chars() {
904            if !matches!(ch, '0'..='9' | 'A'..='D' | 'a'..='d' | '*' | '#') {
905                return Err(ClientError::InvalidConfiguration { 
906                    field: "dtmf_digits".to_string(),
907                    reason: format!("Invalid DTMF character: {}", ch) 
908                });
909            }
910        }
911            
912        // Use session-core DTMF functionality
913        SessionControl::send_dtmf(&self.coordinator, &session_id, digits)
914            .await
915            .map_err(|e| ClientError::CallSetupFailed { 
916                reason: format!("Failed to send DTMF: {}", e) 
917            })?;
918            
919        // Update call metadata
920        if let Some(mut call_info) = self.call_info.get_mut(call_id) {
921            let dtmf_history = call_info.metadata.entry("dtmf_history".to_string())
922                .or_insert_with(String::new);
923            if !dtmf_history.is_empty() {
924                dtmf_history.push(',');
925            }
926            dtmf_history.push_str(&format!("{}@{}", digits, Utc::now().to_rfc3339()));
927            
928            call_info.metadata.insert("last_dtmf_sent".to_string(), digits.to_string());
929            call_info.metadata.insert("last_dtmf_at".to_string(), Utc::now().to_rfc3339());
930        }
931        
932        // Emit MediaEvent for DTMF
933        if let Some(handler) = self.call_handler.client_event_handler.read().await.as_ref() {
934            let media_event = crate::events::MediaEventInfo {
935                call_id: *call_id,
936                event_type: crate::events::MediaEventType::DtmfSent { digits: digits.to_string() },
937                timestamp: Utc::now(),
938                metadata: {
939                    let mut metadata = std::collections::HashMap::new();
940                    metadata.insert("session_id".to_string(), session_id.0.clone());
941                    metadata
942                },
943            };
944            handler.on_media_event(media_event).await;
945        }
946        
947        tracing::info!("Sent DTMF '{}' to call {}", digits, call_id);
948        Ok(())
949    }
950    
951    /// Transfer a call to another destination (blind transfer)
952    /// 
953    /// This method performs a blind transfer, which immediately transfers the call
954    /// to the specified destination without consultation. The original caller is
955    /// connected directly to the transfer target, and the transferring party is
956    /// removed from the call.
957    /// 
958    /// # Arguments
959    /// 
960    /// * `call_id` - The unique identifier of the call to transfer
961    /// * `target` - The SIP or TEL URI of the transfer destination
962    /// 
963    /// # Valid Target Formats
964    /// 
965    /// - **SIP URI**: `sip:user@domain.com` or `sip:user@192.168.1.100:5060`
966    /// - **TEL URI**: `tel:+15551234567` (for PSTN numbers)
967    /// 
968    /// # Returns
969    /// 
970    /// Returns `Ok(())` if the transfer was successfully initiated.
971    /// 
972    /// # Errors
973    /// 
974    /// * `ClientError::CallNotFound` - If no call exists with the given ID
975    /// * `ClientError::InvalidCallState` - If the call is not in a transferable state
976    /// * `ClientError::InvalidConfiguration` - If the target URI is empty or invalid
977    /// * `ClientError::CallSetupFailed` - If the transfer operation fails
978    /// 
979    /// # State Requirements
980    /// 
981    /// The call must be in the `Connected` state to be transferred. Calls in
982    /// other states (ringing, terminated, etc.) cannot be transferred.
983    /// 
984    /// # Examples
985    /// 
986    /// ## Basic Blind Transfer
987    /// 
988    /// ```rust
989    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
990    /// 
991    /// async fn perform_blind_transfer() -> Result<(), Box<dyn std::error::Error>> {
992    ///     let config = ClientConfig::new()
993    ///         .with_sip_addr("127.0.0.1:5073".parse()?);
994    ///     let client = ClientManager::new(config).await?;
995    ///     client.start().await?;
996    ///     
997    ///     let call_id = CallId::new_v4();
998    ///     
999    ///     // Transfer to another SIP user
1000    ///     let transfer_target = "sip:support@example.com";
1001    ///     
1002    ///     match client.transfer_call(&call_id, transfer_target).await {
1003    ///         Ok(()) => {
1004    ///             println!("✅ Call {} transferred to {}", call_id, transfer_target);
1005    ///         }
1006    ///         Err(e) => {
1007    ///             println!("❌ Transfer failed: {}", e);
1008    ///         }
1009    ///     }
1010    ///     
1011    ///     Ok(())
1012    /// }
1013    /// ```
1014    /// 
1015    /// ## Transfer to PSTN Number
1016    /// 
1017    /// ```rust
1018    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
1019    /// 
1020    /// async fn transfer_to_pstn() -> Result<(), Box<dyn std::error::Error>> {
1021    ///     let config = ClientConfig::new()
1022    ///         .with_sip_addr("127.0.0.1:5074".parse()?);
1023    ///     let client = ClientManager::new(config).await?;
1024    ///     client.start().await?;
1025    ///     
1026    ///     let call_id = CallId::new_v4();
1027    ///     
1028    ///     // Transfer to external phone number
1029    ///     let phone_number = "tel:+15551234567";
1030    ///     
1031    ///     if client.transfer_call(&call_id, phone_number).await.is_ok() {
1032    ///         println!("✅ Call transferred to phone: {}", phone_number);
1033    ///     } else {
1034    ///         println!("❌ PSTN transfer failed");
1035    ///     }
1036    ///     
1037    ///     Ok(())
1038    /// }
1039    /// ```
1040    /// 
1041    /// ## Transfer with Validation
1042    /// 
1043    /// ```rust
1044    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
1045    /// 
1046    /// async fn validated_transfer() -> Result<(), Box<dyn std::error::Error>> {
1047    ///     let config = ClientConfig::new()
1048    ///         .with_sip_addr("127.0.0.1:5075".parse()?);
1049    ///     let client = ClientManager::new(config).await?;
1050    ///     client.start().await?;
1051    ///     
1052    ///     let call_id = CallId::new_v4();
1053    ///     let target = "sip:manager@company.com";
1054    ///     
1055    ///     // Check if call can be transferred before attempting
1056    ///     if let Ok(capabilities) = client.get_call_capabilities(&call_id).await {
1057    ///         if capabilities.can_transfer {
1058    ///             // Validate target format
1059    ///             if target.starts_with("sip:") || target.starts_with("tel:") {
1060    ///                 match client.transfer_call(&call_id, target).await {
1061    ///                     Ok(()) => {
1062    ///                         println!("✅ Transfer completed successfully");
1063    ///                     }
1064    ///                     Err(e) => {
1065    ///                         println!("❌ Transfer failed: {}", e);
1066    ///                     }
1067    ///                 }
1068    ///             } else {
1069    ///                 println!("❌ Invalid target URI format");
1070    ///             }
1071    ///         } else {
1072    ///             println!("❌ Call cannot be transferred in current state");
1073    ///         }
1074    ///     }
1075    ///     
1076    ///     Ok(())
1077    /// }
1078    /// ```
1079    /// 
1080    /// ## Multiple Transfer Destinations
1081    /// 
1082    /// ```rust
1083    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
1084    /// 
1085    /// async fn try_multiple_transfers() -> Result<(), Box<dyn std::error::Error>> {
1086    ///     let config = ClientConfig::new()
1087    ///         .with_sip_addr("127.0.0.1:5076".parse()?);
1088    ///     let client = ClientManager::new(config).await?;
1089    ///     client.start().await?;
1090    ///     
1091    ///     let call_id = CallId::new_v4();
1092    ///     
1093    ///     // Try multiple transfer destinations in order
1094    ///     let transfer_options = [
1095    ///         ("sip:primary@support.com", "Primary Support"),
1096    ///         ("sip:backup@support.com", "Backup Support"),
1097    ///         ("tel:+15551234567", "Emergency Line"),
1098    ///     ];
1099    ///     
1100    ///     for (target, description) in transfer_options {
1101    ///         match client.transfer_call(&call_id, target).await {
1102    ///             Ok(()) => {
1103    ///                 println!("✅ Successfully transferred to {} ({})", target, description);
1104    ///                 break; // Stop after first successful transfer
1105    ///             }
1106    ///             Err(e) => {
1107    ///                 println!("❌ Failed to transfer to {}: {}", description, e);
1108    ///                 // Continue to next option
1109    ///             }
1110    ///         }
1111    ///     }
1112    ///     
1113    ///     Ok(())
1114    /// }
1115    /// ```
1116    /// 
1117    /// # Transfer Types
1118    /// 
1119    /// This method performs a **blind transfer** (also called unattended transfer):
1120    /// - The call is immediately transferred without consultation
1121    /// - The transferring party does not speak to the transfer target first
1122    /// - The original caller is connected directly to the transfer destination
1123    /// - The transferring party is removed from the call immediately
1124    /// 
1125    /// For **attended transfers** (with consultation), use the `attended_transfer()` method.
1126    /// 
1127    /// # Side Effects
1128    /// 
1129    /// - Updates call metadata with transfer information and timestamp
1130    /// - Triggers media events for transfer initiation
1131    /// - The local party is immediately disconnected from the call
1132    /// - The remote party is connected to the transfer target
1133    /// 
1134    /// # SIP Protocol Notes
1135    /// 
1136    /// This method uses SIP REFER requests to perform the transfer, which is
1137    /// the standard mechanism defined in RFC 3515. The transfer target must
1138    /// be reachable and accept the incoming call for the transfer to succeed.
1139    pub async fn transfer_call(&self, call_id: &CallId, target: &str) -> ClientResult<()> {
1140        let session_id = self.session_mapping.get(call_id)
1141            .ok_or(ClientError::CallNotFound { call_id: *call_id })?
1142            .clone();
1143            
1144        // Validate call state
1145        if let Some(call_info) = self.call_info.get(call_id) {
1146            match call_info.state {
1147                crate::call::CallState::Connected => {
1148                    // OK to transfer
1149                }
1150                crate::call::CallState::Terminated | 
1151                crate::call::CallState::Failed | 
1152                crate::call::CallState::Cancelled => {
1153                    return Err(ClientError::InvalidCallState { 
1154                        call_id: *call_id, 
1155                        current_state: call_info.state.clone() 
1156                    });
1157                }
1158                _ => {
1159                    return Err(ClientError::InvalidCallStateGeneric { 
1160                        expected: "Connected".to_string(),
1161                        actual: format!("{:?}", call_info.state)
1162                    });
1163                }
1164            }
1165        }
1166        
1167        // Validate target URI
1168        if target.is_empty() {
1169            return Err(ClientError::InvalidConfiguration { 
1170                field: "transfer_target".to_string(),
1171                reason: "Transfer target cannot be empty".to_string() 
1172            });
1173        }
1174        
1175        if !target.starts_with("sip:") && !target.starts_with("tel:") {
1176            return Err(ClientError::InvalidConfiguration { 
1177                field: "transfer_target".to_string(),
1178                reason: "Transfer target must be a valid SIP or TEL URI".to_string() 
1179            });
1180        }
1181            
1182        // Use session-core transfer functionality
1183        SessionControl::transfer_session(&self.coordinator, &session_id, target)
1184            .await
1185            .map_err(|e| ClientError::CallSetupFailed { 
1186                reason: format!("Failed to transfer call: {}", e) 
1187            })?;
1188            
1189        // Update call metadata
1190        if let Some(mut call_info) = self.call_info.get_mut(call_id) {
1191            call_info.metadata.insert("transfer_target".to_string(), target.to_string());
1192            call_info.metadata.insert("transfer_initiated_at".to_string(), Utc::now().to_rfc3339());
1193            call_info.metadata.insert("transfer_type".to_string(), "blind".to_string());
1194        }
1195        
1196        // Emit MediaEvent for transfer initiation
1197        if let Some(handler) = self.call_handler.client_event_handler.read().await.as_ref() {
1198            let media_event = crate::events::MediaEventInfo {
1199                call_id: *call_id,
1200                event_type: crate::events::MediaEventType::TransferInitiated { 
1201                    target: target.to_string(), 
1202                    transfer_type: "blind".to_string() 
1203                },
1204                timestamp: Utc::now(),
1205                metadata: {
1206                    let mut metadata = std::collections::HashMap::new();
1207                    metadata.insert("session_id".to_string(), session_id.0.clone());
1208                    metadata
1209                },
1210            };
1211            handler.on_media_event(media_event).await;
1212        }
1213        
1214        tracing::info!("Initiated blind transfer of call {} to {}", call_id, target);
1215        Ok(())
1216    }
1217    
1218    /// Perform an attended transfer (consultative transfer)
1219    /// 
1220    /// This method performs an attended transfer, which connects two existing calls
1221    /// together. The typical scenario is having one call on hold while establishing
1222    /// a consultation call with the transfer target, then connecting the original
1223    /// caller directly to the transfer target.
1224    /// 
1225    /// # Arguments
1226    /// 
1227    /// * `call_id1` - The primary call to be transferred (usually the original call)
1228    /// * `call_id2` - The consultation call (the transfer target)
1229    /// 
1230    /// # Transfer Process
1231    /// 
1232    /// 1. **Hold**: The primary call (`call_id1`) is placed on hold
1233    /// 2. **Consultation**: The agent speaks with the transfer target (`call_id2`)
1234    /// 3. **Transfer**: The primary caller is connected to the transfer target
1235    /// 4. **Cleanup**: The consultation call is terminated
1236    /// 
1237    /// # Returns
1238    /// 
1239    /// Returns `Ok(())` if the attended transfer was successfully completed.
1240    /// 
1241    /// # Errors
1242    /// 
1243    /// * `ClientError::CallNotFound` - If either call ID doesn't exist
1244    /// * `ClientError::InvalidCallState` - If either call is not in a transferable state
1245    /// * `ClientError::CallSetupFailed` - If any step of the transfer process fails
1246    /// 
1247    /// # State Requirements
1248    /// 
1249    /// Both calls must be in the `Connected` state to perform an attended transfer.
1250    /// 
1251    /// # Examples
1252    /// 
1253    /// ## Basic Attended Transfer
1254    /// 
1255    /// ```rust
1256    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
1257    /// 
1258    /// async fn perform_attended_transfer() -> Result<(), Box<dyn std::error::Error>> {
1259    ///     let config = ClientConfig::new()
1260    ///         .with_sip_addr("127.0.0.1:5077".parse()?);
1261    ///     let client = ClientManager::new(config).await?;
1262    ///     client.start().await?;
1263    ///     
1264    ///     // Assume we have two active calls
1265    ///     let primary_call = CallId::new_v4();    // Original caller
1266    ///     let consultation_call = CallId::new_v4(); // Transfer target
1267    ///     
1268    ///     match client.attended_transfer(&primary_call, &consultation_call).await {
1269    ///         Ok(()) => {
1270    ///             println!("✅ Attended transfer completed successfully");
1271    ///             println!("Primary caller connected to transfer target");
1272    ///         }
1273    ///         Err(e) => {
1274    ///             println!("❌ Attended transfer failed: {}", e);
1275    ///         }
1276    ///     }
1277    ///     
1278    ///     Ok(())
1279    /// }
1280    /// ```
1281    /// 
1282    /// ## Step-by-Step Transfer Workflow
1283    /// 
1284    /// ```rust
1285    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
1286    /// use tokio::time::{sleep, Duration};
1287    /// 
1288    /// async fn transfer_workflow() -> Result<(), Box<dyn std::error::Error>> {
1289    ///     let config = ClientConfig::new()
1290    ///         .with_sip_addr("127.0.0.1:5078".parse()?);
1291    ///     let client = ClientManager::new(config).await?;
1292    ///     client.start().await?;
1293    ///     
1294    ///     let customer_call = CallId::new_v4();
1295    ///     let manager_call = CallId::new_v4();
1296    ///     
1297    ///     // Step 1: Answer customer call (would be done earlier)
1298    ///     println!("📞 Customer call in progress...");
1299    ///     
1300    ///     // Step 2: Put customer on hold to make consultation call
1301    ///     println!("⏸️  Putting customer on hold...");
1302    ///     if client.hold_call(&customer_call).await.is_ok() {
1303    ///         println!("✅ Customer on hold");
1304    ///     }
1305    ///     
1306    ///     // Step 3: Make consultation call to manager (would be done earlier)
1307    ///     println!("📞 Calling manager for consultation...");
1308    ///     // client.make_call("sip:manager@company.com").await?;
1309    ///     
1310    ///     // Step 4: Brief consultation (simulated)
1311    ///     sleep(Duration::from_millis(100)).await;
1312    ///     println!("💬 Consultation complete - transferring call");
1313    ///     
1314    ///     // Step 5: Perform the attended transfer
1315    ///     match client.attended_transfer(&customer_call, &manager_call).await {
1316    ///         Ok(()) => {
1317    ///             println!("✅ Transfer complete - customer now speaking with manager");
1318    ///         }
1319    ///         Err(e) => {
1320    ///             println!("❌ Transfer failed: {}", e);
1321    ///             // Would typically resume customer call here
1322    ///             let _ = client.resume_call(&customer_call).await;
1323    ///         }
1324    ///     }
1325    ///     
1326    ///     Ok(())
1327    /// }
1328    /// ```
1329    /// 
1330    /// ## Error Recovery Attended Transfer
1331    /// 
1332    /// ```rust
1333    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId, ClientError};
1334    /// 
1335    /// async fn robust_attended_transfer() -> Result<(), Box<dyn std::error::Error>> {
1336    ///     let config = ClientConfig::new()
1337    ///         .with_sip_addr("127.0.0.1:5079".parse()?);
1338    ///     let client = ClientManager::new(config).await?;
1339    ///     client.start().await?;
1340    ///     
1341    ///     let caller_id = CallId::new_v4();
1342    ///     let target_id = CallId::new_v4();
1343    ///     
1344    ///     // Check capabilities before attempting transfer
1345    ///     let can_transfer_caller = client.get_call_capabilities(&caller_id).await
1346    ///         .map(|caps| caps.can_transfer)
1347    ///         .unwrap_or(false);
1348    ///     
1349    ///     let can_transfer_target = client.get_call_capabilities(&target_id).await
1350    ///         .map(|caps| caps.can_transfer)
1351    ///         .unwrap_or(false);
1352    ///     
1353    ///     if !can_transfer_caller || !can_transfer_target {
1354    ///         println!("❌ One or both calls cannot be transferred");
1355    ///         return Ok(());
1356    ///     }
1357    ///     
1358    ///     match client.attended_transfer(&caller_id, &target_id).await {
1359    ///         Ok(()) => {
1360    ///             println!("✅ Attended transfer successful");
1361    ///         }
1362    ///         Err(ClientError::CallNotFound { call_id }) => {
1363    ///             println!("❌ Call {} no longer exists", call_id);
1364    ///         }
1365    ///         Err(ClientError::InvalidCallState { call_id, current_state }) => {
1366    ///             println!("❌ Call {} in invalid state: {:?}", call_id, current_state);
1367    ///             
1368    ///             // Try to recover by resuming the original call
1369    ///             if let Err(e) = client.resume_call(&caller_id).await {
1370    ///                 println!("❌ Failed to resume original call: {}", e);
1371    ///             }
1372    ///         }
1373    ///         Err(e) => {
1374    ///             println!("❌ Transfer failed: {}", e);
1375    ///             
1376    ///             // General recovery - try to resume original call
1377    ///             let _ = client.resume_call(&caller_id).await;
1378    ///         }
1379    ///     }
1380    ///     
1381    ///     Ok(())
1382    /// }
1383    /// ```
1384    /// 
1385    /// # Comparison with Blind Transfer
1386    /// 
1387    /// | Feature | Blind Transfer | Attended Transfer |
1388    /// |---------|----------------|-------------------|
1389    /// | **Consultation** | No | Yes |
1390    /// | **Agent Control** | Immediate | Full control |
1391    /// | **Success Rate** | Lower | Higher |
1392    /// | **User Experience** | Basic | Professional |
1393    /// | **Call Setup** | Single call | Two calls |
1394    /// 
1395    /// # Side Effects
1396    /// 
1397    /// - The primary call is placed on hold during the process
1398    /// - Call metadata is updated with transfer type "attended"
1399    /// - Media events are triggered for transfer completion
1400    /// - The consultation call is automatically terminated
1401    /// - The transferring agent is removed from both calls
1402    /// 
1403    /// # Best Practices
1404    /// 
1405    /// 1. **Always verify both calls exist** before attempting transfer
1406    /// 2. **Check call capabilities** to ensure transfer is possible
1407    /// 3. **Implement error recovery** to handle failed transfers gracefully
1408    /// 4. **Inform the customer** when placing them on hold for consultation
1409    /// 5. **Have a fallback plan** if the transfer target is unavailable
1410    pub async fn attended_transfer(&self, call_id1: &CallId, call_id2: &CallId) -> ClientResult<()> {
1411        // Get session IDs for both calls (for validation, though not directly used below)
1412        let _session_id1 = self.session_mapping.get(call_id1)
1413            .ok_or(ClientError::CallNotFound { call_id: *call_id1 })?
1414            .clone();
1415        let _session_id2 = self.session_mapping.get(call_id2)
1416            .ok_or(ClientError::CallNotFound { call_id: *call_id2 })?
1417            .clone();
1418            
1419        // Validate both calls are in connected state
1420        for call_id in [call_id1, call_id2] {
1421            if let Some(call_info) = self.call_info.get(call_id) {
1422                match call_info.state {
1423                    crate::call::CallState::Connected => {
1424                        // OK to transfer
1425                    }
1426                    crate::call::CallState::Terminated | 
1427                    crate::call::CallState::Failed | 
1428                    crate::call::CallState::Cancelled => {
1429                        return Err(ClientError::InvalidCallState { 
1430                            call_id: *call_id, 
1431                            current_state: call_info.state.clone() 
1432                        });
1433                    }
1434                    _ => {
1435                        return Err(ClientError::InvalidCallStateGeneric { 
1436                            expected: "Connected".to_string(),
1437                            actual: format!("{:?}", call_info.state)
1438                        });
1439                    }
1440                }
1441            }
1442        }
1443        
1444        // For attended transfer, we typically would:
1445        // 1. Put the first call on hold
1446        // 2. Establish a consultation call with the transfer target
1447        // 3. Complete the transfer connecting the original caller to the transfer target
1448        // 
1449        // Since session-core doesn't have a specific attended transfer API,
1450        // we'll simulate it with available operations
1451        
1452        // Put first call on hold
1453        self.hold_call(call_id1).await?;
1454        
1455        // Get remote URI from second call to use as transfer target
1456        let target_uri = if let Some(call_info2) = self.call_info.get(call_id2) {
1457            call_info2.remote_uri.clone()
1458        } else {
1459            return Err(ClientError::CallNotFound { call_id: *call_id2 });
1460        };
1461        
1462        // Transfer the first call to the target of the second call
1463        self.transfer_call(call_id1, &target_uri).await?;
1464        
1465        // Hang up the consultation call since transfer is completing
1466        self.hangup_call(call_id2).await?;
1467        
1468        // Update metadata for attended transfer
1469        if let Some(mut call_info) = self.call_info.get_mut(call_id1) {
1470            call_info.metadata.insert("transfer_type".to_string(), "attended".to_string());
1471            call_info.metadata.insert("consultation_call_id".to_string(), call_id2.to_string());
1472            call_info.metadata.insert("attended_transfer_completed_at".to_string(), Utc::now().to_rfc3339());
1473        }
1474        
1475        tracing::info!("Completed attended transfer: call {} transferred to target of call {}", call_id1, call_id2);
1476        Ok(())
1477    }
1478    
1479    /// Get call control capabilities for a specific call
1480    /// 
1481    /// This method returns the available call control operations for a call based on
1482    /// its current state. Different call states support different operations, and this
1483    /// method helps applications determine what actions are available before attempting them.
1484    /// 
1485    /// # Arguments
1486    /// 
1487    /// * `call_id` - The unique identifier of the call to query
1488    /// 
1489    /// # Returns
1490    /// 
1491    /// Returns a `CallCapabilities` struct indicating which operations are available:
1492    /// 
1493    /// - `can_hold` - Whether the call can be placed on hold
1494    /// - `can_resume` - Whether the call can be resumed from hold
1495    /// - `can_transfer` - Whether the call can be transferred
1496    /// - `can_send_dtmf` - Whether DTMF digits can be sent
1497    /// - `can_mute` - Whether the call can be muted
1498    /// - `can_hangup` - Whether the call can be terminated
1499    /// 
1500    /// # Errors
1501    /// 
1502    /// * `ClientError::CallNotFound` - If no call exists with the given ID
1503    /// 
1504    /// # Capability Matrix by Call State
1505    /// 
1506    /// | State | Hold | Resume | Transfer | DTMF | Mute | Hangup |
1507    /// |-------|------|--------|----------|------|------|--------|
1508    /// | **Connected** | ✅ | ⚡ | ✅ | ✅ | ✅ | ✅ |
1509    /// | **Ringing** | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
1510    /// | **Initiating** | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
1511    /// | **Proceeding** | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
1512    /// | **Terminated** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
1513    /// 
1514    /// ⚡ = Available only if call is currently on hold
1515    /// 
1516    /// # Examples
1517    /// 
1518    /// ## Basic Capability Check
1519    /// 
1520    /// ```rust
1521    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
1522    /// 
1523    /// async fn check_capabilities() -> Result<(), Box<dyn std::error::Error>> {
1524    ///     let config = ClientConfig::new()
1525    ///         .with_sip_addr("127.0.0.1:5080".parse()?);
1526    ///     let client = ClientManager::new(config).await?;
1527    ///     client.start().await?;
1528    ///     
1529    ///     let call_id = CallId::new_v4();
1530    ///     
1531    ///     match client.get_call_capabilities(&call_id).await {
1532    ///         Ok(capabilities) => {
1533    ///             println!("Call capabilities:");
1534    ///             println!("  Hold: {}", capabilities.can_hold);
1535    ///             println!("  Resume: {}", capabilities.can_resume);
1536    ///             println!("  Transfer: {}", capabilities.can_transfer);
1537    ///             println!("  DTMF: {}", capabilities.can_send_dtmf);
1538    ///             println!("  Mute: {}", capabilities.can_mute);
1539    ///             println!("  Hangup: {}", capabilities.can_hangup);
1540    ///         }
1541    ///         Err(e) => {
1542    ///             println!("Failed to get capabilities: {}", e);
1543    ///         }
1544    ///     }
1545    ///     
1546    ///     Ok(())
1547    /// }
1548    /// ```
1549    /// 
1550    /// ## Conditional Operation Execution
1551    /// 
1552    /// ```rust
1553    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
1554    /// 
1555    /// async fn conditional_operations() -> Result<(), Box<dyn std::error::Error>> {
1556    ///     let config = ClientConfig::new()
1557    ///         .with_sip_addr("127.0.0.1:5081".parse()?);
1558    ///     let client = ClientManager::new(config).await?;
1559    ///     client.start().await?;
1560    ///     
1561    ///     let call_id = CallId::new_v4();
1562    ///     
1563    ///     if let Ok(caps) = client.get_call_capabilities(&call_id).await {
1564    ///         // Only attempt operations that are available
1565    ///         if caps.can_hold {
1566    ///             println!("✅ Hold operation available");
1567    ///             // client.hold_call(&call_id).await?;
1568    ///         } else {
1569    ///             println!("❌ Cannot hold call in current state");
1570    ///         }
1571    ///         
1572    ///         if caps.can_send_dtmf {
1573    ///             println!("✅ DTMF available - can send digits");
1574    ///             // client.send_dtmf(&call_id, "123").await?;
1575    ///         }
1576    ///         
1577    ///         if caps.can_transfer {
1578    ///             println!("✅ Transfer available");
1579    ///             // client.transfer_call(&call_id, "sip:target@example.com").await?;
1580    ///         }
1581    ///     }
1582    ///     
1583    ///     Ok(())
1584    /// }
1585    /// ```
1586    /// 
1587    /// ## Dynamic UI Updates
1588    /// 
1589    /// ```rust
1590    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
1591    /// 
1592    /// async fn update_ui_based_on_capabilities() -> Result<(), Box<dyn std::error::Error>> {
1593    ///     let config = ClientConfig::new()
1594    ///         .with_sip_addr("127.0.0.1:5082".parse()?);
1595    ///     let client = ClientManager::new(config).await?;
1596    ///     client.start().await?;
1597    ///     
1598    ///     let call_id = CallId::new_v4();
1599    ///     
1600    ///     if let Ok(capabilities) = client.get_call_capabilities(&call_id).await {
1601    ///         // Simulate UI button states
1602    ///         let buttons = vec![
1603    ///             ("Hold", capabilities.can_hold),
1604    ///             ("Resume", capabilities.can_resume),
1605    ///             ("Transfer", capabilities.can_transfer),
1606    ///             ("DTMF", capabilities.can_send_dtmf),
1607    ///             ("Mute", capabilities.can_mute),
1608    ///             ("Hangup", capabilities.can_hangup),
1609    ///         ];
1610    ///         
1611    ///         println!("UI Button States:");
1612    ///         for (button_name, enabled) in buttons {
1613    ///             let status = if enabled { "ENABLED" } else { "DISABLED" };
1614    ///             println!("  [{}] {}", status, button_name);
1615    ///         }
1616    ///         
1617    ///         // Special logic for hold/resume button
1618    ///         if capabilities.can_hold && !capabilities.can_resume {
1619    ///             println!("💡 Show 'Hold' button");
1620    ///         } else if !capabilities.can_hold && capabilities.can_resume {
1621    ///             println!("💡 Show 'Resume' button");
1622    ///         }
1623    ///     }
1624    ///     
1625    ///     Ok(())
1626    /// }
1627    /// ```
1628    /// 
1629    /// ## Capability Monitoring
1630    /// 
1631    /// ```rust
1632    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
1633    /// use tokio::time::{sleep, Duration};
1634    /// 
1635    /// async fn monitor_capability_changes() -> Result<(), Box<dyn std::error::Error>> {
1636    ///     let config = ClientConfig::new()
1637    ///         .with_sip_addr("127.0.0.1:5083".parse()?);
1638    ///     let client = ClientManager::new(config).await?;
1639    ///     client.start().await?;
1640    ///     
1641    ///     let call_id = CallId::new_v4();
1642    ///     
1643    ///     // Monitor capabilities over time (e.g., during state changes)
1644    ///     for i in 0..3 {
1645    ///         if let Ok(caps) = client.get_call_capabilities(&call_id).await {
1646    ///             println!("Check {}: Hold={}, Resume={}, Transfer={}", 
1647    ///                 i + 1, caps.can_hold, caps.can_resume, caps.can_transfer);
1648    ///         }
1649    ///         
1650    ///         sleep(Duration::from_millis(50)).await;
1651    ///     }
1652    ///     
1653    ///     Ok(())
1654    /// }
1655    /// ```
1656    /// 
1657    /// # Implementation Notes
1658    /// 
1659    /// The capabilities are determined based on the call's current state:
1660    /// 
1661    /// - **Connected calls** have the most capabilities available
1662    /// - **Ringing calls** can only be answered or rejected (hangup)
1663    /// - **Initiating calls** can only be cancelled (hangup)
1664    /// - **Terminated calls** have no available operations
1665    /// 
1666    /// The `can_resume` capability is dynamically determined by checking if
1667    /// the call is currently on hold using the call's metadata.
1668    /// 
1669    /// # Best Practices
1670    /// 
1671    /// 1. **Always check capabilities** before attempting operations
1672    /// 2. **Update UI dynamically** based on capability changes
1673    /// 3. **Handle capability changes** during call state transitions
1674    /// 4. **Provide user feedback** when operations are not available
1675    /// 5. **Cache capabilities briefly** to avoid excessive queries
1676    pub async fn get_call_capabilities(&self, call_id: &CallId) -> ClientResult<CallCapabilities> {
1677        let call_info = self.get_call(call_id).await?;
1678        
1679        let capabilities = match call_info.state {
1680            crate::call::CallState::Connected => CallCapabilities {
1681                can_hold: true,
1682                can_resume: self.is_call_on_hold(call_id).await.unwrap_or(false),
1683                can_transfer: true,
1684                can_send_dtmf: true,
1685                can_mute: true,
1686                can_hangup: true,
1687            },
1688            crate::call::CallState::Ringing | crate::call::CallState::IncomingPending => CallCapabilities {
1689                can_hold: false,
1690                can_resume: false,
1691                can_transfer: false,
1692                can_send_dtmf: false,
1693                can_mute: false,
1694                can_hangup: true, // Can reject
1695            },
1696            crate::call::CallState::Initiating | crate::call::CallState::Proceeding => CallCapabilities {
1697                can_hold: false,
1698                can_resume: false,
1699                can_transfer: false,
1700                can_send_dtmf: false,
1701                can_mute: false,
1702                can_hangup: true, // Can cancel
1703            },
1704            _ => CallCapabilities::default(), // Terminated states have no capabilities
1705        };
1706        
1707        Ok(capabilities)
1708    }
1709}