#!/usr/bin/env python3
"""
GWF File Difference Analyzer

This script compares two .gwf files by processing them with framecpp_dump_objects
and analyzing the differences in their structure, comments, naming, and values.
"""

import argparse
import subprocess
import sys
import tempfile
import os
import re
from collections import defaultdict, Counter
from typing import Dict, List, Tuple, Set
import difflib


class GWFDiffAnalyzer:
    # Common regex patterns used throughout the class
    POSITION_PATTERN = r'Position:\s*(\d+)\s+length:\s*(\d+).*instance:\s*(\d+)'
    STRUCTURE_CLASS_PATTERN = r'class\s*\(([^)]+)\):\s*(\d+)\s+instance:\s*(\d+)'
    NAME_PATTERN = r'name:\s*(\S+)'
    COMMENT_PATTERN = r'comment:\s*(.*)'
    ATTRIBUTE_FIELDS = ['type:', 'unit:', 'unitX:', 'unitY:', 'slope:', 'offset:', 'dataValid:']
    
    def __init__(self, file1: str, file2: str, verbosity: int = 0):
        self.file1 = file1
        self.file2 = file2
        self.lines1 = []
        self.lines2 = []
        self.diff_lines = []
        self.verbosity = verbosity
        
    def process_gwf_files(self) -> Tuple[bool, str]:
        """Process GWF files using framecpp_dump_objects."""
        try:
            # Create temporary files for outputs
            with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='_old.txt') as tmp1:
                tmp1_name = tmp1.name
            with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='_new.txt') as tmp2:
                tmp2_name = tmp2.name
            
            # Process first file
            print(f"Processing {self.file1}...")
            result1 = subprocess.run(
                ['framecpp_dump_objects', '--silent-data', self.file1],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                universal_newlines=True
            )
            if result1.returncode != 0:
                return False, f"Error processing {self.file1}: {result1.stderr}"
            
            with open(tmp1_name, 'w') as f:
                f.write(result1.stdout)
            
            # Process second file
            print(f"Processing {self.file2}...")
            result2 = subprocess.run(
                ['framecpp_dump_objects', '--silent-data', self.file2],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                universal_newlines=True
            )
            if result2.returncode != 0:
                return False, f"Error processing {self.file2}: {result2.stderr}"
            
            with open(tmp2_name, 'w') as f:
                f.write(result2.stdout)
            
            # Read processed files
            with open(tmp1_name, 'r') as f:
                self.lines1 = f.readlines()
            with open(tmp2_name, 'r') as f:
                self.lines2 = f.readlines()
            
            # Generate diff
            self.diff_lines = list(difflib.unified_diff(
                self.lines1, self.lines2,
                fromfile=self.file1, tofile=self.file2,
                lineterm=''
            ))
            
            # Cleanup
            os.unlink(tmp1_name)
            os.unlink(tmp2_name)
            
            return True, "Files processed successfully"
            
        except Exception as e:
            return False, str(e)
    
    def analyze_structural_changes(self) -> Dict:
        """Analyze changes in positions and lengths."""
        structural_changes = {
            'position_changes': [],
            'length_changes': [],
            'total_shifts': 0,
            'added_structures': [],
            'removed_structures': [],
            'line_context': {}
        }
        
        pattern = re.compile(r'Position:\s*(\d+)\s+length:\s*(\d+)\s+class\s*\(([^)]+)\):\s*(\d+)\s+instance:\s*(\d+)')
        
        old_structures = {}
        new_structures = {}
        
        # Parse old file
        for i, line in enumerate(self.lines1):
            match = pattern.search(line)
            if match:
                pos, length, cls, cls_num, instance = match.groups()
                key = (cls, cls_num, instance)
                old_structures[key] = {
                    'position': int(pos),
                    'length': int(length),
                    'line_number': i + 1,
                    'line': line.strip()
                }
        
        # Parse new file
        for i, line in enumerate(self.lines2):
            match = pattern.search(line)
            if match:
                pos, length, cls, cls_num, instance = match.groups()
                key = (cls, cls_num, instance)
                new_structures[key] = {
                    'position': int(pos),
                    'length': int(length),
                    'line_number': i + 1,
                    'line': line.strip()
                }
        
        # Compare structures
        for key in old_structures:
            if key in new_structures:
                old = old_structures[key]
                new = new_structures[key]
                
                if old['position'] != new['position']:
                    structural_changes['position_changes'].append({
                        'structure': f"{key[0]} instance {key[2]}",
                        'class_num': key[1],
                        'old_pos': old['position'],
                        'new_pos': new['position'],
                        'shift': new['position'] - old['position'],
                        'old_line_num': old.get('line_number'),
                        'new_line_num': new.get('line_number'),
                        'old_line': old.get('line', ''),
                        'new_line': new.get('line', '')
                    })
                
                if old['length'] != new['length']:
                    structural_changes['length_changes'].append({
                        'structure': f"{key[0]} instance {key[2]}",
                        'class_num': key[1],
                        'old_length': old['length'],
                        'new_length': new['length'],
                        'change': new['length'] - old['length'],
                        'old_line_num': old.get('line_number'),
                        'new_line_num': new.get('line_number'),
                        'old_line': old.get('line', ''),
                        'new_line': new.get('line', '')
                    })
        
        # Find added and removed structures
        for key in new_structures:
            if key not in old_structures:
                new = new_structures[key]
                structural_changes['added_structures'].append({
                    'structure': f"{key[0]} instance {key[2]}",
                    'position': new['position'],
                    'length': new['length'],
                    'line_number': new.get('line_number'),
                    'line': new.get('line', '')
                })
        
        for key in old_structures:
            if key not in new_structures:
                old = old_structures[key]
                structural_changes['removed_structures'].append({
                    'structure': f"{key[0]} instance {key[2]}",
                    'position': old['position'],
                    'length': old['length'],
                    'line_number': old.get('line_number'),
                    'line': old.get('line', '')
                })
        
        structural_changes['total_shifts'] = len(structural_changes['position_changes'])
        return structural_changes
    
    def analyze_comment_changes(self) -> Dict:
        """Analyze changes in comment fields."""
        comment_changes = {
            'empty_to_filled': 0,
            'modified_comments': [],
            'new_comments': [],
            'removed_comments': [],
            'all_empty_to_filled': []  # For verbose mode
        }
        
        # Extract comments with their context
        def extract_comments(lines):
            comments = {}
            current_name = None
            current_structure = None
            
            for i, line in enumerate(lines):
                # Capture structure info
                struct_match = re.search(r'class\s*\(([^)]+)\):\s*(\d+)\s+instance:\s*(\d+)', line)
                if struct_match:
                    current_structure = struct_match.groups()
                    
                if 'name:' in line:
                    match = re.search(r'name:\s*(\S+)', line)
                    if match:
                        current_name = match.group(1)
                elif 'comment:' in line and current_name:
                    match = re.search(r'comment:\s*(.*)', line)
                    if match:
                        comment = match.group(1).strip()
                        comments[current_name] = {
                            'comment': comment,
                            'line_number': i + 1,
                            'structure': current_structure,
                            'line': line.strip()
                        }
            
            return comments
        
        old_comments = extract_comments(self.lines1)
        new_comments = extract_comments(self.lines2)
        
        # Compare comments
        for name in old_comments:
            old_data = old_comments[name]
            new_data = new_comments.get(name)
            
            if new_data:
                old_comment = old_data['comment']
                new_comment = new_data['comment']
                
                if old_comment == '' and new_comment != '':
                    comment_changes['empty_to_filled'] += 1
                    change_info = {
                        'field': name,
                        'comment': new_comment,
                        'old_line_num': old_data['line_number'],
                        'new_line_num': new_data['line_number'],
                        'structure': new_data.get('structure', ['', '', ''])
                    }
                    comment_changes['new_comments'].append(change_info)
                    comment_changes['all_empty_to_filled'].append(change_info)
                elif old_comment != new_comment and old_comment != '' and new_comment != '':
                    comment_changes['modified_comments'].append({
                        'field': name,
                        'old': old_comment,
                        'new': new_comment,
                        'old_line_num': old_data['line_number'],
                        'new_line_num': new_data['line_number'],
                        'structure': new_data.get('structure', ['', '', ''])
                    })
            else:
                # Comment was removed
                if old_data['comment']:
                    comment_changes['removed_comments'].append({
                        'field': name,
                        'comment': old_data['comment'],
                        'line_number': old_data['line_number'],
                        'structure': old_data.get('structure', ['', '', ''])
                    })
        
        # Find new fields with comments in new file
        for name in new_comments:
            if name not in old_comments and new_comments[name]['comment']:
                new_data = new_comments[name]
                comment_changes['new_comments'].append({
                    'field': name,
                    'comment': new_data['comment'],
                    'old_line_num': None,
                    'new_line_num': new_data['line_number'],
                    'structure': new_data.get('structure', ['', '', ''])
                })
        
        return comment_changes
    
    def analyze_naming_changes(self) -> Dict:
        """Analyze changes in field names."""
        naming_changes = {
            'renamed_fields': [],
            'removed_fields': [],
            'added_fields': [],
            'name_context': {}
        }
        
        # Extract field names with context
        def extract_names_with_context(lines):
            names = {}
            current_structure = None
            
            for i, line in enumerate(lines):
                # Capture structure info
                struct_match = re.search(r'class\s*\(([^)]+)\):\s*(\d+)\s+instance:\s*(\d+)', line)
                if struct_match:
                    current_structure = struct_match.groups()
                    
                match = re.search(r'name:\s*(\S+)', line)
                if match:
                    name = match.group(1)
                    names[name] = {
                        'line_number': i + 1,
                        'line': line.strip(),
                        'structure': current_structure
                    }
            return names
        
        old_names_data = extract_names_with_context(self.lines1)
        new_names_data = extract_names_with_context(self.lines2)
        
        old_names = set(old_names_data.keys())
        new_names = set(new_names_data.keys())
        
        removed = old_names - new_names
        added = new_names - old_names
        
        # Store all removed fields
        for name in removed:
            data = old_names_data[name]
            naming_changes['removed_fields'].append({
                'name': name,
                'line_number': data['line_number'],
                'structure': data.get('structure', ['', '', '']),
                'line': data['line']
            })
        
        # Store all added fields
        for name in added:
            data = new_names_data[name]
            naming_changes['added_fields'].append({
                'name': name,
                'line_number': data['line_number'],
                'structure': data.get('structure', ['', '', '']),
                'line': data['line']
            })
        
        # Try to match renamed fields with improved heuristics
        remaining_removed = list(removed)
        remaining_added = list(added)
        
        for old in list(remaining_removed):
            best_match = None
            best_score = 0
            
            for new in remaining_added:
                # Calculate similarity score
                score = 0
                if old.lower() in new.lower() or new.lower() in old.lower():
                    score += 50
                # Check if they share common substrings
                common_len = len(os.path.commonprefix([old, new]))
                score += common_len * 10
                # Check if in same structure
                old_struct = old_names_data[old].get('structure')
                new_struct = new_names_data[new].get('structure')
                if old_struct and new_struct and old_struct == new_struct:
                    score += 30
                
                if score > best_score:
                    best_score = score
                    best_match = new
            
            if best_match and best_score >= 50:
                old_data = old_names_data[old]
                new_data = new_names_data[best_match]
                naming_changes['renamed_fields'].append({
                    'old_name': old,
                    'new_name': best_match,
                    'confidence': best_score,
                    'old_line_num': old_data['line_number'],
                    'new_line_num': new_data['line_number'],
                    'old_structure': old_data.get('structure', ['', '', '']),
                    'new_structure': new_data.get('structure', ['', '', ''])
                })
                remaining_removed.remove(old)
                remaining_added.remove(best_match)
        
        return naming_changes
    
    def analyze_value_changes(self) -> Dict:
        """Analyze changes in values."""
        value_changes = {
            'changed_values': [],
            'all_occurrences': {}  # For verbose mode
        }
        
        # Look for specific value patterns
        value_patterns = [
            (r'ULeapS:\s*(\d+)', 'ULeapS'),
            (r'localTime:\s*([-\d]+)', 'localTime'),
            (r'longitudeD:\s*([-\d]+)', 'longitudeD'),
            (r'latitudeD:\s*([-\d]+)', 'latitudeD'),
            (r'longitudeM:\s*([-\d]+)', 'longitudeM'),
            (r'longitudeS:\s*([-\d.]+)', 'longitudeS'),
            (r'latitudeM:\s*([-\d]+)', 'latitudeM'),
            (r'latitudeS:\s*([-\d.]+)', 'latitudeS'),
            (r'elevation:\s*([-\d.]+)', 'elevation'),
            (r'compress:\s*(\d+)', 'compress'),
            (r'armXazimuth:\s*([-\d.]+)', 'armXazimuth'),
            (r'armYazimuth:\s*([-\d.]+)', 'armYazimuth')
        ]
        
        def extract_value_occurrences(lines, pattern):
            """Extract value occurrences with context from lines."""
            occurrences = []
            for i, line in enumerate(lines):
                match = re.search(pattern, line)
                if match:
                    # Get context
                    struct_match = re.search(self.STRUCTURE_CLASS_PATTERN, line)
                    context = ''
                    if struct_match:
                        context = f"{struct_match.group(1)} instance {struct_match.group(3)}"
                    else:
                        # Look back for structure context
                        for j in range(max(0, i-10), i):
                            struct_match = re.search(self.STRUCTURE_CLASS_PATTERN, lines[j])
                            if struct_match:
                                context = f"{struct_match.group(1)} instance {struct_match.group(3)}"
                                break
                    
                    occurrences.append({
                        'value': match.group(1),
                        'line_number': i + 1,
                        'line': line.strip(),
                        'context': context
                    })
            return occurrences

        for pattern, field_name in value_patterns:
            old_occurrences = extract_value_occurrences(self.lines1, pattern)
            new_occurrences = extract_value_occurrences(self.lines2, pattern)
            
            # Store all occurrences for verbose mode
            if old_occurrences or new_occurrences:
                value_changes['all_occurrences'][field_name] = {
                    'old': old_occurrences,
                    'new': new_occurrences
                }
            
            # Compare values
            if old_occurrences and new_occurrences:
                # Compare by context when possible
                old_by_context = {occ['context']: occ for occ in old_occurrences if occ['context']}
                new_by_context = {occ['context']: occ for occ in new_occurrences if occ['context']}
                
                # Check values in same context
                for context in old_by_context:
                    if context in new_by_context:
                        old_val = old_by_context[context]['value']
                        new_val = new_by_context[context]['value']
                        if old_val != new_val:
                            value_changes['changed_values'].append({
                                'field': field_name,
                                'old_value': old_val,
                                'new_value': new_val,
                                'context': context,
                                'old_line_num': old_by_context[context]['line_number'],
                                'new_line_num': new_by_context[context]['line_number']
                            })
                            break
                else:
                    # If no context match, compare first occurrence
                    if old_occurrences[0]['value'] != new_occurrences[0]['value']:
                        value_changes['changed_values'].append({
                            'field': field_name,
                            'old_value': old_occurrences[0]['value'],
                            'new_value': new_occurrences[0]['value'],
                            'context': '',
                            'old_line_num': old_occurrences[0]['line_number'],
                            'new_line_num': new_occurrences[0]['line_number']
                        })
        
        return value_changes
    
    def analyze_structure_descriptions(self) -> Dict:
        """Analyze FrSH/FrSE structure descriptions."""
        structure_desc = {
            'old_structures': {},
            'new_structures': {},
            'added_structures': [],
            'removed_structures': [],
            'modified_structures': [],
            'critical_errors': 0,
            'warnings': 0
        }
        
        def extract_structure_descriptions(lines):
            """Extract FrSH and associated FrSE entries."""
            structures = {}
            current_frsh = None
            current_frsh_data = {}
            current_frse_list = []
            
            def create_entry_template(line, line_num):
                """Create template for FrSH/FrSE entry with common fields."""
                entry = {
                    'line_number': line_num,
                    'line': line.strip(),
                    'name': None,
                    'comment': None,
                    'attributes': {}
                }
                # Extract position info using common pattern
                pos_match = re.search(self.POSITION_PATTERN, line)
                if pos_match:
                    entry.update({
                        'position': int(pos_match.group(1)),
                        'length': int(pos_match.group(2)),
                        'instance': int(pos_match.group(3))
                    })
                return entry
            
            def save_current_structure():
                """Save current structure if valid."""
                if current_frsh is not None and current_frsh_data:
                    structures[current_frsh] = {
                        'frsh': current_frsh_data,
                        'frse_entries': current_frse_list.copy()
                    }
            
            def assign_field_to_current_entry(field_name, value):
                """Assign field to current FrSE or FrSH entry."""
                if current_frse_list and not current_frse_list[-1].get(field_name):
                    current_frse_list[-1][field_name] = value
                elif not current_frsh_data.get(field_name):
                    current_frsh_data[field_name] = value
                    if field_name == 'name':
                        nonlocal current_frsh
                        current_frsh = value
            
            for i, line in enumerate(lines):
                line_num = i + 1
                
                # Look for FrSH entries
                if 'class (FrSH):' in line:
                    save_current_structure()
                    current_frsh_data = create_entry_template(line, line_num)
                    current_frse_list = []
                
                # Look for FrSE entries
                elif 'class (FrSE):' in line and current_frsh_data:
                    frse_entry = create_entry_template(line, line_num)
                    frse_entry['class'] = None
                    current_frse_list.append(frse_entry)
                
                # Extract fields using common patterns
                elif current_frsh_data:
                    # Name field
                    if 'name:' in line:
                        name_match = re.search(self.NAME_PATTERN, line)
                        if name_match:
                            assign_field_to_current_entry('name', name_match.group(1))
                    
                    # Comment field
                    elif 'comment:' in line:
                        comment_match = re.search(self.COMMENT_PATTERN, line)
                        if comment_match:
                            assign_field_to_current_entry('comment', comment_match.group(1).strip())
                    
                    # Class field (FrSE only)
                    elif 'class:' in line and current_frse_list and current_frse_list[-1]['class'] is None:
                        class_match = re.search(r'class:\s*(\S+)', line)
                        if class_match:
                            current_frse_list[-1]['class'] = class_match.group(1)
                    
                    # Attribute fields
                    else:
                        for attr in self.ATTRIBUTE_FIELDS:
                            if attr in line:
                                value_match = re.search(f'{attr}\\s*(\\S+)', line)
                                if value_match:
                                    value = value_match.group(1)
                                    attr_name = attr.rstrip(':')
                                    # Assign to FrSE if available, else FrSH
                                    if current_frse_list and attr_name not in current_frse_list[-1]['attributes']:
                                        current_frse_list[-1]['attributes'][attr_name] = value
                                    elif attr_name not in current_frsh_data['attributes']:
                                        current_frsh_data['attributes'][attr_name] = value
                                break
            
            save_current_structure()
            return structures
        
        # Extract structures from both files
        structure_desc['old_structures'] = extract_structure_descriptions(self.lines1)
        structure_desc['new_structures'] = extract_structure_descriptions(self.lines2)
        
        old_names = set(structure_desc['old_structures'].keys())
        new_names = set(structure_desc['new_structures'].keys())
        
        # Find added and removed structures
        for name in new_names - old_names:
            structure_desc['added_structures'].append({
                'name': name,
                'data': structure_desc['new_structures'][name]
            })
        
        for name in old_names - new_names:
            structure_desc['removed_structures'].append({
                'name': name,
                'data': structure_desc['old_structures'][name]
            })
        
        # Compare common structures
        for name in old_names & new_names:
            old_struct = structure_desc['old_structures'][name]
            new_struct = structure_desc['new_structures'][name]
            
            differences = {
                'name': name,
                'frse_changes': {
                    'added': [],
                    'removed': [],
                    'class_mismatches': []  # Critical errors
                },
                'warnings': []  # Non-critical issues like comments and attributes
            }
            
            # Compare FrSH attributes
            old_frsh = old_struct['frsh']
            new_frsh = new_struct['frsh']
            
            # Check comment changes (warning only)
            if old_frsh.get('comment') != new_frsh.get('comment'):
                differences['warnings'].append({
                    'type': 'frsh_comment',
                    'message': f"FrSH comment changed",
                    'old': old_frsh.get('comment', ''),
                    'new': new_frsh.get('comment', '')
                })
            
            # Check attribute changes (warnings only)
            all_attrs = set(old_frsh.get('attributes', {}).keys()) | set(new_frsh.get('attributes', {}).keys())
            for attr in all_attrs:
                old_val = old_frsh.get('attributes', {}).get(attr)
                new_val = new_frsh.get('attributes', {}).get(attr)
                if old_val != new_val:
                    differences['warnings'].append({
                        'type': 'frsh_attribute',
                        'attribute': attr,
                        'message': f"FrSH attribute '{attr}' changed",
                        'old': old_val,
                        'new': new_val
                    })
            
            # Compare FrSE entries
            old_frse_by_name = {e['name']: e for e in old_struct['frse_entries'] if e.get('name')}
            new_frse_by_name = {e['name']: e for e in new_struct['frse_entries'] if e.get('name')}
            
            old_frse_names = set(old_frse_by_name.keys())
            new_frse_names = set(new_frse_by_name.keys())
            
            # Added FrSE entries (CRITICAL)
            for frse_name in new_frse_names - old_frse_names:
                differences['frse_changes']['added'].append({
                    'name': frse_name,
                    'data': new_frse_by_name[frse_name]
                })
            
            # Removed FrSE entries (CRITICAL)
            for frse_name in old_frse_names - new_frse_names:
                differences['frse_changes']['removed'].append({
                    'name': frse_name,
                    'data': old_frse_by_name[frse_name]
                })
            
            # Modified FrSE entries
            for frse_name in old_frse_names & new_frse_names:
                old_frse = old_frse_by_name[frse_name]
                new_frse = new_frse_by_name[frse_name]
                
                # CRITICAL: Check class compatibility
                old_class = old_frse.get('class')
                new_class = new_frse.get('class')
                if old_class != new_class:
                    differences['frse_changes']['class_mismatches'].append({
                        'name': frse_name,
                        'old_class': old_class,
                        'new_class': new_class,
                        'old_line': old_frse.get('line_number'),
                        'new_line': new_frse.get('line_number')
                    })
                    continue  # Don't process further if classes don't match
                
                frse_diff = {
                    'name': frse_name,
                    'changes': {}
                }
                
                # Check comment (warning only)
                if old_frse.get('comment') != new_frse.get('comment'):
                    differences['warnings'].append({
                        'type': 'frse_comment',
                        'frse_name': frse_name,
                        'message': f"FrSE '{frse_name}' comment changed",
                        'old': old_frse.get('comment', ''),
                        'new': new_frse.get('comment', '')
                    })
                
                # Check attributes (warnings only)
                all_frse_attrs = set(old_frse.get('attributes', {}).keys()) | set(new_frse.get('attributes', {}).keys())
                for attr in all_frse_attrs:
                    old_val = old_frse.get('attributes', {}).get(attr)
                    new_val = new_frse.get('attributes', {}).get(attr)
                    if old_val != new_val:
                        differences['warnings'].append({
                            'type': 'frse_attribute',
                            'frse_name': frse_name,
                            'attribute': attr,
                            'message': f"FrSE '{frse_name}' attribute '{attr}' changed",
                            'old': old_val,
                            'new': new_val
                        })
                
                # All FrSE changes (comments and attributes) are now warnings
            
            # Only add to modified if there are actual changes or warnings
            if (differences['frse_changes']['added'] or 
                differences['frse_changes']['removed'] or 
                differences['frse_changes']['class_mismatches'] or
                differences['warnings']):
                structure_desc['modified_structures'].append(differences)
                # Update counts
                structure_desc['critical_errors'] += len(differences['frse_changes']['class_mismatches'])
                structure_desc['critical_errors'] += len(differences['frse_changes']['added'])
                structure_desc['critical_errors'] += len(differences['frse_changes']['removed'])
                structure_desc['warnings'] += len(differences['warnings'])
        
        return structure_desc
    
    def _format_frse_entry(self, frse_data: Dict, verbosity: int, indent: str = "  ") -> List[str]:
        """Format FrSE entry details with consistent formatting."""
        lines = []
        class_info = f" [class: {frse_data.get('class', 'unknown')}]" if frse_data.get('class') else ""
        lines.append(f"{indent}- **{frse_data['name']}**{class_info} (line {frse_data.get('line_number', 'unknown')})")
        
        if verbosity >= 1:
            if frse_data.get('comment'):
                lines.append(f"{indent}  - Comment: \"{frse_data['comment']}\"")
            if frse_data.get('attributes'):
                attr_str = ', '.join(f'{k}={v}' for k, v in frse_data['attributes'].items())
                lines.append(f"{indent}  - Attributes: {attr_str}")
        return lines

    def _format_frse_list(self, frse_list: List[Dict], verbosity: int, title: str, indent: str = "  - ") -> List[str]:
        """Format list of FrSE entries with consistent formatting."""
        lines = []
        if frse_list:
            lines.append(f"{indent}**{title}**:")
            for frse in frse_list:
                data = frse.get('data', frse)  # Handle both direct data and wrapped data
                lines.extend(self._format_frse_entry(data, verbosity, indent + "  "))
        return lines

    def _format_warnings(self, warnings: List[Dict], verbosity: int) -> List[str]:
        """Format warning messages with consistent formatting."""
        lines = ["- **ℹ️  Warnings (non-critical)**:"]
        for warning in warnings:
            if warning['type'] == 'frsh_comment':
                lines.append("  - FrSH comment changed:")
                if verbosity >= 1:
                    lines.append(f"    - Old: \"{warning['old']}\"")
                    lines.append(f"    - New: \"{warning['new']}\"")
                else:
                    lines.append(f"    - \"{warning['old'][:50]}...\" → \"{warning['new'][:50]}...\"")
            elif warning['type'] == 'frse_comment':
                lines.append(f"  - FrSE '{warning['frse_name']}' comment changed:")
                if verbosity >= 1:
                    lines.append(f"    - Old: \"{warning['old']}\"")
                    lines.append(f"    - New: \"{warning['new']}\"")
                else:
                    lines.append(f"    - \"{warning['old'][:50]}...\" → \"{warning['new'][:50]}...\"")
            elif warning['type'] == 'frsh_attribute':
                lines.append(f"  - FrSH attribute '{warning['attribute']}' changed:")
                if warning['old'] is None:
                    lines.append(f"    - Added: {warning['new']}")
                elif warning['new'] is None:
                    lines.append(f"    - Removed: {warning['old']}")
                else:
                    lines.append(f"    - {warning['old']} → {warning['new']}")
            elif warning['type'] == 'frse_attribute':
                lines.append(f"  - FrSE '{warning['frse_name']}' attribute '{warning['attribute']}' changed:")
                if warning['old'] is None:
                    lines.append(f"    - Added: {warning['new']}")
                elif warning['new'] is None:
                    lines.append(f"    - Removed: {warning['old']}")
                else:
                    lines.append(f"    - {warning['old']} → {warning['new']}")
        return lines

    def _format_comment_changes(self, comments: List[Dict], verbosity: int, title: str, is_removed: bool = False) -> List[str]:
        """Format comment changes with consistent formatting."""
        lines = [f"\n### {title}:"]
        limit = None if verbosity >= 2 else 5
        for comment in comments[:limit]:
            if is_removed:
                lines.append(f"  - {comment['field']}: \"{comment['comment'][:60]}...\"")
                if verbosity >= 2:
                    lines.append(f"    Line {comment['line_number']}")
                    if comment['structure'][0]:
                        lines.append(f"    In structure: {comment['structure'][0]} instance {comment['structure'][2]}")
            else:
                lines.append(f"  - {comment['field']}:")
                lines.append(f"    Old: \"{comment['old'][:60]}...\"")
                lines.append(f"    New: \"{comment['new'][:60]}...\"")
                if verbosity >= 2:
                    lines.append(f"    Line {comment['old_line_num']} → {comment['new_line_num']}")
        return lines

    def _generate_structure_summary(self, summary: List[str], verbosity: int, suppress_warnings: bool = False) -> str:
        """Generate summary specifically for structure descriptions."""
        structure_desc = self.analyze_structure_descriptions()
        
        summary.append("## Structure Descriptions Analysis (FrSH/FrSE)")
        summary.append(f"- Total structures in file 1: {len(structure_desc['old_structures'])}")
        summary.append(f"- Total structures in file 2: {len(structure_desc['new_structures'])}")
        summary.append(f"- Added structures: {len(structure_desc['added_structures'])}")
        summary.append(f"- Removed structures: {len(structure_desc['removed_structures'])}")
        summary.append(f"- Modified structures: {len(structure_desc['modified_structures'])}")
        
        if structure_desc['critical_errors'] > 0:
            summary.append(f"\n**⚠️  CRITICAL ERRORS: {structure_desc['critical_errors']} FrSE issues found (class mismatches, additions, or removals)**")
        if structure_desc['warnings'] > 0 and not suppress_warnings:
            summary.append(f"**ℹ️  Warnings: {structure_desc['warnings']} comment and attribute changes (non-critical)**")
        elif structure_desc['warnings'] > 0 and suppress_warnings:
            summary.append(f"**ℹ️  {structure_desc['warnings']} warnings suppressed (use without --suppress-warnings to see them)**")
        
        # Added structures
        if structure_desc['added_structures']:
            summary.append("\n### Added Structures")
            for struct in structure_desc['added_structures']:
                summary.append(f"\n#### {struct['name']}")
                data = struct['data']
                frsh = data['frsh']
                
                summary.append(f"- **FrSH** (line {frsh['line_number']})")
                if frsh.get('comment'):
                    summary.append(f"  - Comment: \"{frsh['comment']}\"")
                if frsh.get('attributes'):
                    summary.append(f"  - Attributes: {', '.join(f'{k}={v}' for k, v in frsh['attributes'].items())}")
                
                if data['frse_entries']:
                    summary.append(f"- **FrSE entries**: {len(data['frse_entries'])}")
                    if verbosity >= 1:
                        summary.extend(self._format_frse_list(data['frse_entries'], verbosity, "", "  "))
        
        # Removed structures
        if structure_desc['removed_structures']:
            summary.append("\n### Removed Structures")
            for struct in structure_desc['removed_structures']:
                summary.append(f"\n#### {struct['name']}")
                data = struct['data']
                frsh = data['frsh']
                
                summary.append(f"- **FrSH** (line {frsh['line_number']})")
                if frsh.get('comment'):
                    summary.append(f"  - Comment: \"{frsh['comment']}\"")
                if frsh.get('attributes'):
                    summary.append(f"  - Attributes: {', '.join(f'{k}={v}' for k, v in frsh['attributes'].items())}")
                
                if data['frse_entries']:
                    summary.append(f"- **FrSE entries**: {len(data['frse_entries'])}")
                    if verbosity >= 1:
                        summary.extend(self._format_frse_list(data['frse_entries'], verbosity, "", "  "))
        
        # Modified structures
        if structure_desc['modified_structures']:
            summary.append("\n### Modified Structures")
            for struct in structure_desc['modified_structures']:
                summary.append(f"\n#### {struct['name']}")
                
                # Show all critical errors first
                has_critical = False
                
                # Class mismatches
                if struct['frse_changes']['class_mismatches']:
                    if not has_critical:
                        summary.append("- **🚨 CRITICAL ERRORS**:")
                        has_critical = True
                    summary.append("  - **FrSE Class Mismatches**:")
                    for mismatch in struct['frse_changes']['class_mismatches']:
                        summary.append(f"    - **{mismatch['name']}**: class '{mismatch['old_class']}' → '{mismatch['new_class']}'")
                        summary.append(f"      Old file line {mismatch['old_line']}, New file line {mismatch['new_line']}")
                
                # Added FrSE entries
                frse_changes = struct['frse_changes']
                if frse_changes['added']:
                    if not has_critical:
                        summary.append("- **🚨 CRITICAL ERRORS**:")
                        has_critical = True
                    summary.extend(self._format_frse_list(frse_changes['added'], verbosity, "Added FrSE entries", "  "))
                
                # Removed FrSE entries
                if frse_changes['removed']:
                    if not has_critical:
                        summary.append("- **🚨 CRITICAL ERRORS**:")
                        has_critical = True
                    summary.extend(self._format_frse_list(frse_changes['removed'], verbosity, "Removed FrSE entries", "  "))
                
                # Show warnings after critical errors (unless suppressed)
                if struct['warnings'] and not suppress_warnings:
                    summary.extend(self._format_warnings(struct['warnings'], verbosity))
        
        return '\n'.join(summary)
    
    def generate_summary(self, verbosity: int = None, structures_only: bool = False, suppress_warnings: bool = False) -> str:
        """Generate a comprehensive summary of differences."""
        if verbosity is None:
            verbosity = self.verbosity
            
        summary = []
        summary.append(f"# GWF File Comparison Report\n")
        summary.append(f"**File 1**: {self.file1}")
        summary.append(f"**File 2**: {self.file2}")
        
        if structures_only:
            summary.append(f"**Mode**: Structure descriptions only (FrSH/FrSE)\n")
            return self._generate_structure_summary(summary, verbosity, suppress_warnings)
        
        summary.append(f"**Lines in file 1**: {len(self.lines1)}")
        summary.append(f"**Lines in file 2**: {len(self.lines2)}\n")
        
        # Structural changes
        structural = self.analyze_structural_changes()
        summary.append("## 1. Structural Changes")
        summary.append(f"- Position shifts: {structural['total_shifts']}")
        summary.append(f"- Length changes: {len(structural['length_changes'])}")
        summary.append(f"- Added structures: {len(structural['added_structures'])}")
        summary.append(f"- Removed structures: {len(structural['removed_structures'])}")
        
        if structural['length_changes']:
            summary.append("\n### Notable length changes:")
            limit = None if verbosity >= 2 else 5
            for change in structural['length_changes'][:limit]:
                summary.append(f"  - {change['structure']}: {change['old_length']} → {change['new_length']} ({change['change']:+d})")
                if verbosity >= 1:
                    summary.append(f"    Old: Line {change['old_line_num']}: {change['old_line'][:80]}...")
                    summary.append(f"    New: Line {change['new_line_num']}: {change['new_line'][:80]}...")
        
        if verbosity >= 1 and structural['position_changes']:
            summary.append("\n### Position shifts:")
            limit = None if verbosity >= 2 else 10
            for change in structural['position_changes'][:limit]:
                summary.append(f"  - {change['structure']}: {change['old_pos']} → {change['new_pos']} (shift: {change['shift']:+d})")
                if verbosity >= 2:
                    summary.append(f"    Old: Line {change['old_line_num']}: {change['old_line'][:80]}...")
                    summary.append(f"    New: Line {change['new_line_num']}: {change['new_line'][:80]}...")
        
        if verbosity >= 1:
            if structural['added_structures']:
                summary.append("\n### Added structures:")
                for struct in structural['added_structures']:
                    summary.append(f"  - {struct['structure']} at position {struct['position']} (length: {struct['length']})")
                    if verbosity >= 2:
                        summary.append(f"    Line {struct['line_number']}: {struct['line'][:80]}...")
            
            if structural['removed_structures']:
                summary.append("\n### Removed structures:")
                for struct in structural['removed_structures']:
                    summary.append(f"  - {struct['structure']} at position {struct['position']} (length: {struct['length']})")
                    if verbosity >= 2:
                        summary.append(f"    Line {struct['line_number']}: {struct['line'][:80]}...")
        
        # Comment changes
        comments = self.analyze_comment_changes()
        if not suppress_warnings:
            summary.append(f"\n## 2. Documentation Improvements")
            summary.append(f"- Empty comments now filled: {comments['empty_to_filled']}")
            summary.append(f"- Modified comments: {len(comments['modified_comments'])}")
            summary.append(f"- Removed comments: {len(comments['removed_comments'])}")
        elif comments['empty_to_filled'] > 0 or comments['modified_comments'] or comments['removed_comments']:
            total_comment_changes = comments['empty_to_filled'] + len(comments['modified_comments']) + len(comments['removed_comments'])
            summary.append(f"\n## 2. Documentation Changes (Suppressed)")
            summary.append(f"- {total_comment_changes} comment changes suppressed (use without --suppress-warnings to see them)")
        
        if comments['new_comments'] and not suppress_warnings:
            summary.append("\n### Sample of new comments:")
            limit = None if verbosity >= 2 else 5
            for comment in comments['new_comments'][:limit]:
                summary.append(f"  - {comment['field']}: \"{comment['comment'][:60]}...\"")
                if verbosity >= 1:
                    if comment['old_line_num']:
                        summary.append(f"    Old line {comment['old_line_num']} → New line {comment['new_line_num']}")
                    else:
                        summary.append(f"    New field at line {comment['new_line_num']}")
                    if comment['structure'][0]:
                        summary.append(f"    In structure: {comment['structure'][0]} instance {comment['structure'][2]}")
        
        if verbosity >= 1 and comments['modified_comments'] and not suppress_warnings:
            summary.extend(self._format_comment_changes(comments['modified_comments'], verbosity, "Modified comments"))
        
        if verbosity >= 1 and comments['removed_comments'] and not suppress_warnings:
            summary.extend(self._format_comment_changes(comments['removed_comments'], verbosity, "Removed comments", is_removed=True))
        
        # Naming changes
        naming = self.analyze_naming_changes()
        if naming['renamed_fields'] or (verbosity >= 1 and (naming['added_fields'] or naming['removed_fields'])):
            summary.append(f"\n## 3. Naming Changes")
            
            if naming['renamed_fields']:
                summary.append("\n### Renamed fields:")
                for rename in naming['renamed_fields']:
                    summary.append(f"- {rename['old_name']} → {rename['new_name']}")
                    if verbosity >= 1:
                        summary.append(f"  Confidence: {rename['confidence']}")
                        summary.append(f"  Old line {rename['old_line_num']} → New line {rename['new_line_num']}")
                        if rename['old_structure'][0]:
                            summary.append(f"  Structure: {rename['old_structure'][0]} instance {rename['old_structure'][2]}")
            
            if verbosity >= 1:
                if naming['removed_fields']:
                    summary.append("\n### Removed fields:")
                    limit = None if verbosity >= 2 else 10
                    for field in naming['removed_fields'][:limit]:
                        summary.append(f"- {field['name']} (line {field['line_number']})")
                        if verbosity >= 2 and field['structure'][0]:
                            summary.append(f"  In structure: {field['structure'][0]} instance {field['structure'][2]}")
                
                if naming['added_fields']:
                    summary.append("\n### Added fields:")
                    limit = None if verbosity >= 2 else 10
                    for field in naming['added_fields'][:limit]:
                        summary.append(f"- {field['name']} (line {field['line_number']})")
                        if verbosity >= 2 and field['structure'][0]:
                            summary.append(f"  In structure: {field['structure'][0]} instance {field['structure'][2]}")
        
        # Value changes
        values = self.analyze_value_changes()
        if values['changed_values']:
            summary.append(f"\n## 4. Value Updates")
            for change in values['changed_values']:
                if change['context']:
                    summary.append(f"- {change['field']} in {change['context']}: {change['old_value']} → {change['new_value']}")
                else:
                    summary.append(f"- {change['field']}: {change['old_value']} → {change['new_value']}")
                if verbosity >= 1:
                    summary.append(f"  Old line {change['old_line_num']} → New line {change['new_line_num']}")
            
            if verbosity >= 2 and values['all_occurrences']:
                summary.append("\n### All value occurrences:")
                for field_name, occurrences in values['all_occurrences'].items():
                    if len(occurrences['old']) > 1 or len(occurrences['new']) > 1:
                        summary.append(f"\n#### {field_name}:")
                        
                        # Create side-by-side comparison
                        # First, match by context when possible
                        old_by_context = {}
                        new_by_context = {}
                        old_no_context = []
                        new_no_context = []
                        
                        for occ in occurrences['old']:
                            if occ['context']:
                                old_by_context[occ['context']] = occ
                            else:
                                old_no_context.append(occ)
                        
                        for occ in occurrences['new']:
                            if occ['context']:
                                new_by_context[occ['context']] = occ
                            else:
                                new_no_context.append(occ)
                        
                        # Display matched contexts side-by-side
                        all_contexts = sorted(set(old_by_context.keys()) | set(new_by_context.keys()))
                        
                        if all_contexts:
                            summary.append("  Context-matched values:")
                            summary.append("  " + "-" * 80)
                            summary.append(f"  {'Context':<40} | {'Old Value':<15} | {'New Value':<15}")
                            summary.append("  " + "-" * 80)
                            
                            for context in all_contexts:
                                old_val = old_by_context.get(context, {}).get('value', 'N/A')
                                new_val = new_by_context.get(context, {}).get('value', 'N/A')
                                old_line = old_by_context.get(context, {}).get('line_number', '')
                                new_line = new_by_context.get(context, {}).get('line_number', '')
                                
                                # Mark changes
                                if old_val != new_val and old_val != 'N/A' and new_val != 'N/A':
                                    marker = " *"
                                else:
                                    marker = ""
                                
                                summary.append(f"  {context:<40} | {old_val:<15} | {new_val:<15}{marker}")
                                if old_line or new_line:
                                    old_line_str = f"Line {old_line}" if old_line else ""
                                    new_line_str = f"Line {new_line}" if new_line else ""
                                    summary.append(f"  {'':<40} | {old_line_str:<15} | {new_line_str:<15}")
                        
                        # Display unmatched values
                        if old_no_context or new_no_context:
                            summary.append("\n  Values without context:")
                            if old_no_context:
                                summary.append("  Old file only:")
                                for occ in old_no_context:
                                    summary.append(f"    Line {occ['line_number']}: {occ['value']}")
                            if new_no_context:
                                summary.append("  New file only:")
                                for occ in new_no_context:
                                    summary.append(f"    Line {occ['line_number']}: {occ['value']}")
        
        return '\n'.join(summary)


