#!/usr/bin/env python3
"""
Script to align all spacing to 8px grid for Action 11.2.1.3

This script:
1. Finds all non-8px-aligned spacing values (1, 3, 5, 10, 20)
2. Replaces them with nearest 8px-aligned values
3. Preserves necessary fine-tuning (4px values in specific contexts)
"""

import os
import re
import sys
from pathlib import Path
from typing import List, Tuple

# Non-8px-aligned spacing values and their replacements
# Note: We're conservative with 4px (spacing-1) values as they might be intentional fine-tuning
SPACING_REPLACEMENTS = {
    # 12px (spacing-3) → 16px (better spacing)
    'gap-3': 'gap-4',  # 12px → 16px
    'p-3': 'p-4',      # 12px → 16px
    'm-3': 'm-4',      # 12px → 16px
    'px-3': 'px-4',
    'py-3': 'py-4',
    'mx-3': 'mx-4',
    'my-3': 'my-4',
    'space-x-3': 'space-x-4',
    'space-y-3': 'space-y-4',
    
    # 20px (spacing-5) → 16px or 24px (prefer 24px for better spacing)
    'gap-5': 'gap-6',  # 20px → 24px
    'p-5': 'p-6',      # 20px → 24px
    'm-5': 'm-6',      # 20px → 24px
    'px-5': 'px-6',
    'py-5': 'py-6',
    'mx-5': 'mx-6',
    'my-5': 'my-6',
    'space-x-5': 'space-x-6',
    'space-y-5': 'space-y-6',
    
    # 40px (spacing-10) → 32px or 48px (prefer 48px for better spacing)
    'gap-10': 'gap-12',  # 40px → 48px
    'p-10': 'p-12',      # 40px → 48px
    'm-10': 'm-12',      # 40px → 48px
    'px-10': 'px-12',
    'py-10': 'py-12',
    'mx-10': 'mx-12',
    'my-10': 'my-12',
    'space-x-10': 'space-x-12',
    'space-y-10': 'space-y-12',
    
    # 80px (spacing-20) → 64px or 96px (prefer 96px for better spacing)
    'gap-20': 'gap-24',  # 80px → 96px
    'p-20': 'p-24',      # 80px → 96px
    'm-20': 'm-24',      # 80px → 96px
    'px-20': 'px-24',
    'py-20': 'py-24',
    'mx-20': 'mx-24',
    'my-20': 'my-24',
    'space-x-20': 'space-x-24',
    'space-y-20': 'space-y-24',
}

# Patterns to preserve (contexts where non-8px spacing might be intentional)
PRESERVE_PATTERNS = [
    # Test files
    r'\.test\.',
    r'\.spec\.',
    # Documentation
    r'\.md',
    # Responsive breakpoints (sm:, md:, lg:) - these are intentional
    r'sm:gap-[135]|md:gap-[135]|lg:gap-[135]',
    r'sm:p-[135]|md:p-[135]|lg:p-[135]',
    r'sm:m-[135]|md:m-[135]|lg:m-[135]',
    # Arbitrary values that might be intentional
    r'gap-\[|p-\[|m-\[',
]

# Files/directories to skip
SKIP_PATTERNS = [
    'node_modules',
    '.git',
    '.build',
    'dist',
    'coverage',
    'test-reports',
    'EXHAUSTIVE_TODO_LIST.md',
    'GRID_SYSTEM.md',
    'SPACING_AUDIT_REPORT.md',
    'SPACING_GUIDE.md',
    'design-tokens.css',  # This file defines the spacing scale
]

def should_preserve(line: str, file_path: str) -> bool:
    """Check if this line should be preserved"""
    # Skip test and documentation files
    if any(pattern in file_path for pattern in SKIP_PATTERNS):
        return True
    
    # Check preserve patterns
    for pattern in PRESERVE_PATTERNS:
        if re.search(pattern, line, re.IGNORECASE):
            return True
    
    return False

def replace_spacing(file_path: Path, dry_run: bool = False) -> Tuple[int, int]:
    """Replace non-8px spacing in a file"""
    replaced = 0
    preserved = 0
    
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()
            original_content = content
        
        lines = content.split('\n')
        new_lines = []
        
        for line_num, line in enumerate(lines, 1):
            original_line = line
            
            # Check if should preserve
            if should_preserve(line, str(file_path)):
                # Count preserved instances
                for old in SPACING_REPLACEMENTS.keys():
                    if old in line:
                        preserved += 1
                        break
                new_lines.append(line)
                continue
            
            # Replace non-8px spacing
            line_replaced = False
            for old, new in SPACING_REPLACEMENTS.items():
                # Use word boundaries to avoid partial matches
                pattern = r'\b' + re.escape(old) + r'\b'
                if re.search(pattern, line):
                    line = re.sub(pattern, new, line)
                    if not line_replaced:
                        replaced += 1
                        line_replaced = True
            
            new_lines.append(line)
        
        new_content = '\n'.join(new_lines)
        
        if not dry_run and new_content != original_content:
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(new_content)
        
        return replaced, preserved
    
    except Exception as e:
        print(f"Error processing {file_path}: {e}", file=sys.stderr)
        return 0, 0

def main():
    """Main function"""
    import argparse
    
    parser = argparse.ArgumentParser(description='Align spacing to 8px grid')
    parser.add_argument('--dry-run', action='store_true', help='Show what would be changed without making changes')
    parser.add_argument('--path', default='apps/web/src', help='Path to search (default: apps/web/src)')
    args = parser.parse_args()
    
    base_path = Path(args.path)
    if not base_path.exists():
        print(f"Error: Path {base_path} does not exist", file=sys.stderr)
        sys.exit(1)
    
    # Find all TypeScript/TSX files
    tsx_files = []
    for ext in ['*.tsx', '*.ts']:
        tsx_files.extend(base_path.rglob(ext))
    
    # Filter out skipped files
    files_to_process = []
    for file_path in tsx_files:
        if not any(pattern in str(file_path) for pattern in SKIP_PATTERNS):
            files_to_process.append(file_path)
    
    print(f"Found {len(files_to_process)} files to process")
    if args.dry_run:
        print("DRY RUN MODE - No files will be modified\n")
    
    total_replaced = 0
    total_preserved = 0
    files_modified = []
    
    for file_path in sorted(files_to_process):
        replaced, preserved = replace_spacing(file_path, dry_run=args.dry_run)
        if replaced > 0:
            total_replaced += replaced
            total_preserved += preserved
            files_modified.append((file_path, replaced, preserved))
            status = "[DRY RUN] Would modify" if args.dry_run else "Modified"
            print(f"{status}: {file_path} ({replaced} replaced, {preserved} preserved)")
    
    print(f"\n{'='*60}")
    print(f"Total files {'would be ' if args.dry_run else ''}modified: {len(files_modified)}")
    print(f"Total instances replaced: {total_replaced}")
    print(f"Total instances preserved: {total_preserved}")
    print(f"{'='*60}")
    
    if args.dry_run and len(files_modified) > 0:
        print("\nRun without --dry-run to apply changes")

if __name__ == '__main__':
    main()
