unilang_parser 0.35.0

Parser for Unilang CLI instruction syntax.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
//! Convenience API for CLI parameter parsing with message tail collection.
//!
//! This module provides a higher-level interface on top of [`Parser`](crate::Parser)
//! for CLI tools that need to extract `param::value` arguments and collect
//! remaining tokens as a single message string.
//!
//! # Use Case
//!
//! Many CLI tools need to parse arguments in the pattern:
//!
//! ```bash
//! command session::resume timeout::7200000 tell me about the code
//! ```
//!
//! Where named parameters (`session::resume`, `timeout::7200000`) are extracted
//! and the remaining tokens (`tell me about the code`) become a message.
//!
//! # Algorithm
//!
//! Parsing occurs in two phases:
//!
//! 1. **Parameter Phase**: Process `param::value` pairs until:
//!    - A positional argument (no `::`) is encountered, or
//!    - An unknown parameter is encountered (if `process_param` returns `Ok(false)`)
//!
//! 2. **Message Phase**: All remaining tokens are joined into a single string
//!
//! # Parameter Syntax Validation
//!
//! The parser validates parameter syntax BEFORE attempting to parse values:
//!
//! - **Valid**: `param::value` (double colon separator)
//! - **Invalid**: `param:value` (single colon - syntax error)
//! - **Invalid**: `param=value` (equals sign - syntax error)
//!
//! Any argument containing `:` but not `::` will be rejected with a clear
//! error message indicating the correct syntax. This prevents confusing
//! error messages where malformed parameters are interpreted as file paths
//! or positional arguments.
//!
//! # Example
//!
//! ```rust
//! use unilang_parser::cli_parser::{parse_cli_args, CliParams, CliParseResult};
//!
//! #[derive(Default)]
//! struct MyParams
//! {
//!   timeout: u64,
//!   verbose: bool,
//! }
//!
//! impl CliParams for MyParams
//! {
//!   fn process_param(&mut self, key: &str, value: &str) -> Result<bool, String>
//!   {
//!     match key
//!     {
//!       "timeout" => { self.timeout = value.parse().map_err(|e| format!("{}", e))?; }
//!       "verbose" => { self.verbose = value.parse().map_err(|e| format!("{}", e))?; }
//!       _ => return Ok(false), // Unknown parameter starts message phase
//!     }
//!     Ok(true)
//!   }
//!
//!   fn validate(&self) -> Result<(), String>
//!   {
//!     if self.timeout == 0
//!     {
//!       return Err("timeout must be > 0".into());
//!     }
//!     Ok(())
//!   }
//! }
//!
//! fn main() -> Result<(), String>
//! {
//!   let args = vec![
//!     "timeout::5000".to_string(),
//!     "verbose::true".to_string(),
//!     "tell".to_string(),
//!     "me".to_string(),
//!     "about".to_string(),
//!     "the".to_string(),
//!     "code".to_string(),
//!   ];
//!
//!   let result: CliParseResult<MyParams> = parse_cli_args(&args)?;
//!
//!   assert_eq!(result.params.timeout, 5000);
//!   assert!(result.params.verbose);
//!   assert_eq!(result.message, "tell me about the code");
//!
//!   Ok(())
//! }
//! ```

use alloc::collections::BTreeSet;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;

/// Result of CLI parsing with separated parameters and message.
///
/// Contains the parsed typed parameters and the remaining tokens
/// joined as a single message string.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CliParseResult<T>
{
  /// Parsed parameters from `param::value` tokens.
  pub params: T,
  /// Remaining tokens joined as message string.
  /// Empty string if no positional arguments were present.
  pub message: String,
}

