swift_mt_message/parser/
swift_parser.rs

1//! # SWIFT MT Message Parser
2//!
3//! ## Purpose
4//! Comprehensive parser for SWIFT MT (Message Type) messages that converts raw SWIFT message strings
5//! into typed, structured data with full validation and field consumption tracking.
6//!
7//! ## Features
8//! - **Multi-Format Support**: Handles all supported MT message types (101, 103, 104, etc.)
9//! - **Block Structure Parsing**: Extracts and validates all 5 SWIFT message blocks
10//! - **Field Consumption Tracking**: Sequential processing of duplicate fields with position tracking
11//! - **Type-Safe Parsing**: Converts raw strings to strongly-typed field structures
12//! - **Automatic Message Detection**: Auto-detects message type from application header
13//! - **Comprehensive Validation**: Validates message structure, field formats, and business rules
14//!
15//! ## Architecture
16//! The parser follows a layered approach:
17//! 1. **Block Extraction**: Extracts blocks 1-5 from raw SWIFT message
18//! 2. **Header Parsing**: Parses blocks 1, 2, 3, and 5 into header structures
19//! 3. **Field Parsing**: Parses block 4 fields with position and variant tracking
20//! 4. **Message Construction**: Builds typed message structures with sequential field consumption
21//! 5. **Validation**: Applies format and business rule validation
22//!
23//! ## Usage Examples
24//! ```rust
25//! use swift_mt_message::parser::SwiftParser;
26//! use swift_mt_message::messages::MT103;
27//! use swift_mt_message::ParsedSwiftMessage;
28//!
29//! # fn main() -> swift_mt_message::Result<()> {
30//! # let swift_message_string = "{1:F01BANKDEFFAXXX0123456789}{2:I103BANKDEFFAXXXU3003}{4:\n:20:TXN123456\n:23B:CRED\n:32A:240315USD1000,00\n:50K:JOHN DOE\n123 MAIN ST\n:59:DE89370400440532013000\nBENEFICIARY NAME\n:71A:SHA\n-}";
31//! // Parse specific message type
32//! let mt103 = SwiftParser::parse::<MT103>(&swift_message_string)?;
33//!
34//! // Auto-detect message type
35//! let parsed_message = SwiftParser::parse_auto(&swift_message_string)?;
36//! match parsed_message {
37//!     ParsedSwiftMessage::MT103(msg) => println!("Parsed MT103: {:?}", msg),
38//!     ParsedSwiftMessage::MT202(msg) => println!("Parsed MT202: {:?}", msg),
39//!     _ => println!("Other message type"),
40//! }
41//! # Ok(())
42//! # }
43//! ```
44
45use std::collections::{HashMap, HashSet};
46
47use crate::errors::{ParseError, Result, SwiftValidationError};
48use crate::headers::{ApplicationHeader, BasicHeader, Trailer, UserHeader};
49use crate::messages::{
50    MT101, MT103, MT104, MT107, MT110, MT111, MT112, MT190, MT191, MT192, MT196, MT199, MT200,
51    MT202, MT204, MT205, MT210, MT290, MT291, MT292, MT296, MT299, MT900, MT910, MT920, MT935,
52    MT940, MT941, MT942, MT950,
53};
54use crate::swift_error_codes::t_series;
55use crate::{ParsedSwiftMessage, SwiftMessage, SwiftMessageBody};
56
57/// Parsing context that flows through the parsing pipeline
58#[derive(Debug, Clone)]
59pub struct ParsingContext {
60    /// Current field being parsed
61    pub current_field: Option<String>,
62    /// Current component being parsed
63    pub current_component: Option<String>,
64    /// Message type
65    pub message_type: String,
66    /// Original message for context
67    pub original_message: String,
68}
69
70impl ParsingContext {
71    /// Create a new parsing context
72    pub fn new(message_type: String, original_message: String) -> Self {
73        Self {
74            current_field: None,
75            current_component: None,
76            message_type,
77            original_message,
78        }
79    }
80
81    /// Create a context with field information
82    pub fn with_field(&self, field: String) -> Self {
83        let mut ctx = self.clone();
84        ctx.current_field = Some(field);
85        ctx.current_component = None;
86        ctx
87    }
88
89    /// Create a context with component information
90    pub fn with_component(&self, component: String) -> Self {
91        let mut ctx = self.clone();
92        ctx.current_component = Some(component);
93        ctx
94    }
95}
96
97/// Field consumption tracker for sequential processing of duplicate fields
98///
99/// ## Purpose
100/// Ensures that when a message contains multiple instances of the same field (e.g., multiple :50: fields),
101/// they are consumed sequentially in the order they appear in the original message. This is critical
102/// for messages like MT101 where sequence matters.
103///
104/// ## Implementation
105/// - Tracks consumed field indices by tag
106/// - Provides next available field value for sequential consumption
107/// - Maintains message order integrity during field processing
108///
109/// ## Example
110/// ```rust
111/// use swift_mt_message::parser::FieldConsumptionTracker;
112///
113/// let mut tracker = FieldConsumptionTracker::new();
114/// // Field "50" has values at positions [5, 15, 25] in message
115/// let field_values = vec![
116///     ("value1".to_string(), 5),
117///     ("value2".to_string(), 15),
118///     ("value3".to_string(), 25),
119/// ];
120/// let (value1, pos1) = tracker.get_next_available("50", &field_values).unwrap();
121/// tracker.mark_consumed("50", pos1);
122/// let (value2, pos2) = tracker.get_next_available("50", &field_values).unwrap();
123/// // Ensures value2 is from position 15, not 5 or 25
124/// ```
125#[derive(Debug, Clone)]
126pub struct FieldConsumptionTracker {
127    /// Maps field tags to sets of consumed position indices
128    consumed_indices: HashMap<String, HashSet<usize>>,
129}
130
131impl Default for FieldConsumptionTracker {
132    fn default() -> Self {
133        Self::new()
134    }
135}
136
137impl FieldConsumptionTracker {
138    /// Create a new consumption tracker
139    pub fn new() -> Self {
140        Self {
141            consumed_indices: HashMap::new(),
142        }
143    }
144
145    /// Mark a field value at a specific position as consumed
146    pub fn mark_consumed(&mut self, tag: &str, index: usize) {
147        // Avoid allocation when the key already exists
148        use std::collections::hash_map::Entry;
149        match self.consumed_indices.entry(tag.to_string()) {
150            Entry::Occupied(mut e) => {
151                e.get_mut().insert(index);
152            }
153            Entry::Vacant(e) => {
154                let mut set = HashSet::new();
155                set.insert(index);
156                e.insert(set);
157            }
158        }
159    }
160
161    /// Get the next available (unconsumed) field value for a tag
162    pub fn get_next_available<'a>(
163        &self,
164        tag: &str,
165        values: &'a [(String, usize)],
166    ) -> Option<(&'a str, usize)> {
167        let consumed_set = self.consumed_indices.get(tag);
168
169        // Find first unconsumed value in original message order
170        values
171            .iter()
172            .find(|(_, pos)| consumed_set.is_none_or(|set| !set.contains(pos)))
173            .map(|(value, pos)| (value.as_str(), *pos))
174    }
175}
176
177/// Field routing strategy for numbered field tags
178#[derive(Debug, Clone)]
179enum FieldRoutingStrategy {
180    /// For Field50InstructingParty - typically uses C, L variants
181    InstructingParty,
182    /// For Field50Creditor - typically uses A, F, K variants
183    CreditorParty,
184}
185
186impl FieldRoutingStrategy {
187    /// Get preferred variants for this routing strategy
188    fn get_preferred_variants(&self, base_tag: &str) -> Option<Vec<&'static str>> {
189        match (self, base_tag) {
190            (FieldRoutingStrategy::InstructingParty, "50") => Some(vec!["C", "L"]),
191            (FieldRoutingStrategy::CreditorParty, "50") => Some(vec!["A", "F", "K"]),
192            _ => None,
193        }
194    }
195}
196
197/// Apply intelligent routing strategy for Field 50 numbered fields
198///
199/// This function implements a smart routing algorithm for Field 50 variants when dealing
200/// with numbered field tags (50#1, 50#2). The strategy is:
201///
202/// 1. First call (no Field 50 consumed yet) -> Prefer C, L variants (InstructingParty)
203/// 2. Second call (some Field 50 already consumed) -> Prefer A, F, K variants (Creditor)
204///
205/// This matches the typical SWIFT message pattern where:
206/// - field_50_instructing (50#1) typically uses 50C or 50L
207/// - field_50_creditor (50#2) typically uses 50A, 50F, or 50K
208fn apply_field50_routing_strategy<'a>(
209    mut candidates: Vec<(&'a String, &'a Vec<(String, usize)>)>,
210    tracker: &FieldConsumptionTracker,
211    base_tag: &str,
212) -> Vec<(&'a String, &'a Vec<(String, usize)>)> {
213    // Check how many Field 50 variants have been consumed already
214    let consumed_field50_count = candidates
215        .iter()
216        .filter(|(tag, _)| {
217            tracker
218                .consumed_indices
219                .get(*tag)
220                .is_some_and(|set| !set.is_empty())
221        })
222        .count();
223
224    #[cfg(debug_assertions)]
225    eprintln!(
226        "DEBUG: Field50 routing strategy - consumed_count={}, candidates={:?}",
227        consumed_field50_count,
228        candidates
229            .iter()
230            .map(|(tag, _)| tag.as_str())
231            .collect::<Vec<_>>()
232    );
233
234    // Determine routing strategy based on consumption history
235    let strategy = if consumed_field50_count == 0 {
236        // First Field 50 access -> prefer InstructingParty variants (C, L)
237        FieldRoutingStrategy::InstructingParty
238    } else {
239        // Subsequent Field 50 access -> prefer Creditor variants (A, F, K)
240        FieldRoutingStrategy::CreditorParty
241    };
242
243    if let Some(preferred_variants) = strategy.get_preferred_variants(base_tag) {
244        // Reorder candidates to prioritize preferred variants
245        candidates.sort_by_key(|(tag, _)| {
246            let variant_char = tag.chars().last().unwrap_or(' ');
247            let variant_str = variant_char.to_string();
248
249            // Check if this variant is in the preferred list
250            let is_preferred = preferred_variants.contains(&variant_str.as_str());
251
252            if is_preferred {
253                0 // High priority
254            } else {
255                1 // Lower priority
256            }
257        });
258
259        #[cfg(debug_assertions)]
260        eprintln!(
261            "DEBUG: Field50 routing - strategy={:?}, preferred_variants={:?}, reordered_candidates={:?}",
262            strategy,
263            preferred_variants,
264            candidates
265                .iter()
266                .map(|(tag, _)| tag.as_str())
267                .collect::<Vec<_>>()
268        );
269    }
270
271    candidates
272}
273
274/// Find field values for numbered tags with intelligent routing
275///
276/// ## Purpose
277/// Special handling for numbered field tags (e.g., "50#1", "50#2") that require
278/// intelligent routing based on the specific numbered tag and its variant constraints.
279///
280/// ## Parameters
281/// - `fields`: HashMap of all parsed fields with position tracking
282/// - `base_tag`: Base field tag (e.g., "50", "59")
283/// - `tracker`: Mutable reference to consumption tracker for sequential processing
284/// - `valid_variants`: Optional list of valid variant letters specific to this numbered field
285/// - `numbered_tag`: The full numbered tag (e.g., "50#1", "50#2") for routing context
286///
287/// ## Returns
288/// `Option<(field_value, variant, position)>`
289pub fn find_field_with_variant_sequential_numbered(
290    fields: &HashMap<String, Vec<(String, usize)>>,
291    base_tag: &str,
292    tracker: &mut FieldConsumptionTracker,
293    valid_variants: Option<Vec<&str>>,
294    _numbered_tag: &str,
295) -> Option<(String, Option<String>, usize)> {
296    #[cfg(debug_assertions)]
297    eprintln!(
298        "DEBUG: find_field_with_variant_sequential_numbered for tag={}, base={}, variants={:?}",
299        _numbered_tag, base_tag, valid_variants
300    );
301
302    // Use the variant constraints for this specific numbered field
303    // This ensures that 50#1 uses its specific variants (e.g., C, L)
304    // and 50#2 uses its specific variants (e.g., A, F, K)
305    find_field_with_variant_sequential_constrained(
306        fields,
307        base_tag,
308        tracker,
309        valid_variants.as_deref(),
310    )
311}
312
313/// Find field values by base tag with sequential consumption tracking and variant constraints
314///
315/// ## Purpose
316/// Enhanced version of find_field_with_variant_sequential that accepts a list of valid variants
317/// to constrain the search. This is crucial for numbered field tags like "50#1" and "50#2"
318/// where different positions accept different variants.
319///
320/// ## Parameters
321/// - `fields`: HashMap of all parsed fields with position tracking
322/// - `base_tag`: Base field tag (e.g., "50", "59")
323/// - `tracker`: Mutable reference to consumption tracker for sequential processing
324/// - `valid_variants`: Optional list of valid variant letters (e.g., ["C", "L"] for Field50InstructingParty)
325///
326/// ## Returns
327/// `Option<(field_value, variant, position)>` where:
328/// - `field_value`: The actual field content
329/// - `variant`: Optional variant letter (A, F, K, etc.) for enum fields
330/// - `position`: Original position in the message for ordering
331pub fn find_field_with_variant_sequential_constrained(
332    fields: &HashMap<String, Vec<(String, usize)>>,
333    base_tag: &str,
334    tracker: &mut FieldConsumptionTracker,
335    valid_variants: Option<&[&str]>,
336) -> Option<(String, Option<String>, usize)> {
337    #[cfg(debug_assertions)]
338    {
339        eprintln!(
340            "DEBUG: find_field_with_variant called for base_tag={}, valid_variants={:?}",
341            base_tag, valid_variants
342        );
343        eprintln!(
344            "  Available fields: {:?}",
345            fields.keys().collect::<Vec<_>>()
346        );
347
348        // Special debug for numbered field tags (50#1, 50#2, etc.)
349        if base_tag == "50" {
350            eprintln!(
351                "DEBUG: Field50 routing - base_tag={}, constraints={:?}",
352                base_tag, valid_variants
353            );
354            eprintln!(
355                "DEBUG: Available Field50 variants: {:?}",
356                fields
357                    .keys()
358                    .filter(|k| k.starts_with("50") && k.len() == 3)
359                    .collect::<Vec<_>>()
360            );
361        }
362    }
363
364    // First try to find exact match (for non-variant fields)
365    if let Some(values) = fields.get(base_tag)
366        && let Some((value, pos)) = tracker.get_next_available(base_tag, values)
367    {
368        #[cfg(debug_assertions)]
369        {
370            eprintln!(
371                "DEBUG: Found exact match for base_tag={}, marking as consumed at pos={}",
372                base_tag, pos
373            );
374            if base_tag == "90D" || base_tag == "86" {
375                eprintln!(
376                    "DEBUG: Returning value for {}: '{}'",
377                    base_tag,
378                    if value.len() > 50 {
379                        &value[..50]
380                    } else {
381                        value
382                    }
383                );
384            }
385        }
386        tracker.mark_consumed(base_tag, pos);
387        return Some((value.to_string(), None, pos));
388    }
389
390    // For enum fields, look for variant tags (50A, 50F, 50K, etc.)
391    // Sort tags by position to ensure we process them in order
392    let mut variant_candidates: Vec<(&String, &Vec<(String, usize)>)> = fields
393        .iter()
394        .filter(|(tag, _)| {
395            tag.starts_with(base_tag)
396                && tag.len() == base_tag.len() + 1
397                && tag
398                    .chars()
399                    .last()
400                    .is_some_and(|c| c.is_ascii_alphabetic() && c.is_ascii_uppercase())
401        })
402        .collect();
403
404    // Implement intelligent routing for numbered field tags
405    if base_tag == "50" && valid_variants.is_some() {
406        // Apply numbered field routing strategy for Field 50
407        variant_candidates = apply_field50_routing_strategy(variant_candidates, tracker, base_tag);
408    }
409
410    // Sort by the minimum unconsumed position in each tag's values
411    variant_candidates.sort_by_key(|(tag, values)| {
412        values
413            .iter()
414            .filter(|(_, pos)| {
415                tracker
416                    .consumed_indices
417                    .get(*tag)
418                    .is_none_or(|set| !set.contains(pos))
419            })
420            .map(|(_, pos)| *pos)
421            .min()
422            .unwrap_or(usize::MAX)
423    });
424
425    for (tag, values) in variant_candidates {
426        let variant_char = tag.chars().last().unwrap();
427        let variant_str = variant_char.to_string();
428
429        // Check if this variant is allowed (if constraints are provided)
430        if let Some(valid) = valid_variants
431            && !valid.contains(&variant_str.as_str())
432        {
433            #[cfg(debug_assertions)]
434            eprintln!(
435                "DEBUG: Skipping field {} with variant {} - not in valid variants {:?}",
436                tag, variant_str, valid
437            );
438            continue; // Skip variants that aren't in the valid list
439        }
440
441        if let Some((value, pos)) = tracker.get_next_available(tag, values) {
442            #[cfg(debug_assertions)]
443            eprintln!(
444                "DEBUG: Found field {} with variant {} for base tag {}",
445                tag, variant_str, base_tag
446            );
447            tracker.mark_consumed(tag, pos);
448            return Some((value.to_string(), Some(variant_str), pos));
449        }
450    }
451
452    None
453}
454
455/// Main parser for SWIFT MT messages
456///
457/// ## Purpose
458/// Primary parsing engine that converts raw SWIFT message strings into typed, validated message structures.
459/// Handles all aspects of SWIFT message processing including block extraction, header parsing, field parsing,
460/// and type-safe message construction.
461///
462/// ## Capabilities
463/// - **Multi-Message Support**: Parses all 24 supported MT message types
464/// - **Flexible Parsing**: Both type-specific and auto-detection parsing modes
465/// - **Robust Error Handling**: Comprehensive error reporting for malformed messages
466/// - **Field Validation**: Format validation and business rule checking
467/// - **Position Tracking**: Maintains field order for sequential processing requirements
468///
469/// ## Parsing Process
470/// 1. **Block Extraction**: Identifies and extracts SWIFT blocks 1-5
471/// 2. **Header Validation**: Parses and validates basic, application, user headers and trailer
472/// 3. **Message Type Detection**: Determines message type from application header
473/// 4. **Field Processing**: Parses block 4 fields with position and variant tracking
474/// 5. **Type Construction**: Builds strongly-typed message structures
475/// 6. **Validation**: Applies format and business rule validation
476///
477/// ## Thread Safety
478/// SwiftParser is stateless and thread-safe. All methods are static and can be called
479/// concurrently from multiple threads.
480pub struct SwiftParser {}
481
482impl Default for SwiftParser {
483    fn default() -> Self {
484        Self::new()
485    }
486}
487
488impl SwiftParser {
489    /// Create a new parser with default configuration
490    pub fn new() -> Self {
491        Self {}
492    }
493
494    /// Parse a message and return ParseResult with all errors collected
495    pub fn parse_with_errors<T: SwiftMessageBody>(
496        &self,
497        raw_message: &str,
498    ) -> Result<crate::errors::ParseResult<SwiftMessage<T>>> {
499        let block1 = Self::extract_block(raw_message, 1)?;
500        let block2 = Self::extract_block(raw_message, 2)?;
501        let block3 = Self::extract_block(raw_message, 3)?;
502        let block4 = Self::extract_block(raw_message, 4)?;
503        let block5 = Self::extract_block(raw_message, 5)?;
504
505        // Parse headers
506        let basic_header = BasicHeader::parse(&block1.unwrap_or_default())?;
507        let application_header = ApplicationHeader::parse(&block2.unwrap_or_default())?;
508        let user_header = block3.map(|b| UserHeader::parse(&b)).transpose()?;
509        let trailer = block5.map(|b| Trailer::parse(&b)).transpose()?;
510
511        // Extract message type from application header
512        let message_type = application_header.message_type().to_string();
513
514        // Validate message type matches expected type using SWIFT error codes
515        if message_type != T::message_type() {
516            return Err(ParseError::SwiftValidation(Box::new(
517                SwiftValidationError::format_error(
518                    t_series::T03,
519                    "MESSAGE_TYPE",
520                    &message_type,
521                    T::message_type(),
522                    &format!(
523                        "Message type mismatch: expected {}, got {}",
524                        T::message_type(),
525                        message_type
526                    ),
527                ),
528            )));
529        }
530
531        // Parse block 4 using MessageParser-based approach
532        let fields = T::parse_from_block4(&block4.unwrap_or_default())?;
533
534        Ok(crate::errors::ParseResult::Success(SwiftMessage {
535            basic_header,
536            application_header,
537            user_header,
538            trailer,
539            message_type,
540            fields,
541        }))
542    }
543    /// Parse a raw SWIFT message string into a typed message (static method for backward compatibility)
544    pub fn parse<T: SwiftMessageBody>(raw_message: &str) -> Result<SwiftMessage<T>> {
545        Self::new().parse_message(raw_message)
546    }
547
548    /// Parse a raw SWIFT message string into a typed message with configuration support
549    pub fn parse_message<T: SwiftMessageBody>(&self, raw_message: &str) -> Result<SwiftMessage<T>> {
550        let block1 = Self::extract_block(raw_message, 1)?;
551        let block2 = Self::extract_block(raw_message, 2)?;
552        let block3 = Self::extract_block(raw_message, 3)?;
553        let block4 = Self::extract_block(raw_message, 4)?;
554        let block5 = Self::extract_block(raw_message, 5)?;
555
556        // Parse headers
557        let basic_header = BasicHeader::parse(&block1.unwrap_or_default())?;
558        let application_header = ApplicationHeader::parse(&block2.unwrap_or_default())?;
559        let user_header = block3.map(|b| UserHeader::parse(&b)).transpose()?;
560        let trailer = block5.map(|b| Trailer::parse(&b)).transpose()?;
561
562        // Extract message type from application header
563        let message_type = application_header.message_type().to_string();
564
565        // Validate message type matches expected type using SWIFT error codes
566        if message_type != T::message_type() {
567            return Err(ParseError::SwiftValidation(Box::new(
568                SwiftValidationError::format_error(
569                    t_series::T03,
570                    "MESSAGE_TYPE",
571                    &message_type,
572                    T::message_type(),
573                    &format!(
574                        "Message type mismatch: expected {}, got {}",
575                        T::message_type(),
576                        message_type
577                    ),
578                ),
579            )));
580        }
581
582        // Parse block 4 using MessageParser-based approach
583        let fields = T::parse_from_block4(&block4.unwrap_or_default())?;
584
585        Ok(SwiftMessage {
586            basic_header,
587            application_header,
588            user_header,
589            trailer,
590            message_type,
591            fields,
592        })
593    }
594
595    /// Parse a raw SWIFT message string with automatic message type detection (static method for backward compatibility)
596    pub fn parse_auto(raw_message: &str) -> Result<ParsedSwiftMessage> {
597        Self::new().parse_message_auto(raw_message)
598    }
599
600    /// Parse a raw SWIFT message string with automatic message type detection and configuration support
601    pub fn parse_message_auto(&self, raw_message: &str) -> Result<ParsedSwiftMessage> {
602        // First, extract blocks to get the message type
603        let block2 = Self::extract_block(raw_message, 2)?;
604
605        // Parse application header to get message type
606        let application_header = ApplicationHeader::parse(&block2.unwrap_or_default())?;
607        let message_type = application_header.message_type();
608
609        // Route to appropriate parser based on message type
610        match message_type {
611            "101" => {
612                let parsed = self.parse_message::<MT101>(raw_message)?;
613                Ok(ParsedSwiftMessage::MT101(Box::new(parsed)))
614            }
615            "103" => {
616                let parsed = self.parse_message::<MT103>(raw_message)?;
617                Ok(ParsedSwiftMessage::MT103(Box::new(parsed)))
618            }
619            "104" => {
620                let parsed = self.parse_message::<MT104>(raw_message)?;
621                Ok(ParsedSwiftMessage::MT104(Box::new(parsed)))
622            }
623            "107" => {
624                let parsed = self.parse_message::<MT107>(raw_message)?;
625                Ok(ParsedSwiftMessage::MT107(Box::new(parsed)))
626            }
627            "110" => {
628                let parsed = self.parse_message::<MT110>(raw_message)?;
629                Ok(ParsedSwiftMessage::MT110(Box::new(parsed)))
630            }
631            "111" => {
632                let parsed = self.parse_message::<MT111>(raw_message)?;
633                Ok(ParsedSwiftMessage::MT111(Box::new(parsed)))
634            }
635            "112" => {
636                let parsed = self.parse_message::<MT112>(raw_message)?;
637                Ok(ParsedSwiftMessage::MT112(Box::new(parsed)))
638            }
639            "190" => {
640                let parsed = self.parse_message::<MT190>(raw_message)?;
641                Ok(ParsedSwiftMessage::MT190(Box::new(parsed)))
642            }
643            "191" => {
644                let parsed = self.parse_message::<MT191>(raw_message)?;
645                Ok(ParsedSwiftMessage::MT191(Box::new(parsed)))
646            }
647            "200" => {
648                let parsed = self.parse_message::<MT200>(raw_message)?;
649                Ok(ParsedSwiftMessage::MT200(Box::new(parsed)))
650            }
651            "202" => {
652                let parsed = self.parse_message::<MT202>(raw_message)?;
653                Ok(ParsedSwiftMessage::MT202(Box::new(parsed)))
654            }
655            "204" => {
656                let parsed = self.parse_message::<MT204>(raw_message)?;
657                Ok(ParsedSwiftMessage::MT204(Box::new(parsed)))
658            }
659            "205" => {
660                let parsed = self.parse_message::<MT205>(raw_message)?;
661                Ok(ParsedSwiftMessage::MT205(Box::new(parsed)))
662            }
663            "210" => {
664                let parsed = self.parse_message::<MT210>(raw_message)?;
665                Ok(ParsedSwiftMessage::MT210(Box::new(parsed)))
666            }
667            "290" => {
668                let parsed = self.parse_message::<MT290>(raw_message)?;
669                Ok(ParsedSwiftMessage::MT290(Box::new(parsed)))
670            }
671            "291" => {
672                let parsed = self.parse_message::<MT291>(raw_message)?;
673                Ok(ParsedSwiftMessage::MT291(Box::new(parsed)))
674            }
675            "900" => {
676                let parsed = self.parse_message::<MT900>(raw_message)?;
677                Ok(ParsedSwiftMessage::MT900(Box::new(parsed)))
678            }
679            "910" => {
680                let parsed = self.parse_message::<MT910>(raw_message)?;
681                Ok(ParsedSwiftMessage::MT910(Box::new(parsed)))
682            }
683            "920" => {
684                let parsed = self.parse_message::<MT920>(raw_message)?;
685                Ok(ParsedSwiftMessage::MT920(Box::new(parsed)))
686            }
687            "935" => {
688                let parsed = self.parse_message::<MT935>(raw_message)?;
689                Ok(ParsedSwiftMessage::MT935(Box::new(parsed)))
690            }
691            "940" => {
692                let parsed = self.parse_message::<MT940>(raw_message)?;
693                Ok(ParsedSwiftMessage::MT940(Box::new(parsed)))
694            }
695            "941" => {
696                let parsed = self.parse_message::<MT941>(raw_message)?;
697                Ok(ParsedSwiftMessage::MT941(Box::new(parsed)))
698            }
699            "942" => {
700                let parsed = self.parse_message::<MT942>(raw_message)?;
701                Ok(ParsedSwiftMessage::MT942(Box::new(parsed)))
702            }
703            "950" => {
704                let parsed = self.parse_message::<MT950>(raw_message)?;
705                Ok(ParsedSwiftMessage::MT950(Box::new(parsed)))
706            }
707            "192" => {
708                let parsed = self.parse_message::<MT192>(raw_message)?;
709                Ok(ParsedSwiftMessage::MT192(Box::new(parsed)))
710            }
711            "196" => {
712                let parsed = self.parse_message::<MT196>(raw_message)?;
713                Ok(ParsedSwiftMessage::MT196(Box::new(parsed)))
714            }
715            "292" => {
716                let parsed = self.parse_message::<MT292>(raw_message)?;
717                Ok(ParsedSwiftMessage::MT292(Box::new(parsed)))
718            }
719            "296" => {
720                let parsed = self.parse_message::<MT296>(raw_message)?;
721                Ok(ParsedSwiftMessage::MT296(Box::new(parsed)))
722            }
723            "199" => {
724                let parsed = self.parse_message::<MT199>(raw_message)?;
725                Ok(ParsedSwiftMessage::MT199(Box::new(parsed)))
726            }
727            "299" => {
728                let parsed = self.parse_message::<MT299>(raw_message)?;
729                Ok(ParsedSwiftMessage::MT299(Box::new(parsed)))
730            }
731            _ => Err(ParseError::UnsupportedMessageType {
732                message_type: message_type.to_string(),
733            }),
734        }
735    }
736
737    /// Extract a specific message block from raw SWIFT message with SWIFT validation
738    pub fn extract_block(raw_message: &str, block_index: u8) -> Result<Option<String>> {
739        // Validate block index using SWIFT error codes
740        if !(1..=5).contains(&block_index) {
741            return Err(ParseError::SwiftValidation(Box::new(
742                crate::errors::SwiftValidationError::format_error(
743                    crate::swift_error_codes::t_series::T01,
744                    "BLOCK_INDEX",
745                    &block_index.to_string(),
746                    "1-5",
747                    &format!("Invalid block index: {block_index}"),
748                ),
749            )));
750        }
751
752        let block_marker = format!("{{{block_index}:");
753
754        if let Some(start) = raw_message.find(&block_marker) {
755            let content_start = start + block_marker.len();
756
757            match block_index {
758                1 | 2 => {
759                    // Blocks 1 and 2 end with simple closing brace (no nested content)
760                    if let Some(end) = raw_message[start..].find('}') {
761                        let end = start + end;
762                        Ok(Some(raw_message[content_start..end].to_string()))
763                    } else {
764                        Ok(None)
765                    }
766                }
767                3 | 5 => {
768                    // Blocks 3 and 5 may have nested braces (e.g., {103:EBA} or {CHK:...})
769                    if let Some(end) = Self::find_matching_brace(&raw_message[start..]) {
770                        let end = start + end;
771                        Ok(Some(raw_message[content_start..end].to_string()))
772                    } else {
773                        Ok(None)
774                    }
775                }
776                4 => {
777                    // Block 4 ends with "-}"
778                    if let Some(end) = raw_message[start..].find("-}") {
779                        let end = start + end;
780                        Ok(Some(raw_message[content_start..end].to_string()))
781                    } else {
782                        Ok(None)
783                    }
784                }
785                _ => Err(ParseError::SwiftValidation(Box::new(
786                    crate::errors::SwiftValidationError::format_error(
787                        crate::swift_error_codes::t_series::T02,
788                        "BLOCK",
789                        &block_index.to_string(),
790                        "1-5",
791                        &format!("Invalid block index: {block_index}"),
792                    ),
793                ))),
794            }
795        } else {
796            Ok(None)
797        }
798    }
799
800    /// Find the matching closing brace for a block that starts with an opening brace
801    /// Handles nested braces correctly
802    fn find_matching_brace(text: &str) -> Option<usize> {
803        let mut chars = text.char_indices();
804
805        // Skip the first character (should be '{')
806        let mut brace_count = if let Some((_, '{')) = chars.next() {
807            1
808        } else {
809            return None;
810        };
811
812        for (i, ch) in chars {
813            match ch {
814                '{' => brace_count += 1,
815                '}' => {
816                    brace_count -= 1;
817                    if brace_count == 0 {
818                        return Some(i);
819                    }
820                }
821                _ => {}
822            }
823        }
824
825        None
826    }
827}
828
829/// Reconstruct a Block 4 string from a field map
830///
831/// Converts a HashMap of fields (with position tracking) into a Block 4 string
832/// that can be parsed using parse_from_block4()
833fn reconstruct_block4_from_fields(fields: &HashMap<String, Vec<(String, usize)>>) -> String {
834    // Flatten and sort all fields by position
835    let mut all_fields: Vec<(&str, &str, usize)> = Vec::new();
836    for (tag, values) in fields {
837        for (value, pos) in values {
838            all_fields.push((tag, value, *pos));
839        }
840    }
841    all_fields.sort_by_key(|(_, _, pos)| *pos);
842
843    // Build the Block 4 string
844    let mut result = String::new();
845    for (tag, value, _) in all_fields {
846        // Remove the tag prefix if it exists in the value
847        let clean_value = if value.starts_with(&format!(":{tag}:")) {
848            &value[tag.len() + 2..]
849        } else if let Some(stripped) = value.strip_prefix(':') {
850            // Handle variant fields like :50K:, :53A:, etc.
851            if let Some(second_colon) = stripped.find(':') {
852                &value[second_colon + 2..]
853            } else {
854                value
855            }
856        } else {
857            value
858        };
859
860        result.push_str(&format!(":{tag}:{clean_value}\n"));
861    }
862
863    result
864}
865
866/// Parse sequence fields (e.g., transactions in MT101, MT104)
867///
868/// This function identifies sequence boundaries and parses each sequence into the target type.
869/// Sequences typically start with a mandatory field (like Field 21) that marks the beginning
870/// of each repetition.
871pub fn parse_sequences<T>(
872    fields: &HashMap<String, Vec<(String, usize)>>,
873    tracker: &mut FieldConsumptionTracker,
874) -> Result<Vec<T>>
875where
876    T: crate::SwiftMessageBody,
877{
878    // Use the enhanced sequence parser for messages that have complex sequences
879    let message_type = std::any::type_name::<T>();
880
881    if message_type.contains("MT104Transaction") {
882        // For MT104, we need to handle the three-sequence structure
883        use crate::parser::sequence_parser::{get_sequence_config, split_into_sequences};
884
885        let config = get_sequence_config("MT104");
886        let parsed_sequences = split_into_sequences(fields, &config)?;
887
888        // Parse only sequence B (transactions)
889        return parse_sequence_b_items::<T>(&parsed_sequences.sequence_b, tracker);
890    }
891
892    if message_type.contains("MT110Cheque") {
893        // MT110 has repetitive cheque sequences starting with field 21
894        use crate::parser::sequence_parser::{get_sequence_config, split_into_sequences};
895
896        let config = get_sequence_config("MT110");
897        let parsed_sequences = split_into_sequences(fields, &config)?;
898
899        // Parse only sequence B (cheques)
900        return parse_sequence_b_items::<T>(&parsed_sequences.sequence_b, tracker);
901    }
902
903    if message_type.contains("MT204Transaction") {
904        // MT204 has a special structure where fields are grouped by type, not by transaction
905        // We need to reconstruct transactions from the grouped fields
906
907        // Count how many transactions we have (based on field 20 occurrences, excluding the first one)
908        let field_20_count = fields.get("20").map(|v| v.len()).unwrap_or(0);
909        if field_20_count <= 1 {
910            return Ok(Vec::new()); // No transactions if only one or zero field 20s
911        }
912
913        let num_transactions = field_20_count - 1; // First field 20 is for sequence A
914
915        // Build transactions by distributing fields
916        let mut transactions = Vec::new();
917
918        for i in 0..num_transactions {
919            let mut tx_fields = HashMap::new();
920
921            // Get field 20 (skip the first one which is for sequence A)
922            if let Some(field_20_values) = fields.get("20")
923                && i + 1 < field_20_values.len()
924            {
925                tx_fields.insert("20".to_string(), vec![field_20_values[i + 1].clone()]);
926            }
927
928            // Get field 21 if present (optional)
929            if let Some(field_21_values) = fields.get("21")
930                && i < field_21_values.len()
931            {
932                tx_fields.insert("21".to_string(), vec![field_21_values[i].clone()]);
933            }
934
935            // Get field 32B
936            if let Some(field_32b_values) = fields.get("32B")
937                && i < field_32b_values.len()
938            {
939                tx_fields.insert("32B".to_string(), vec![field_32b_values[i].clone()]);
940            }
941
942            // Get field 53 (various variants)
943            for variant in ["53", "53A", "53B", "53D"] {
944                if let Some(field_53_values) = fields.get(variant)
945                    && i < field_53_values.len()
946                {
947                    tx_fields.insert(variant.to_string(), vec![field_53_values[i].clone()]);
948                    break; // Only one variant per transaction
949                }
950            }
951
952            // Get field 72 if present (optional)
953            // MT204 structure: First Field 72 is for Sequence A, then one Field 72 per transaction in Sequence B
954            if let Some(field_72_values) = fields.get("72") {
955                // Skip the first Field 72 (belongs to Sequence A), then take one per transaction
956                if i + 1 < field_72_values.len() {
957                    tx_fields.insert("72".to_string(), vec![field_72_values[i + 1].clone()]);
958                }
959            }
960
961            // Reconstruct block4 string from fields and parse using parse_from_block4
962            let block4_str = reconstruct_block4_from_fields(&tx_fields);
963            if let Ok(transaction) = T::parse_from_block4(&block4_str) {
964                transactions.push(transaction);
965            }
966        }
967
968        return Ok(transactions);
969    }
970
971    // Get all fields sorted by position
972    let mut all_fields: Vec<(String, String, usize)> = Vec::new();
973    for (tag, values) in fields {
974        for (value, pos) in values {
975            // Only include unconsumed fields
976            if tracker
977                .consumed_indices
978                .get(tag)
979                .is_none_or(|set| !set.contains(pos))
980            {
981                #[cfg(debug_assertions)]
982                if tag.starts_with("50") {
983                    eprintln!(
984                        "DEBUG: Collecting field for sequence: tag='{}' from fields HashMap",
985                        tag
986                    );
987                }
988                all_fields.push((tag.clone(), value.clone(), *pos));
989            }
990        }
991    }
992    all_fields.sort_by_key(|(_, _, pos)| *pos);
993
994    // Determine the sequence start marker based on message type
995    let (primary_marker, secondary_marker) = if message_type.contains("MT920Sequence") {
996        ("12", None)
997    } else if message_type.contains("MT935RateChange") {
998        ("23", Some("25"))
999    } else if message_type.contains("MT940StatementLine")
1000        || message_type.contains("MT942StatementLine")
1001    {
1002        ("61", None)
1003    } else {
1004        ("21", None)
1005    };
1006
1007    let mut sequences = Vec::new();
1008    let mut current_sequence_fields: HashMap<String, Vec<(String, usize)>> = HashMap::new();
1009    let mut in_sequence = false;
1010
1011    // For MT942, fields that belong in the repeating section are only 61 and 86
1012    // Fields 90D, 90C, and other 86 occurrences after the repeating section should not be consumed
1013    let is_mt942_statement = message_type.contains("MT942StatementLine");
1014
1015    // Track whether we've seen Field 61 in the current sequence to know if we should expect Field 86
1016    let mut has_field_61_in_sequence = false;
1017    let mut has_field_86_in_sequence = false;
1018
1019    for (tag, value, pos) in all_fields {
1020        // Check if this is the start of a new sequence
1021        let is_sequence_start = (tag == primary_marker
1022            || secondary_marker.is_some_and(|m| tag == m))
1023            && !tag.ends_with("R")
1024            && !tag.ends_with("F")
1025            && !tag.ends_with("C")
1026            && !tag.ends_with("D");
1027
1028        if is_sequence_start {
1029            // If we were already in a sequence, parse the previous one
1030            if in_sequence && !current_sequence_fields.is_empty() {
1031                let block4_str = reconstruct_block4_from_fields(&current_sequence_fields);
1032                if let Ok(sequence_item) = T::parse_from_block4(&block4_str) {
1033                    sequences.push(sequence_item);
1034                }
1035                current_sequence_fields.clear();
1036            }
1037            in_sequence = true;
1038            has_field_61_in_sequence = true; // Field 61 is the sequence start marker
1039            has_field_86_in_sequence = false;
1040        }
1041
1042        // For MT942StatementLine, be smart about which fields to include
1043        let should_include_in_sequence = if is_mt942_statement && in_sequence {
1044            if tag == "61" {
1045                // New Field 61 - if we already have one in the sequence, start a new sequence
1046                if has_field_61_in_sequence && !current_sequence_fields.is_empty() {
1047                    // Complete the previous sequence
1048                    let block4_str = reconstruct_block4_from_fields(&current_sequence_fields);
1049                    if let Ok(sequence_item) = T::parse_from_block4(&block4_str) {
1050                        sequences.push(sequence_item);
1051                    }
1052                    current_sequence_fields.clear();
1053                    has_field_86_in_sequence = false;
1054                }
1055                has_field_61_in_sequence = true;
1056                true
1057            } else if tag == "86" && has_field_61_in_sequence && !has_field_86_in_sequence {
1058                // This is the Field 86 that pairs with the current Field 61
1059                has_field_86_in_sequence = true;
1060                true
1061            } else {
1062                // Any other field ends the sequence
1063                false
1064            }
1065        } else if !is_mt942_statement && in_sequence {
1066            // For non-MT942 messages, include all fields in the sequence
1067            true
1068        } else {
1069            false
1070        };
1071
1072        // Add field to current sequence if we should include it
1073        if should_include_in_sequence {
1074            #[cfg(debug_assertions)]
1075            if tag.starts_with("50") || tag.starts_with("90") || tag.starts_with("86") {
1076                eprintln!(
1077                    "DEBUG: Adding field to sequence: tag='{}', value_start='{}'",
1078                    tag,
1079                    value.lines().next().unwrap_or("")
1080                );
1081            }
1082            current_sequence_fields
1083                .entry(tag.clone())
1084                .or_default()
1085                .push((value, pos));
1086
1087            // Mark this field as consumed
1088            tracker.mark_consumed(&tag, pos);
1089        } else if is_mt942_statement && in_sequence && !should_include_in_sequence {
1090            // For MT942, if we encounter a field that shouldn't be in the sequence,
1091            // we should end the current sequence and not consume this field
1092            if !current_sequence_fields.is_empty() {
1093                let block4_str = reconstruct_block4_from_fields(&current_sequence_fields);
1094                if let Ok(sequence_item) = T::parse_from_block4(&block4_str) {
1095                    sequences.push(sequence_item);
1096                }
1097                current_sequence_fields.clear();
1098            }
1099            in_sequence = false;
1100            has_field_61_in_sequence = false;
1101            has_field_86_in_sequence = false;
1102
1103            #[cfg(debug_assertions)]
1104            eprintln!("DEBUG: Ending MT942 sequence at field: tag='{}'", tag);
1105        }
1106    }
1107
1108    // Parse the last sequence if there is one
1109    if in_sequence && !current_sequence_fields.is_empty() {
1110        let block4_str = reconstruct_block4_from_fields(&current_sequence_fields);
1111        match T::parse_from_block4(&block4_str) {
1112            Ok(sequence_item) => {
1113                sequences.push(sequence_item);
1114            }
1115            Err(_e) => {
1116                #[cfg(debug_assertions)]
1117                eprintln!("DEBUG: Failed to parse final sequence item: {_e:?}");
1118            }
1119        }
1120    }
1121
1122    Ok(sequences)
1123}
1124
1125/// Parse sequence B items from already split fields
1126fn parse_sequence_b_items<T>(
1127    fields: &HashMap<String, Vec<(String, usize)>>,
1128    tracker: &mut FieldConsumptionTracker,
1129) -> Result<Vec<T>>
1130where
1131    T: crate::SwiftMessageBody,
1132{
1133    let mut sequences = Vec::new();
1134
1135    // Get all fields sorted by position
1136    let mut all_fields: Vec<(String, String, usize)> = Vec::new();
1137    for (tag, values) in fields {
1138        for (value, pos) in values {
1139            all_fields.push((tag.clone(), value.clone(), *pos));
1140        }
1141    }
1142    all_fields.sort_by_key(|(_, _, pos)| *pos);
1143
1144    #[cfg(debug_assertions)]
1145    eprintln!(
1146        "DEBUG parse_sequence_b_items: found {} total fields to process",
1147        all_fields.len()
1148    );
1149
1150    // Determine the sequence start tag based on message type
1151    let message_type = std::any::type_name::<T>();
1152    let sequence_start_tag = if message_type.contains("MT204Transaction") {
1153        "20" // MT204 transactions start with field 20
1154    } else {
1155        "21" // Most other transactions start with field 21
1156    };
1157    let mut current_sequence_fields: HashMap<String, Vec<(String, usize)>> = HashMap::new();
1158    let mut in_sequence = false;
1159
1160    for (tag, value, pos) in all_fields {
1161        // Check if this is the start of a new sequence
1162        if tag == sequence_start_tag
1163            && !tag.ends_with("R")
1164            && !tag.ends_with("F")
1165            && !tag.ends_with("C")
1166            && !tag.ends_with("D")
1167        {
1168            #[cfg(debug_assertions)]
1169            eprintln!(
1170                "DEBUG: Found sequence start tag {} at position {}, in_sequence={}, current_fields_count={}",
1171                tag,
1172                pos,
1173                in_sequence,
1174                current_sequence_fields.len()
1175            );
1176
1177            // If we were already in a sequence, parse the previous one
1178            if in_sequence && !current_sequence_fields.is_empty() {
1179                #[cfg(debug_assertions)]
1180                eprintln!(
1181                    "DEBUG: Parsing previous sequence with {} field types",
1182                    current_sequence_fields.len()
1183                );
1184
1185                let block4_str = reconstruct_block4_from_fields(&current_sequence_fields);
1186                match T::parse_from_block4(&block4_str) {
1187                    Ok(sequence_item) => {
1188                        sequences.push(sequence_item);
1189                        #[cfg(debug_assertions)]
1190                        eprintln!("DEBUG: Successfully parsed sequence #{}", sequences.len());
1191                    }
1192                    Err(e) => {
1193                        #[cfg(debug_assertions)]
1194                        eprintln!("DEBUG: Failed to parse sequence: {}", e);
1195                    }
1196                }
1197                current_sequence_fields.clear();
1198            }
1199            in_sequence = true;
1200        }
1201
1202        // Add field to current sequence if we're in one
1203        if in_sequence {
1204            #[cfg(debug_assertions)]
1205            if tag.starts_with("50") {
1206                eprintln!(
1207                    "DEBUG: Adding field to sequence: tag='{}', value_start='{}'",
1208                    tag,
1209                    value.lines().next().unwrap_or("")
1210                );
1211            }
1212            current_sequence_fields
1213                .entry(tag.clone())
1214                .or_default()
1215                .push((value, pos));
1216
1217            // Mark this field as consumed
1218            tracker.mark_consumed(&tag, pos);
1219        }
1220    }
1221
1222    // Parse the last sequence if there is one
1223    if in_sequence && !current_sequence_fields.is_empty() {
1224        #[cfg(debug_assertions)]
1225        eprintln!(
1226            "DEBUG: Parsing final sequence with {} field types",
1227            current_sequence_fields.len()
1228        );
1229
1230        let block4_str = reconstruct_block4_from_fields(&current_sequence_fields);
1231        match T::parse_from_block4(&block4_str) {
1232            Ok(sequence_item) => {
1233                sequences.push(sequence_item);
1234                #[cfg(debug_assertions)]
1235                eprintln!(
1236                    "DEBUG: Successfully parsed final sequence #{}",
1237                    sequences.len()
1238                );
1239            }
1240            Err(_e) => {
1241                #[cfg(debug_assertions)]
1242                eprintln!("DEBUG: Failed to parse final sequence item: {_e:?}");
1243            }
1244        }
1245    }
1246
1247    #[cfg(debug_assertions)]
1248    eprintln!(
1249        "DEBUG: parse_sequence_b_items returning {} sequences",
1250        sequences.len()
1251    );
1252
1253    Ok(sequences)
1254}