#![allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransformationType {
Destructive(DestructiveReason),
Constructive(ConstructiveReason),
Neutral(NeutralReason),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DestructiveReason {
ContentDeletion,
CharacterCorruption,
SemanticChange,
DataLoss,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConstructiveReason {
ArrowDuplication, BoxExpansion, WhitespaceNormalization,
VisualAlignment, BorderCorrection, }
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(clippy::enum_variant_names)] pub enum NeutralReason {
CaseNormalization,
EncodingNormalization,
LineEndingNormalization,
WhitespaceNormalization,
}
#[derive(Debug, Clone)]
pub struct TransformationAnalysis {
pub transformations: Vec<Transformation>,
pub summary: TransformationSummary,
}
#[derive(Debug, Clone)]
pub struct Transformation {
pub transform_type: TransformationType,
pub location: Location,
pub description: String,
pub impact_score: f32, }
#[derive(Debug, Clone)]
pub struct Location {
pub line: usize,
pub col_start: usize,
pub col_end: usize,
}
#[derive(Debug, Clone)]
pub struct TransformationSummary {
pub destructive_count: usize,
pub constructive_count: usize,
pub neutral_count: usize,
pub net_quality_impact: f32, pub risk_score: f32, }
#[must_use]
pub fn analyze_transformations(input: &str, output: &str) -> TransformationAnalysis {
let input_lines: Vec<&str> = input.lines().collect();
let output_lines: Vec<&str> = output.lines().collect();
let mut transformations = Vec::new();
for (line_idx, (input_line, output_line)) in
input_lines.iter().zip(output_lines.iter()).enumerate()
{
analyze_line_transformations(line_idx, input_line, output_line, &mut transformations);
}
let max_lines = input_lines.len().max(output_lines.len());
if input_lines.len() != output_lines.len() {
transformations.push(Transformation {
transform_type: if output_lines.len() > input_lines.len() {
TransformationType::Constructive(ConstructiveReason::VisualAlignment)
} else {
TransformationType::Destructive(DestructiveReason::DataLoss)
},
location: Location {
line: max_lines,
col_start: 0,
col_end: 0,
},
description: format!(
"Line count changed: {} → {}",
input_lines.len(),
output_lines.len()
),
impact_score: if output_lines.len() > input_lines.len() {
0.1
} else {
-0.5
},
});
}
let summary = calculate_summary(&transformations);
TransformationAnalysis {
transformations,
summary,
}
}
fn analyze_line_transformations(
line_idx: usize,
input_line: &str,
output_line: &str,
transformations: &mut Vec<Transformation>,
) {
let input_chars: Vec<char> = input_line.chars().collect();
let output_chars: Vec<char> = output_line.chars().collect();
let mut i = 0;
let mut j = 0;
while i < input_chars.len() && j < output_chars.len() {
if input_chars[i] == output_chars[j] {
i += 1;
j += 1;
} else {
analyze_character_difference(
line_idx,
i,
j,
&input_chars,
&output_chars,
transformations,
);
while i < input_chars.len()
&& j < output_chars.len()
&& input_chars[i] != output_chars[j]
{
i += 1;
j += 1;
}
}
}
if i < input_chars.len() || j < output_chars.len() {
transformations.push(Transformation {
transform_type: TransformationType::Destructive(DestructiveReason::DataLoss),
location: Location {
line: line_idx,
col_start: i.min(j),
col_end: input_chars.len().max(output_chars.len()),
},
description: format!(
"Content length mismatch: {} vs {} chars",
input_chars.len(),
output_chars.len()
),
impact_score: -0.3,
});
}
}
fn analyze_character_difference(
line_idx: usize,
input_pos: usize,
output_pos: usize,
input_chars: &[char],
output_chars: &[char],
transformations: &mut Vec<Transformation>,
) {
let input_char = input_chars.get(input_pos).copied().unwrap_or(' ');
let output_char = output_chars.get(output_pos).copied().unwrap_or(' ');
let (transform_type, impact_score) = classify_character_transformation(
input_char,
output_char,
input_chars,
output_chars,
input_pos,
output_pos,
);
transformations.push(Transformation {
transform_type,
location: Location {
line: line_idx,
col_start: input_pos,
col_end: output_pos + 1,
},
description: format!("'{input_char}' → '{output_char}'"),
impact_score,
});
}
fn classify_character_transformation(
input_char: char,
output_char: char,
input_chars: &[char],
output_chars: &[char],
input_pos: usize,
output_pos: usize,
) -> (TransformationType, f32) {
if let Some(result) = check_constructive_transformation(
input_char,
output_char,
input_chars,
output_chars,
input_pos,
output_pos,
) {
return result;
}
if let Some(result) = check_destructive_transformation(
input_char,
output_char,
input_chars,
output_chars,
input_pos,
output_pos,
) {
return result;
}
(
TransformationType::Neutral(NeutralReason::EncodingNormalization),
0.0,
)
}
fn check_constructive_transformation(
input_char: char,
output_char: char,
input_chars: &[char],
output_chars: &[char],
input_pos: usize,
output_pos: usize,
) -> Option<(TransformationType, f32)> {
if input_char == '↓'
&& output_char == '↓'
&& is_arrow_duplication(input_chars, output_chars, input_pos, output_pos)
{
return Some((
TransformationType::Constructive(ConstructiveReason::ArrowDuplication),
0.3,
));
}
if input_char == ' '
&& matches!(output_char, '↓' | '↑' | '←' | '→')
&& is_arrow_duplication(input_chars, output_chars, input_pos, output_pos)
{
return Some((
TransformationType::Constructive(ConstructiveReason::ArrowDuplication),
0.3,
));
}
if is_box_expansion(
input_char,
output_char,
input_chars,
output_chars,
input_pos,
output_pos,
) {
return Some((
TransformationType::Constructive(ConstructiveReason::BoxExpansion),
0.2,
));
}
None
}
fn check_destructive_transformation(
input_char: char,
output_char: char,
input_chars: &[char],
output_chars: &[char],
input_pos: usize,
output_pos: usize,
) -> Option<(TransformationType, f32)> {
if matches!(output_char, '↑' | '↓' | '←' | '→') && is_in_text_content(input_chars, input_pos)
{
return Some((
TransformationType::Destructive(DestructiveReason::CharacterCorruption),
-0.8,
));
}
if output_char == '│' && is_in_text_content(input_chars, input_pos) {
return Some((
TransformationType::Destructive(DestructiveReason::CharacterCorruption),
-0.6,
));
}
if input_char != ' '
&& output_char == ' '
&& !is_diagram_whitespace_change(input_chars, output_chars, input_pos, output_pos)
{
return Some((
TransformationType::Destructive(DestructiveReason::ContentDeletion),
-0.5,
));
}
None
}
fn is_arrow_duplication(
input_chars: &[char],
output_chars: &[char],
input_pos: usize,
output_pos: usize,
) -> bool {
if input_chars.get(input_pos) == Some(&' ')
&& matches!(output_chars.get(output_pos), Some('↓' | '↑' | '←' | '→'))
{
let start = output_pos.saturating_sub(5);
let end = (output_pos + 6).min(output_chars.len());
let context: String = output_chars[start..end].iter().collect();
let arrow_count = context
.chars()
.filter(|&c| matches!(c, '↓' | '↑' | '←' | '→'))
.count();
arrow_count >= 2
} else {
false
}
}
const fn is_box_expansion(
_input_char: char,
_output_char: char,
_input_chars: &[char],
_output_chars: &[char],
_input_pos: usize,
_output_pos: usize,
) -> bool {
false
}
const fn is_diagram_whitespace_change(
_input_chars: &[char],
_output_chars: &[char],
_input_pos: usize,
_output_pos: usize,
) -> bool {
false
}
fn is_in_text_content(chars: &[char], pos: usize) -> bool {
let start = pos.saturating_sub(10);
let end = (pos + 11).min(chars.len());
let context: String = chars[start..end].iter().collect();
context.chars().any(char::is_alphabetic) && !context.contains("┌┐└┘│─") }
fn calculate_summary(transformations: &[Transformation]) -> TransformationSummary {
let mut destructive_count = 0;
let mut constructive_count = 0;
let mut neutral_count = 0;
let mut net_quality_impact = 0.0;
for transformation in transformations {
net_quality_impact += transformation.impact_score;
match transformation.transform_type {
TransformationType::Destructive(_) => destructive_count += 1,
TransformationType::Constructive(_) => constructive_count += 1,
TransformationType::Neutral(_) => neutral_count += 1,
}
}
#[allow(clippy::cast_precision_loss)]
let total_transformations = transformations.len() as f32;
#[allow(clippy::cast_precision_loss)]
let risk_score = if total_transformations > 0.0 {
destructive_count as f32 / total_transformations
} else {
0.0
};
TransformationSummary {
destructive_count,
constructive_count,
neutral_count,
net_quality_impact,
risk_score,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arrow_duplication_classification() {
let input = " ↓\n┌────┐\n│ │\n└────┘\n ↓";
let output = " ↓↓\n┌────┐\n│ │\n└────┘\n ↓↓";
let analysis = analyze_transformations(input, output);
assert!(analysis.transformations.iter().any(|t| matches!(
t.transform_type,
TransformationType::Constructive(ConstructiveReason::ArrowDuplication)
)));
assert!(analysis.summary.net_quality_impact > 0.0);
assert!(analysis.summary.risk_score < 0.5);
}
#[test]
fn test_destructive_transformation() {
let input = "Hello World";
let output = "H↑llo W↑rld";
let analysis = analyze_transformations(input, output);
assert!(analysis
.transformations
.iter()
.any(|t| matches!(t.transform_type, TransformationType::Destructive(_))));
assert!(analysis.summary.net_quality_impact < 0.0);
assert!(analysis.summary.risk_score > 0.5);
}
}