/// Trait for parameter sets that can be parsed from CLI arguments.
///
/// Implementors define how to process individual `key::value` parameters
/// and optionally validate the complete parameter set.
///
/// # Return Value Semantics
///
/// The `process_param` method returns `Result<bool, String>`:
///
/// - `Ok(true)` - Parameter was recognized and processed successfully
/// - `Ok(false)` - Parameter was not recognized; starts message phase
/// - `Err(String)` - Parameter was recognized but had an invalid value
///
/// This design allows tools to choose whether unknown parameters should
/// error (return `Err`) or be treated as the start of the message (return `Ok(false)`).
pub trait CliParams: Default
{
  /// Process a single `key::value` parameter.
  ///
  /// Called for each named argument found in the input. The implementor
  /// should match on the key and parse the value accordingly.
  ///
  /// # Parameters
  ///
  /// - `key`: The parameter name (e.g., "timeout" from `timeout::5000`)
  /// - `value`: The parameter value (e.g., "5000" from `timeout::5000`)
  ///
  /// # Returns
  ///
  /// - `Ok(true)` if the parameter was recognized and processed
  /// - `Ok(false)` if the parameter was not recognized
  /// - `Err(String)` if the parameter value was invalid
  ///
  /// # Errors
  ///
  /// Returns `Err(String)` if the parameter value is invalid or cannot be parsed.
  ///
  /// # Example
  ///
  /// ```rust
  /// use unilang_parser::cli_parser::CliParams;
  ///
  /// #[derive(Default)]
  /// struct Params { timeout: u64 }
  ///
  /// impl CliParams for Params
  /// {
  ///   fn process_param(&mut self, key: &str, value: &str) -> Result<bool, String>
  ///   {
  ///     match key
  ///     {
  ///       "timeout" =>
  ///       {
  ///         self.timeout = value.parse()
  ///           .map_err(|e| format!("Invalid timeout: {}", e))?;
  ///       }
  ///       _ => return Ok(false),
  ///     }
  ///     Ok(true)
  ///   }
  /// }
  /// ```
  fn process_param(&mut self, key: &str, value: &str) -> Result<bool, String>;

  /// Validate the complete parameter set after all parameters are processed.
  ///
  /// Called once after all `process_param` calls complete. Use this to
  /// check constraints that span multiple parameters or require all
  /// parameters to be set.
  ///
  /// # Returns
  ///
  /// - `Ok(())` if validation passes
  /// - `Err(String)` with error description if validation fails
  ///
  /// # Errors
  ///
  /// Returns `Err(String)` if validation fails.
  ///
  /// # Default Implementation
  ///
  /// Returns `Ok(())` (no validation).
  fn validate(&self) -> Result<(), String>
  {
    Ok(())
  }
}

/// Parse CLI arguments into typed parameters and message tail.
///
/// Uses the unilang parser to tokenize and classify arguments, then
/// binds named arguments to the parameter type and joins positional
/// arguments into a message string.
///
/// # Parameters
///
/// - `args`: Slice of command-line arguments (typically from `std::env::args()`)
///
/// # Returns
///
/// - `Ok(CliParseResult<T>)` with parsed parameters and message
/// - `Err(String)` if parsing or validation fails
///
/// # Errors
///
/// Returns an error if:
/// - A known parameter has an invalid value
/// - Validation fails (from `CliParams::validate`)
/// - The underlying parser encounters a syntax error
///
/// # Example
///
/// ```rust
/// use unilang_parser::cli_parser::{parse_cli_args, CliParams, CliParseResult};
///
/// #[derive(Default)]
/// struct Params { dry_run: bool }
///
/// impl CliParams for Params
/// {
///   fn process_param(&mut self, key: &str, value: &str) -> Result<bool, String>
///   {
///     match key
///     {
///       "dry" =>
///       {
///         self.dry_run = value != "0";
///       }
///       _ => return Ok(false),
///     }
///     Ok(true)
///   }
/// }
///
/// let args = vec!["dry::1".to_string(), "run".to_string(), "tests".to_string()];
/// let result: CliParseResult<Params> = parse_cli_args(&args).unwrap();
///
/// assert!(result.params.dry_run);
/// assert_eq!(result.message, "run tests");
/// ```
pub fn parse_cli_args<T: CliParams>(args: &[String]) -> Result<CliParseResult<T>, String>
{
  // Handle empty input early
  if args.is_empty()
  {
    let params = T::default();
    params.validate()?;
    return Ok(CliParseResult
    {
      params,
      message: String::new(),
    });
  }

  // Initialize parameters with defaults
  let mut params = T::default();
  let mut message_parts = Vec::new();
  let mut parsing_params = true;

  // Two-phase parsing:
  // 1. Parameter phase: process key::value pairs
  // 2. Message phase: collect remaining tokens as message
  for arg in args
  {
    if parsing_params
    {
      // Fix(manual-test-2026-02-12): Validate parameter syntax before attempting to parse
      // Root cause: split_once("::") returns None for single-colon strings, causing
      // malformed parameters to be silently treated as positional arguments instead
      // of producing clear syntax errors
      // Pitfall: Parameter parsers must validate syntax BEFORE attempting to match
      // parameters. Treating malformed syntax as positional args creates confusing
      // error messages that mislead users about the actual problem
      if arg.contains(':') && !arg.contains("::")
      {
        return Err( format!(
          "Invalid parameter syntax: '{}'. Parameters must use '::' separator (e.g., 'param::value')",
          arg
        ) );
      }

      // Fix(issue-086): Detect common misuse patterns from clap/shell users and emit
      // actionable hint errors before silently treating them as positional/message tokens.
      // Root cause: Users migrating from --flag CLIs or using shell/env variable syntax
      //   produce no error — their wrong-syntax args become silent message fragments,
      //   and the downstream "unexpected argument" error points nowhere near the real cause.
      // Pitfall: These checks must fire in the PARAMETER phase only. Once message mode
      //   starts, '-' or '=' may be valid message characters (paths, operators, etc.).
      if arg.contains( '=' ) && !arg.contains( "::" )
      {
        let mut parts = arg.splitn( 2, '=' );
        let name = parts.next().unwrap_or( arg );
        let value = parts.next().unwrap_or( "value" );
        return Err( format!(
          "Invalid parameter syntax: '{}'. Use '::' separator instead of '=': e.g., '{name}::{value}'",
          arg
        ) );
      }

      if arg.starts_with( "--" )
      {
        let name = arg.trim_start_matches( '-' );
        return Err( format!(
          "Unilang does not use '--flag' syntax: '{}'. Use named parameters instead: e.g., '{name}::true'",
          arg
        ) );
      }

      if arg.starts_with( '-' )
        && arg.len() > 1
        && arg.chars().nth( 1 ).is_some_and( | c | c.is_ascii_alphabetic() )
      {
        return Err( format!(
          "Unilang does not use '-f' short flags: '{}'. Use named parameters instead: e.g., 'flag::true'",
          arg
        ) );
      }

      // Try to parse as parameter (key::value)
      if let Some((key, value)) = arg.split_once("::")
      {
        let processed = params.process_param(key, value)?;
        if !processed
        {
          // Unknown parameter - switch to message phase
          parsing_params = false;
          message_parts.push(arg.clone());
        }
      }
      else
      {
        // No :: delimiter - start message phase
        parsing_params = false;
        message_parts.push(arg.clone());
      }
    }
    else
    {
      // Already in message phase - consume all remaining args
      message_parts.push(arg.clone());
    }
  }

  // Validate the complete parameter set
  params.validate()?;

  // Join message parts
  let message = message_parts.join(" ");

  Ok(CliParseResult { params, message })
}