def main():
    parser = argparse.ArgumentParser(
        description='Compare two GWF files and analyze their differences',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Example usage:
  %(prog)s file1.gwf file2.gwf
  %(prog)s old_version.gwf new_version.gwf --output report.txt
  %(prog)s file1.gwf file2.gwf --structures-only
  %(prog)s file1.gwf file2.gwf -s -v
  %(prog)s file1.gwf file2.gwf -s --suppress-warnings
        """
    )
    
    parser.add_argument('file1', help='First GWF file to compare')
    parser.add_argument('file2', help='Second GWF file to compare')
    parser.add_argument('-o', '--output', help='Output file for the report (default: print to stdout)')
    parser.add_argument('-v', '--verbose', action='count', default=0,
                       help='Increase verbosity level (use -v for moderate detail, -vv for full detail)')
    parser.add_argument('-s', '--structures-only', action='store_true',
                       help='Analyze only structure descriptions (FrSH/FrSE entries)')
    parser.add_argument('--suppress-warnings', action='store_true',
                       help='Suppress non-critical warnings (show only critical errors)')
    
    args = parser.parse_args()
    
    # Validate input files
    if not os.path.exists(args.file1):
        print(f"Error: File '{args.file1}' not found", file=sys.stderr)
        sys.exit(1)
    if not os.path.exists(args.file2):
        print(f"Error: File '{args.file2}' not found", file=sys.stderr)
        sys.exit(1)
    
    # Check if framecpp_dump_objects is available
    try:
        subprocess.run(['framecpp_dump_objects', '--help'], 
                      stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
    except (subprocess.CalledProcessError, FileNotFoundError):
        print("Error: framecpp_dump_objects not found. Please ensure it's installed and in PATH", 
              file=sys.stderr)
        sys.exit(1)
    
    # Create analyzer and process files
    analyzer = GWFDiffAnalyzer(args.file1, args.file2, verbosity=args.verbose)
    
    success, message = analyzer.process_gwf_files()
    if not success:
        print(f"Error: {message}", file=sys.stderr)
        sys.exit(1)
    
    if args.verbose >= 1:
        print(message)
    
    # Generate and output summary
    summary = analyzer.generate_summary(structures_only=args.structures_only, suppress_warnings=args.suppress_warnings)
    
    if args.output:
        with open(args.output, 'w') as f:
            f.write(summary)
        print(f"Report written to {args.output}")
    else:
        print(summary)


if __name__ == '__main__':
    main()