use crate::errors::{ParseError, Result};
use std::collections::HashMap;
pub fn parse_block4_fields(block4: &str) -> Result<HashMap<String, Vec<(String, usize)>>> {
let estimated_fields = block4.matches("\n:").count().max(10);
let mut field_map: HashMap<String, Vec<(String, usize)>> =
HashMap::with_capacity(estimated_fields);
let content = block4.trim();
let mut current_pos = 0;
let mut field_position = 0; let mut line_number = 1;
while current_pos < content.len() {
if current_pos > 0 && content.chars().nth(current_pos - 1) == Some('\n') {
line_number += 1;
}
if let Some(field_start) = content[current_pos..].find(':') {
let field_start = current_pos + field_start;
if let Some(tag_end) = content[field_start + 1..].find(':') {
let tag_end = field_start + 1 + tag_end;
let raw_field_tag = &content[field_start + 1..tag_end];
let field_tag = normalize_field_tag(raw_field_tag);
#[cfg(debug_assertions)]
{
if raw_field_tag.starts_with("50") {
eprintln!(
"DEBUG: parse_block4_fields - raw_field_tag='{}', normalized field_tag='{}'",
raw_field_tag, field_tag
);
}
}
let value_start = tag_end + 1;
let value_end = if let Some(next_field) = content[value_start..].find("\n:") {
value_start + next_field
} else {
content.len()
};
let field_value_slice = &content[value_start..value_end];
let trimmed_value = field_value_slice.trim();
let position_info = (line_number << 16) | (field_position & 0xFFFF);
field_map
.entry(field_tag.into_owned())
.or_default()
.push((trimmed_value.to_string(), position_info));
field_position += 1; current_pos = value_end;
} else {
return Err(ParseError::InvalidBlockStructure {
block: "4".to_string(),
message: format!(
"Malformed field tag at line {line_number}, position {current_pos}"
),
});
}
} else {
break;
}
}
Ok(field_map)
}
pub fn normalize_field_tag(raw_tag: &str) -> std::borrow::Cow<'_, str> {
use std::borrow::Cow;
if raw_tag.contains('#') {
return Cow::Borrowed(raw_tag);
}
let numeric_end = raw_tag
.find(|c: char| !c.is_ascii_digit())
.unwrap_or(raw_tag.len());
if numeric_end == raw_tag.len() {
return Cow::Borrowed(raw_tag);
}
let numeric_part = &raw_tag[..numeric_end];
let suffix = &raw_tag[numeric_end..];
match numeric_part {
"11" | "13" | "21" | "23" | "25" | "26" | "28" | "32" | "33" | "34" | "37" | "50"
| "51" | "52" | "53" | "54" | "55" | "56" | "57" | "58" | "59" | "60" | "62" | "71"
| "77" | "90" => {
Cow::Borrowed(raw_tag)
}
_ => {
if suffix.chars().all(|c| c.is_ascii_uppercase()) {
Cow::Owned(numeric_part.to_string())
} else {
Cow::Borrowed(raw_tag)
}
}
}
}
pub fn extract_base_tag(tag: &str) -> &str {
if let Some(index_pos) = tag.find('#') {
&tag[..index_pos]
} else {
tag
}
}