/// Convenience wrapper for parsing `&str` slices.
///
/// Converts the `&str` slice to `String` slice and calls [`parse_cli_args`].
///
/// # Parameters
///
/// - `args`: Slice of string slices
///
/// # Returns
///
/// Same as [`parse_cli_args`]
///
/// # Errors
///
/// Same as [`parse_cli_args`]
///
/// # Example
///
/// ```rust
/// use unilang_parser::cli_parser::{parse_cli_str_args, CliParams, CliParseResult};
///
/// #[derive(Default)]
/// struct Params { verbose: bool }
///
/// impl CliParams for Params
/// {
///   fn process_param(&mut self, key: &str, value: &str) -> Result<bool, String>
///   {
///     match key
///     {
///       "verbose" => { self.verbose = value == "true" || value == "1"; }
///       _ => return Ok(false),
///     }
///     Ok(true)
///   }
/// }
///
/// let args = &["verbose::true", "hello", "world"];
/// let result: CliParseResult<Params> = parse_cli_str_args(args).unwrap();
///
/// assert!(result.params.verbose);
/// assert_eq!(result.message, "hello world");
/// ```
pub fn parse_cli_str_args<T: CliParams>(args: &[&str]) -> Result<CliParseResult<T>, String>
{
  let owned: Vec<String> = args.iter().map(|s| (*s).to_string()).collect();
  parse_cli_args(&owned)
}

// =============================================================================
// Advanced Config-Aware Parsing API
// =============================================================================

/// Result of config-aware CLI parsing with explicit parameter tracking.
///
/// Contains the parsed parameters, message, and a set of parameter names
/// that were explicitly set by the user (vs defaulted).
#[derive(Debug, Clone)]
pub struct CliParseResultAdvanced<T>
{
  /// Parsed parameters from `param::value` tokens.
  pub params: T,
  /// Remaining tokens joined as message string.
  pub message: String,
  /// Set of canonical parameter names that were explicitly provided.
  ///
  /// Use this to distinguish between user-provided values and defaults.
  /// Aliases are normalized to their canonical names.
  pub explicit_params: BTreeSet<String>,
}

