"""
SessionValidator - Comprehensive validation agent for Session View productions.

Validates Session View productions across four critical dimensions:
1. BPM Coherence - All samples within ±5 BPM of project tempo
2. Key Harmony - All MIDI clips use correct key/scale
3. Sample Rotation - No consecutive scenes use same sample
4. Energy Matching - Sample RMS matches scene energy requirements

This validator ensures professional-grade consistency across all scenes
and provides detailed error reporting for issues that need correction.
"""

from typing import Dict, List, Tuple, Optional, Any
from dataclasses import dataclass, field
import logging

logger = logging.getLogger(__name__)


@dataclass
class ValidationResult:
    """Result of a single validation check."""
    name: str
    score: float
    passed: bool
    details: List[Dict[str, Any]] = field(default_factory=list)
    violations: List[Dict[str, Any]] = field(default_factory=list)
    recommendations: List[str] = field(default_factory=list)


class SessionValidator:
    """
    Comprehensive validation agent for Session View productions.
    
    Validates productions across four critical dimensions:
    
    1. **BPM Coherence**: Ensures all loaded audio samples are within
       ±5 BPM tolerance of the project tempo for tight rhythmic consistency.
    
    2. **Key Harmony**: Verifies all MIDI clips (chords, bass, melody) use
       notes that belong to the specified musical key/scale.
    
    3. **Sample Rotation**: Checks that consecutive scenes don't use the
       same sample, preventing repetitive timbres and maintaining variety.
    
    4. **Energy Matching**: Validates that sample RMS levels match the
       expected energy profile for each scene (intro=soft, chorus=hard, etc.)
    
    Attributes:
        song: Ableton Live song object from self.song()
        metadata_store: SampleMetadataStore instance for feature lookups
        tolerance_bpm: BPM tolerance for coherence checking (default 5.0)
        coherence_threshold: Minimum overall score for passing (default 0.85)
    """
    
    def __init__(self, song, metadata_store):
        """
        Initialize the Session Validator.
        
        Args:
            song: Ableton Live song object (from self.song())
            metadata_store: SampleMetadataStore instance for sample features
        """
        self.song = song
        self.ms = metadata_store
        self.tolerance_bpm = 5.0
        self.coherence_threshold = 0.85
        
        # Energy level definitions (RMS targets)
        self.energy_targets = {
            'soft': {'min': 0.0, 'max': 0.3, 'target': 0.2},
            'medium': {'min': 0.3, 'max': 0.7, 'target': 0.5},
            'hard': {'min': 0.7, 'max': 1.0, 'target': 0.85}
        }
        
        # Scene energy mapping (typical values)
        self.scene_energy_map = {
            'intro': 'soft',
            'verse': 'medium',
            'pre_chorus': 'medium',
            'chorus': 'hard',
            'bridge': 'medium',
            'outro': 'soft',
            'build': 'hard',
            'drop': 'hard'
        }
        
        # Valid scale notes per key (simplified for common reggaeton keys)
        self.key_scales = {
            'Am': ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
            'Cm': ['C', 'D', 'Eb', 'F', 'G', 'Ab', 'Bb'],
            'Dm': ['D', 'E', 'F', 'G', 'A', 'Bb', 'C'],
            'Gm': ['G', 'A', 'Bb', 'C', 'D', 'Eb', 'F'],
            'Em': ['E', 'F#', 'G', 'A', 'B', 'C', 'D'],
            'Fm': ['F', 'G', 'Ab', 'Bb', 'C', 'Db', 'Eb'],
            'Bm': ['B', 'C#', 'D', 'E', 'F#', 'G', 'A'],
            'C': ['C', 'D', 'E', 'F', 'G', 'A', 'B'],
            'D': ['D', 'E', 'F#', 'G', 'A', 'B', 'C#'],
            'G': ['G', 'A', 'B', 'C', 'D', 'E', 'F#'],
            'E': ['E', 'F#', 'G#', 'A', 'B', 'C#', 'D#'],
            'F': ['F', 'G', 'A', 'Bb', 'C', 'D', 'E'],
            'A': ['A', 'B', 'C#', 'D', 'E', 'F#', 'G#'],
        }
        
        # MIDI note to note name mapping
        self.note_names = {
            0: 'C', 1: 'C#', 2: 'D', 3: 'D#', 4: 'E', 5: 'F',
            6: 'F#', 7: 'G', 8: 'G#', 9: 'A', 10: 'A#', 11: 'B'
        }
    
    def validate_production(self, target_bpm: float, key: str, num_scenes: int) -> Dict[str, Any]:
        """
        Perform full validation of Session View production.
        
        Runs all four validation checks and calculates an overall quality score.
        
        Args:
            target_bpm: Project tempo in BPM
            key: Musical key (e.g., "Am", "Cm", "Dm")
            num_scenes: Number of scenes to validate
            
        Returns:
            Dictionary containing:
            - bpm_coherence: ValidationResult
            - key_harmony: ValidationResult
            - sample_rotation: ValidationResult
            - energy_matching: ValidationResult
            - overall_score: Average of all scores (0.0-1.0)
            - passed: True if overall_score >= 0.85
            - summary: Human-readable summary of results
        """
        logger.info(f"Starting Session View validation: {target_bpm} BPM, {key}, {num_scenes} scenes")
        
        results = {
            'bpm_coherence': self._validate_bpm_coherence(target_bpm),
            'key_harmony': self._validate_key_harmony(key),
            'sample_rotation': self._validate_sample_rotation(num_scenes),
            'energy_matching': self._validate_energy_matching(num_scenes, target_bpm),
        }
        
        # Calculate overall score
        scores = [r['score'] for r in results.values()]
        overall_score = sum(scores) / len(scores)
        
        results['overall_score'] = overall_score
        results['passed'] = overall_score >= self.coherence_threshold
        
        # Generate summary
        results['summary'] = self._generate_summary(results, target_bpm, key, num_scenes)
        
        # Log results
        status = "PASSED" if results['passed'] else "FAILED"
        logger.info(f"Validation {status}: Overall score = {overall_score:.2f}")
        
        return results
    
    def _validate_bpm_coherence(self, target_bpm: float, tolerance: float = 5.0) -> Dict[str, Any]:
        """
        Check all audio clips are within BPM tolerance of project tempo.
        
        Iterates through all tracks and clip slots in Session View,
        extracts sample paths, and queries metadata store for BPM values.
        
        Args:
            target_bpm: Project tempo in BPM
            tolerance: Acceptable deviation in BPM (default 5.0)
            
        Returns:
            ValidationResult with:
            - score: Percentage of samples within tolerance
            - details: List of all checked samples with BPM values
            - violations: Samples outside tolerance
            - recommendations: How to fix BPM issues
        """
        details = []
        violations = []
        recommendations = []
        
        # Get all tracks from Session View
        tracks = self.song.tracks
        samples_checked = 0
        samples_valid = 0
        
        for track_idx in range(len(tracks)):
            track = tracks[track_idx]
            track_name = track.name
            
            # Get clip slots from Session View
            clip_slots = track.clip_slots
            
            for slot_idx in range(len(clip_slots)):
                clip_slot = clip_slots[slot_idx]
                
                # Skip empty slots
                if not clip_slot.has_clip:
                    continue
                
                clip = clip_slot.clip
                
                # Only check audio clips (not MIDI)
                if not clip.is_audio_clip:
                    continue
                
                # Get sample path from clip
                try:
                    sample_path = clip.sample_name
                    
                    if not sample_path:
                        continue
                    
                    samples_checked += 1
                    
                    # Query metadata store for BPM
                    sample_data = self.ms.get_sample_by_path(sample_path)
                    
                    if sample_data and sample_data.get('bpm'):
                        sample_bpm = sample_data['bpm']
                        deviation = abs(sample_bpm - target_bpm)
                        is_valid = deviation <= tolerance
                        
                        detail = {
                            'track': track_name,
                            'slot': slot_idx,
                            'sample': sample_path.split('/')[-1],
                            'sample_bpm': sample_bpm,
                            'target_bpm': target_bpm,
                            'deviation': deviation,
                            'valid': is_valid
                        }
                        
                        details.append(detail)
                        
                        if is_valid:
                            samples_valid += 1
                        else:
                            violations.append(detail)
                    else:
                        # BPM not in metadata store
                        detail = {
                            'track': track_name,
                            'slot': slot_idx,
                            'sample': sample_path.split('/')[-1],
                            'sample_bpm': None,
                            'target_bpm': target_bpm,
                            'deviation': None,
                            'valid': True,  # Assume valid if unknown
                            'warning': 'BPM not found in metadata store'
                        }
                        details.append(detail)
                        samples_valid += 1
                        
                except Exception as e:
                    logger.warning(f"Error checking BPM for clip at track {track_idx}, slot {slot_idx}: {e}")
        
        # Calculate score
        score = samples_valid / samples_checked if samples_checked > 0 else 1.0
        
        # Generate recommendations
        if violations:
            recommendations.append(
                f"Found {len(violations)} samples outside ±{tolerance} BPM tolerance"
            )
            recommendations.append(
                "Consider warping clips to match project tempo or selecting different samples"
            )
            
            # List specific violations
            for v in violations[:5]:  # Show first 5
                recommendations.append(
                    f"  - {v['sample']}: {v['sample_bpm']:.1f} BPM (deviation: {v['deviation']:.1f})"
                )
        
        return {
            'name': 'BPM Coherence',
            'score': score,
            'passed': score >= self.coherence_threshold,
            'details': details,
            'violations': violations,
            'recommendations': recommendations,
            'samples_checked': samples_checked,
            'samples_valid': samples_valid
        }
    
    def _validate_key_harmony(self, key: str) -> Dict[str, Any]:
        """
        Check all MIDI clips use notes from the correct key/scale.
        
        Validates chord progressions, bass root notes, and melody lines
        against the specified musical key.
        
        Args:
            key: Musical key (e.g., "Am", "Cm", "Dm")
            
        Returns:
            ValidationResult with:
            - score: Percentage of MIDI clips using correct notes
            - details: List of checked clips with note analysis
            - violations: Clips with out-of-key notes
            - recommendations: How to fix harmony issues
        """
        details = []
        violations = []
        recommendations = []
        
        # Get valid notes for this key
        valid_notes = self.key_scales.get(key, [])
        
        if not valid_notes:
            logger.warning(f"Unknown key: {key}. Using default Am scale.")
            valid_notes = self.key_scales['Am']
        
        tracks = self.song.tracks
        clips_checked = 0
        clips_valid = 0
        
        for track_idx in range(len(tracks)):
            track = tracks[track_idx]
            track_name = track.name
            
            # Determine track type from name
            track_type = self._infer_track_type(track_name)
            
            # Get clip slots
            clip_slots = track.clip_slots
            
            for slot_idx in range(len(clip_slots)):
                clip_slot = clip_slots[slot_idx]
                
                # Skip empty slots
                if not clip_slot.has_clip:
                    continue
                
                clip = clip_slot.clip
                
                # Only check MIDI clips
                if not clip.is_midi_clip:
                    continue
                
                clips_checked += 1
                
                try:
                    # Get MIDI notes from clip
                    midi_notes = self._extract_midi_notes(clip)
                    
                    # Check each note against key
                    out_of_key_notes = []
                    
                    for note in midi_notes:
                        pitch = note.get('pitch', 0)
                        note_name = self.note_names.get(pitch % 12, 'Unknown')
                        
                        if note_name not in valid_notes:
                            out_of_key_notes.append({
                                'pitch': pitch,
                                'note_name': note_name,
                                'position': note.get('start_time', 0)
                            })
                    
                    is_valid = len(out_of_key_notes) == 0
                    
                    detail = {
                        'track': track_name,
                        'track_type': track_type,
                        'slot': slot_idx,
                        'clip': clip.name,
                        'total_notes': len(midi_notes),
                        'out_of_key_notes': len(out_of_key_notes),
                        'valid': is_valid
                    }
                    
                    if out_of_key_notes:
                        detail['violations'] = out_of_key_notes
                    
                    details.append(detail)
                    
                    if is_valid:
                        clips_valid += 1
                    else:
                        violations.append(detail)
                        
                except Exception as e:
                    logger.warning(f"Error checking harmony for clip at track {track_idx}, slot {slot_idx}: {e}")
                    clips_valid += 1  # Assume valid on error
        
        # Calculate score
        score = clips_valid / clips_checked if clips_checked > 0 else 1.0
        
        # Generate recommendations
        if violations:
            recommendations.append(
                f"Found {len(violations)} MIDI clips with out-of-key notes in {key}"
            )
            recommendations.append(
                "Consider transposing notes to fit the key or using scale-constrained MIDI generation"
            )
            
            # List specific violations
            for v in violations[:5]:  # Show first 5
                if v.get('violations'):
                    bad_notes = [f"{vn['note_name']}{vn['pitch']}" for vn in v['violations'][:3]]
                    recommendations.append(
                        f"  - {v['track']}: {len(v['violations'])} out-of-key notes ({', '.join(bad_notes)})"
                    )
        
        return {
            'name': 'Key Harmony',
            'score': score,
            'passed': score >= self.coherence_threshold,
            'details': details,
            'violations': violations,
            'recommendations': recommendations,
            'clips_checked': clips_checked,
            'clips_valid': clips_valid,
            'key': key,
            'valid_notes': valid_notes
        }
    
    def _validate_sample_rotation(self, num_scenes: int) -> Dict[str, Any]:
        """
        Check no consecutive scenes use the same sample.
        
        For each track category (drums, bass, chords, etc.), verifies that
        scene N and scene N+1 don't use identical samples to maintain variety.
        
        Args:
            num_scenes: Number of scenes to validate
            
        Returns:
            ValidationResult with:
            - score: Percentage of scene transitions without repetition
            - details: Sample usage per scene
            - violations: Consecutive scenes with same sample
            - recommendations: How to improve variety
        """
        details = []
        violations = []
        recommendations = []
        
        tracks = self.song.tracks
        scene_sample_map = {}  # {scene_idx: {track_idx: sample_path}}
        transitions_checked = 0
        transitions_valid = 0
        
        # Build scene → sample mapping
        for scene_idx in range(num_scenes):
            scene_sample_map[scene_idx] = {}
            
            for track_idx in range(len(tracks)):
                track = tracks[track_idx]
                clip_slots = track.clip_slots
                
                # Get clip at this scene
                if scene_idx < len(clip_slots):
                    clip_slot = clip_slots[scene_idx]
                    
                    if clip_slot.has_clip:
                        clip = clip_slot.clip
                        
                        # Get sample path (audio) or pattern info (MIDI)
                        if clip.is_audio_clip:
                            sample_path = clip.sample_name
                            if sample_path:
                                scene_sample_map[scene_idx][track_idx] = sample_path
                        else:
                            # For MIDI, use clip name as identifier
                            scene_sample_map[scene_idx][track_idx] = f"MIDI:{clip.name}"
        
        # Check consecutive scenes for repetition
        for scene_idx in range(num_scenes - 1):
            current_scene = scene_sample_map.get(scene_idx, {})
            next_scene = scene_sample_map.get(scene_idx + 1, {})
            
            # Find common tracks between scenes
            common_tracks = set(current_scene.keys()) & set(next_scene.keys())
            
            for track_idx in common_tracks:
                transitions_checked += 1
                
                current_sample = current_scene[track_idx]
                next_sample = next_scene[track_idx]
                
                # Check if samples are identical
                if current_sample == next_sample:
                    # Find track name
                    track_name = tracks[track_idx].name if track_idx < len(tracks) else f"Track {track_idx}"
                    
                    violation = {
                        'transition': f"Scene {scene_idx} → Scene {scene_idx + 1}",
                        'track': track_name,
                        'track_index': track_idx,
                        'sample': current_sample.split('/')[-1] if '/' in current_sample else current_sample,
                        'type': 'consecutive_repetition'
                    }
                    
                    violations.append(violation)
                else:
                    transitions_valid += 1
        
        # Calculate score
        score = transitions_valid / transitions_checked if transitions_checked > 0 else 1.0
        
        # Generate recommendations
        if violations:
            recommendations.append(
                f"Found {len(violations)} instances of consecutive scene repetition"
            )
            recommendations.append(
                "Use sample rotation to vary timbres between adjacent scenes"
            )
            
            # List specific violations
            for v in violations[:5]:
                recommendations.append(
                    f"  - {v['transition']} on {v['track']}: {v['sample']}"
                )
        
        return {
            'name': 'Sample Rotation',
            'score': score,
            'passed': score >= self.coherence_threshold,
            'details': details,
            'violations': violations,
            'recommendations': recommendations,
            'transitions_checked': transitions_checked,
            'transitions_valid': transitions_valid,
            'scenes_analyzed': num_scenes
        }
    
    def _validate_energy_matching(self, num_scenes: int, target_bpm: float) -> Dict[str, Any]:
        """
        Check sample RMS levels match expected scene energy.
        
        Compares actual sample RMS (from metadata store) against expected
        energy targets for each scene type (intro=soft, chorus=hard, etc.)
        
        Args:
            num_scenes: Number of scenes to validate
            target_bpm: Project tempo for context
            
        Returns:
            ValidationResult with:
            - score: Percentage of samples matching energy targets
            - details: RMS analysis per sample
            - violations: Samples with mismatched energy
            - recommendations: How to fix energy issues
        """
        details = []
        violations = []
        recommendations = []
        
        tracks = self.song.tracks
        samples_checked = 0
        samples_matched = 0
        
        # Define expected energy per scene index (default pattern)
        scene_energy_patterns = {
            0: 'soft',    # Intro
            1: 'medium',  # Verse
            2: 'medium',  # Verse
            3: 'medium',  # Pre-chorus
            4: 'hard',    # Chorus
            5: 'hard',    # Chorus
            6: 'medium',  # Bridge
            7: 'hard',    # Final chorus
        }
        
        for scene_idx in range(num_scenes):
            expected_energy_level = scene_energy_patterns.get(scene_idx, 'medium')
            energy_target = self.energy_targets[expected_energy_level]
            
            for track_idx in range(len(tracks)):
                track = tracks[track_idx]
                clip_slots = track.clip_slots
                
                if scene_idx < len(clip_slots):
                    clip_slot = clip_slots[scene_idx]
                    
                    if clip_slot.has_clip:
                        clip = clip_slot.clip
                        
                        # Only check audio clips
                        if not clip.is_audio_clip:
                            continue
                        
                        samples_checked += 1
                        
                        try:
                            sample_path = clip.sample_name
                            
                            if not sample_path:
                                continue
                            
                            # Query metadata store for RMS
                            sample_data = self.ms.get_sample_by_path(sample_path)
                            
                            if sample_data and sample_data.get('rms') is not None:
                                sample_rms = sample_data['rms']
                                
                                # Normalize RMS to 0.0-1.0 range (typical RMS is 0.0-0.5)
                                normalized_rms = min(1.0, sample_rms * 2.0)
                                
                                # Check if RMS matches expected energy
                                is_match = (
                                    energy_target['min'] <= normalized_rms <= energy_target['max']
                                )
                                
                                detail = {
                                    'scene': scene_idx,
                                    'track': track.name,
                                    'sample': sample_path.split('/')[-1],
                                    'expected_energy': expected_energy_level,
                                    'expected_rms_range': f"{energy_target['min']:.2f}-{energy_target['max']:.2f}",
                                    'actual_rms': normalized_rms,
                                    'matched': is_match
                                }
                                
                                details.append(detail)
                                
                                if is_match:
                                    samples_matched += 1
                                else:
                                    violations.append(detail)
                            else:
                                # RMS not in metadata store
                                samples_matched += 1  # Assume match if unknown
                                
                        except Exception as e:
                            logger.warning(f"Error checking energy for scene {scene_idx}, track {track_idx}: {e}")
                            samples_matched += 1
        
        # Calculate score
        score = samples_matched / samples_checked if samples_checked > 0 else 1.0
        
        # Generate recommendations
        if violations:
            recommendations.append(
                f"Found {len(violations)} samples with mismatched energy levels"
            )
            recommendations.append(
                "Select samples with appropriate dynamics for each section"
            )
            recommendations.append(
                "Use gain staging or compression to adjust sample energy"
            )
            
            # List specific violations
            for v in violations[:5]:
                recommendations.append(
                    f"  - Scene {v['scene']}/{v['track']}: {v['sample']} "
                    f"(RMS: {v['actual_rms']:.2f}, expected: {v['expected_rms_range']})"
                )
        
        return {
            'name': 'Energy Matching',
            'score': score,
            'passed': score >= self.coherence_threshold,
            'details': details,
            'violations': violations,
            'recommendations': recommendations,
            'samples_checked': samples_checked,
            'samples_matched': samples_matched,
            'target_bpm': target_bpm
        }
    
    def _generate_summary(self, results: Dict, target_bpm: float, key: str, num_scenes: int) -> str:
        """Generate human-readable summary of validation results."""
        passed = results['passed']
        overall_score = results['overall_score']
        
        summary_lines = [
            f"Session View Validation Summary",
            f"================================",
            f"Configuration: {target_bpm} BPM | Key: {key} | {num_scenes} scenes",
            f"",
            f"Overall Score: {overall_score:.2f} ({'PASSED' if passed else 'FAILED'})",
            f"Threshold: {self.coherence_threshold:.2f}",
            f"",
            f"Category Scores:",
            f"  • BPM Coherence:     {results['bpm_coherence']['score']:.2f}",
            f"  • Key Harmony:       {results['key_harmony']['score']:.2f}",
            f"  • Sample Rotation:   {results['sample_rotation']['score']:.2f}",
            f"  • Energy Matching:   {results['energy_matching']['score']:.2f}",
        ]
        
        # Add violations summary
        total_violations = (
            len(results['bpm_coherence']['violations']) +
            len(results['key_harmony']['violations']) +
            len(results['sample_rotation']['violations']) +
            len(results['energy_matching']['violations'])
        )
        
        summary_lines.append(f"")
        summary_lines.append(f"Total Violations: {total_violations}")
        
        if total_violations > 0:
            summary_lines.append(f"")
            summary_lines.append(f"Recommendations:")
            
            all_recommendations = []
            for category in ['bpm_coherence', 'key_harmony', 'sample_rotation', 'energy_matching']:
                all_recommendations.extend(results[category]['recommendations'])
            
            for rec in all_recommendations[:10]:  # Limit to 10 recommendations
                summary_lines.append(f"  • {rec}")
        
        return "\n".join(summary_lines)
    
    def _infer_track_type(self, track_name: str) -> str:
        """Infer track type from track name."""
        name_lower = track_name.lower()
        
        if 'drum' in name_lower or 'kick' in name_lower or 'snare' in name_lower:
            return 'drums'
        elif 'bass' in name_lower:
            return 'bass'
        elif 'chord' in name_lower or 'pad' in name_lower:
            return 'chords'
        elif 'melody' in name_lower or 'lead' in name_lower or 'synth' in name_lower:
            return 'melody'
        elif 'fx' in name_lower or 'effect' in name_lower:
            return 'fx'
        elif 'perc' in name_lower:
            return 'percussion'
        else:
            return 'other'
    
    def _extract_midi_notes(self, clip) -> List[Dict[str, Any]]:
        """
        Extract MIDI notes from a clip.
        
        Args:
            clip: Ableton Live MIDI clip object
            
        Returns:
            List of dicts with pitch, start_time, duration, velocity
        """
        notes = []
        
        try:
            # Try to get notes from clip
            # This uses Ableton's API - may need adjustment based on actual implementation
            if hasattr(clip, 'notes'):
                midi_notes = clip.notes
                
                for note in midi_notes:
                    notes.append({
                        'pitch': note.pitch if hasattr(note, 'pitch') else note[0],
                        'start_time': note.start_time if hasattr(note, 'start_time') else note[1],
                        'duration': note.duration if hasattr(note, 'duration') else note[2],
                        'velocity': note.velocity if hasattr(note, 'velocity') else note[3]
                    })
        except Exception as e:
            logger.warning(f"Error extracting MIDI notes: {e}")
        
        return notes
    
    def get_detailed_report(self, results: Dict) -> str:
        """
        Generate detailed report from validation results.
        
        Args:
            results: Results dictionary from validate_production()
            
        Returns:
            Formatted string report with all details
        """
        lines = [
            "=" * 80,
            "SESSION VIEW VALIDATION - DETAILED REPORT",
            "=" * 80,
            "",
        ]
        
        for category in ['bpm_coherence', 'key_harmony', 'sample_rotation', 'energy_matching']:
            result = results[category]
            lines.extend([
                f"\n{result['name']}",
                "-" * len(result['name']),
                f"Score: {result['score']:.2f} ({'PASS' if result['passed'] else 'FAIL'})",
                f"Checked: {result.get('samples_checked', result.get('clips_checked', result.get('transitions_checked', 'N/A')))}",
                f"Valid: {result.get('samples_valid', result.get('clips_valid', result.get('transitions_valid', 'N/A')))}",
            ])
            
            if result['violations']:
                lines.append(f"\nViolations ({len(result['violations'])}):")
                for v in result['violations'][:10]:
                    lines.append(f"  • {v}")
            
            if result['recommendations']:
                lines.append(f"\nRecommendations:")
                for rec in result['recommendations']:
                    lines.append(f"  • {rec}")
        
        lines.extend([
            "",
            "=" * 80,
            f"OVERALL: {results['overall_score']:.2f} ({'PASSED' if results['passed'] else 'FAILED'})",
            "=" * 80,
        ])
        
        return "\n".join(lines)


def validate_session_production(song, metadata_store, target_bpm: float, key: str, num_scenes: int) -> Dict[str, Any]:
    """
    Convenience function for validating Session View production.
    
    Args:
        song: Ableton Live song object
        metadata_store: SampleMetadataStore instance
        target_bpm: Project tempo in BPM
        key: Musical key
        num_scenes: Number of scenes to validate
        
    Returns:
        Validation results dictionary
    """
    validator = SessionValidator(song, metadata_store)
    return validator.validate_production(target_bpm, key, num_scenes)