/// Extended trait for config-aware parameter parsing.
///
/// This trait extends the basic [`CliParams`] with support for:
/// - Tracking which parameters were explicitly set
/// - Applying defaults from external configuration
/// - Post-parse finalization
///
/// # Type Parameter
///
/// - `C`: The configuration type (e.g., `HashMap<String, JsonValue>`)
///
/// # Example
///
/// ```rust,ignore
/// use unilang_parser::cli_parser::{CliParamsAdvanced, CliParser};
/// use std::collections::HashMap;
///
/// #[derive(Default)]
/// struct Params
/// {
///   timeout: u64,
///   verbosity: u8,
/// }
///
/// impl CliParamsAdvanced<HashMap<String, u64>> for Params
/// {
///   fn process_param(&mut self, key: &str, value: &str)
///     -> Result<Option<&'static str>, String>
///   {
///     match key
///     {
///       "timeout" =>
///       {
///         self.timeout = value.parse()
///           .map_err(|e| format!("Invalid timeout: {e}"))?;
///         Ok(Some("timeout"))
///       }
///       "v" | "verbosity" =>
///       {
///         self.verbosity = value.parse()
///           .map_err(|e| format!("Invalid verbosity: {e}"))?;
///         Ok(Some("verbosity")) // Canonical name for alias
///       }
///       _ => Ok(None),
///     }
///   }
///
///   fn apply_defaults(
///     &mut self,
///     config: &HashMap<String, u64>,
///     explicit: &BTreeSet<String>,
///   )
///   {
///     if !explicit.contains("timeout")
///     {
///       self.timeout = *config.get("timeout").unwrap_or(&30000);
///     }
///     if !explicit.contains("verbosity")
///     {
///       self.verbosity = *config.get("verbosity").unwrap_or(&2) as u8;
///     }
///   }
/// }
/// ```
pub trait CliParamsAdvanced<C>: Default
{
  /// Process a single `key::value` parameter with tracking.
  ///
  /// Similar to [`CliParams::process_param`] but returns the canonical
  /// parameter name when successfully processed, enabling alias tracking.
  ///
  /// # Parameters
  ///
  /// - `key`: The parameter name (e.g., "timeout" or alias "t")
  /// - `value`: The parameter value
  ///
  /// # Returns
  ///
  /// - `Ok(Some("canonical_name"))` if parameter was processed
  /// - `Ok(None)` if parameter was not recognized (starts message phase)
  /// - `Err(String)` if parameter value was invalid
  ///
  /// # Errors
  ///
  /// Returns `Err(String)` if the parameter value is invalid.
  fn process_param(&mut self, key: &str, value: &str) -> Result<Option<&'static str>, String>;

  /// Apply configuration defaults after parsing.
  ///
  /// Called after all arguments are processed, before validation.
  /// Use the `explicit` set to avoid overwriting user-provided values.
  ///
  /// # Parameters
  ///
  /// - `config`: External configuration to read defaults from
  /// - `explicit`: Set of parameter names that were explicitly set
  fn apply_defaults(&mut self, config: &C, explicit: &BTreeSet<String>);

  /// Finalize parameters after defaults are applied.
  ///
  /// Called after `apply_defaults`, before validation.
  /// Use this for post-processing like smart defaults based on other fields.
  ///
  /// # Parameters
  ///
  /// - `explicit`: Set of parameter names that were explicitly set
  /// - `message`: The parsed message string
  ///
  /// Default implementation does nothing.
  fn finalize(&mut self, _explicit: &BTreeSet<String>, _message: &str) {}

  /// Validate the complete parameter set.
  ///
  /// Called after finalization. Check all constraints here.
  ///
  /// # Returns
  ///
  /// - `Ok(())` if validation passes
  /// - `Err(String)` with error description if validation fails
  ///
  /// # Errors
  ///
  /// Returns `Err(String)` if validation fails.
  ///
  /// Default implementation returns `Ok(())`.
  fn validate(&self) -> Result<(), String>
  {
    Ok(())
  }
}

/// Builder for config-aware CLI parsing.
///
/// Provides a fluent API for parsing CLI arguments with configuration support.
///
/// # Example
///
/// ```rust,ignore
/// use unilang_parser::cli_parser::CliParser;
///
/// let config = load_config();
/// let result = CliParser::new()
///   .with_config(&config)
///   .parse::<MyParams>(&args)?;
///
/// // Check if user explicitly set a parameter
/// if result.explicit_params.contains("timeout")
/// {
///   println!("User specified timeout: {}", result.params.timeout);
/// }
/// ```
pub struct CliParser<'a, C>
{
  config: Option<&'a C>,
}

impl<C> core::fmt::Debug for CliParser<'_, C>
{
  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
  {
    f.debug_struct("CliParser")
      .field("config", &self.config.is_some())
      .finish()
  }
}

impl<'a, C> CliParser<'a, C>
{
  /// Create a new CLI parser builder.
  #[inline]
  #[must_use]
  pub fn new() -> Self
  {
    Self { config: None }
  }

  /// Set the configuration for default value resolution.
  ///
  /// # Parameters
  ///
  /// - `config`: Reference to configuration data
  #[inline]
  #[must_use]
  pub fn with_config(mut self, config: &'a C) -> Self
  {
    self.config = Some(config);
    self
  }

  /// Parse CLI arguments with config support.
  ///
  /// # Parameters
  ///
  /// - `args`: Slice of command-line arguments
  ///
  /// # Returns
  ///
  /// - `Ok(CliParseResultAdvanced<T>)` with parsed parameters, message, and explicit set
  /// - `Err(String)` if parsing or validation fails
  ///
  /// # Errors
  ///
  /// Returns an error if:
  /// - A known parameter has an invalid value
  /// - Validation fails
  /// - Configuration is required but not provided
  pub fn parse<T>(self, args: &[String]) -> Result<CliParseResultAdvanced<T>, String>
  where
    T: CliParamsAdvanced<C>,
  {
    let config = self.config.ok_or_else(||
      "Configuration required for CliParamsAdvanced. Use with_config().".to_string()
    )?;

    // Initialize parameters with defaults
    let mut params = T::default();
    let mut message_parts = Vec::new();
    let mut explicit_params = BTreeSet::new();
    let mut parsing_params = true;

    // Two-phase parsing
    for arg in args
    {
      if parsing_params
      {
        // Fix(issue-086): Mirror the misuse detection from parse_cli_args so both API paths
        // produce identical errors for the same wrong input (consistency gap).
        // Root cause: CliParser::parse had no syntax validation — wrong-input args became
        //   silent message fragments, diverging from parse_cli_args which rejected them.
        // Pitfall: Checks must match parse_cli_args exactly; any drift creates confusing
        //   per-API differences that users cannot predict.
        if arg.contains( ':' ) && !arg.contains( "::" )
        {
          return Err( format!(
            "Invalid parameter syntax: '{}'. Parameters must use '::' separator (e.g., 'param::value')",
            arg
          ) );
        }

        if arg.contains( '=' ) && !arg.contains( "::" )
        {
          let mut parts = arg.splitn( 2, '=' );
          let name = parts.next().unwrap_or( arg );
          let value = parts.next().unwrap_or( "value" );
          return Err( format!(
            "Invalid parameter syntax: '{}'. Use '::' separator instead of '=': e.g., '{name}::{value}'",
            arg
          ) );
        }

        if arg.starts_with( "--" )
        {
          let name = arg.trim_start_matches( '-' );
          return Err( format!(
            "Unilang does not use '--flag' syntax: '{}'. Use named parameters instead: e.g., '{name}::true'",
            arg
          ) );
        }

        if arg.starts_with( '-' )
          && arg.len() > 1
          && arg.chars().nth( 1 ).is_some_and( | c | c.is_ascii_alphabetic() )
        {
          return Err( format!(
            "Unilang does not use '-f' short flags: '{}'. Use named parameters instead: e.g., 'flag::true'",
            arg
          ) );
        }

        if let Some((key, value)) = arg.split_once("::")
        {
          if let Some(canonical) = params.process_param(key, value)?
          {
            explicit_params.insert(canonical.to_string());
          }
          else
          {
            // Unknown parameter - switch to message phase
            parsing_params = false;
            message_parts.push(arg.clone());
          }
        }
        else
        {
          // No :: delimiter - start message phase
          parsing_params = false;
          message_parts.push(arg.clone());
        }
      }
      else
      {
        message_parts.push(arg.clone());
      }
    }

    // Join message parts
    let message = message_parts.join(" ");

    // Apply configuration defaults
    params.apply_defaults(config, &explicit_params);

    // Finalize (smart defaults based on context)
    params.finalize(&explicit_params, &message);

    // Validate
    params.validate()?;

    Ok(CliParseResultAdvanced
    {
      params,
      message,
      explicit_params,
    })
  }

  /// Convenience method for parsing `&str` slices.
  ///
  /// # Parameters
  ///
  /// - `args`: Slice of string slices
  ///
  /// # Returns
  ///
  /// Same as [`CliParser::parse`]
  ///
  /// # Errors
  ///
  /// Same as [`CliParser::parse`]
  pub fn parse_str<T>(self, args: &[&str]) -> Result<CliParseResultAdvanced<T>, String>
  where
    T: CliParamsAdvanced<C>,
  {
    let owned: Vec<String> = args.iter().map(|s| (*s).to_string()).collect();
    self.parse(&owned)
  }
}

impl<C> Default for CliParser<'_, C>
{
  fn default() -> Self
  {
    Self::new()
  }
}