"""
Quality Assurance Suite - Agente 19

Comprehensive audio quality validation for Ableton Live projects.
Provides professional-grade quality checks including:
- Mix validation (levels, phase, coherence)
- Frequency balance analysis
- Dynamic range measurement
- Phase issue detection
- Harmonic coherence checking
- Stereo balance analysis

Metrics and Thresholds:
- Clipping: peak > -0.1dB
- Headroom: peak < -6dB
- Stereo width: 0.8-1.2
- Coherence: > 0.80
"""
from __future__ import annotations

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

logger = logging.getLogger(__name__)

# Infinity value for dB calculations (must be defined before dataclass defaults)
inf = float('inf')


class QualityIssueType(Enum):
    """Types of quality issues that can be detected."""
    CLIPPING = "clipping"
    LOW_HEADROOM = "low_headroom"
    PHASE_ISSUE = "phase_issue"
    FREQUENCY_MASKING = "frequency_masking"
    MONO_COMPATIBILITY = "mono_compatibility"
    STEREO_IMBALANCE = "stereo_imbalance"
    DYNAMIC_RANGE_LOW = "dynamic_range_low"
    HARMONIC_INCOHERENCE = "harmonic_incoherence"
    TRUE_PEAK_OVER = "true_peak_over"
    BALANCE_ISSUE = "balance_issue"


@dataclass
class QualityIssue:
    """Represents a detected quality issue."""
    issue_type: QualityIssueType
    track_index: Optional[int]
    track_name: str
    severity: str  # "critical", "warning", "info"
    message: str
    value: float
    threshold: float
    recommendation: str


@dataclass
class MixMetrics:
    """Metrics for overall mix quality."""
    peak_db: float = -inf
    rms_db: float = -inf
    true_peak_db: float = -inf
    lufs_integrated: float = -inf
    dynamic_range_dr: float = 0.0
    headroom_db: float = 0.0
    stereo_width: float = 1.0
    phase_correlation: float = 1.0
    balance_center: float = 0.0
    coherence_score: float = 0.0


@dataclass
class TrackMetrics:
    """Metrics for individual track analysis."""
    track_index: int
    track_name: str
    peak_db: float = -inf
    rms_db: float = -inf
    true_peak_db: float = -inf
    pan: float = 0.0
    volume: float = 0.0
    muted: bool = False
    solo: bool = False
    
    # Frequency analysis (if available)
    spectral_balance: Optional[Dict[str, float]] = None
    dominant_freq_range: Optional[str] = None
    
    # Phase analysis
    phase_correlation: float = 1.0
    phase_issues: List[str] = field(default_factory=list)


@dataclass
class FrequencyBand:
    """Represents a frequency band for analysis."""
    name: str
    low_hz: float
    high_hz: float
    energy_db: float = -inf
    mask_risk: bool = False


@dataclass
class QualityReport:
    """Complete quality assurance report."""
    overall_score: float
    mix_metrics: MixMetrics
    track_metrics: List[TrackMetrics]
    issues: List[QualityIssue]
    frequency_bands: List[FrequencyBand]
    recommendations: List[str]
    passed_checks: List[str]
    timestamp: str = field(default_factory=lambda: __import__('datetime').datetime.now().isoformat())


# Constants for quality thresholds
THRESHOLDS = {
    "clipping_db": -0.1,
    "headroom_min_db": -6.0,
    "headroom_ideal_db": -3.0,
    "stereo_width_min": 0.8,
    "stereo_width_max": 1.2,
    "coherence_min": 0.80,
    "coherence_good": 0.85,
    "coherence_excellent": 0.90,
    "true_peak_max": -1.0,
    "dynamic_range_min": 8.0,  # DR value
    "phase_correlation_min": 0.5,
    "balance_tolerance": 0.2,  # L/R balance
}


# Frequency ranges for masking detection
FREQ_RANGES = {
    "sub_bass": (20, 60),
    "bass": (60, 250),
    "low_mid": (250, 500),
    "mid": (500, 2000),
    "high_mid": (2000, 4000),
    "high": (4000, 8000),
    "air": (8000, 20000),
}


def _linear_to_db(linear: float) -> float:
    """Convert linear amplitude to dB."""
    if linear <= 0:
        return -999.0
    return 20.0 * math.log10(linear)


def _db_to_linear(db: float) -> float:
    """Convert dB to linear amplitude."""
    return 10.0 ** (db / 20.0)


class QualityAssurance:
    """
    Professional quality assurance suite for Ableton Live projects.
    
    Provides comprehensive analysis of:
    - Mix levels and headroom
    - Phase coherence and correlation
    - Frequency balance and masking
    - Dynamic range
    - Stereo field balance
    - Harmonic coherence
    """
    
    def __init__(self, ableton_connection=None):
        """
        Initialize QualityAssurance.
        
        Args:
            ableton_connection: Optional connection to Ableton Live for real-time data
        """
        self.ableton = ableton_connection
        self.thresholds = THRESHOLDS.copy()
        self._issues: List[QualityIssue] = []
        self._track_metrics: List[TrackMetrics] = []
        self._mix_metrics = MixMetrics()
        
    def validate_mix(self, tracks_data: List[Dict[str, Any]]) -> Dict[str, Any]:
        """
        Validate overall mix quality - levels, phase, coherence.
        
        Args:
            tracks_data: List of track dictionaries with 'index', 'name', 'volume', 'pan', etc.
            
        Returns:
            Dictionary with validation results
        """
        self._issues = []
        self._track_metrics = []
        
        # Calculate track metrics
        for track in tracks_data:
            metrics = self._analyze_track(track)
            self._track_metrics.append(metrics)
        
        # Calculate mix-wide metrics
        self._mix_metrics = self._calculate_mix_metrics()
        
        # Run validations
        self._check_clipping()
        self._check_headroom()
        self._check_stereo_balance()
        self._check_phase_coherence()
        
        # Calculate overall score
        score = self._calculate_overall_score()
        
        return {
            "valid": len([i for i in self._issues if i.severity == "critical"]) == 0,
            "score": round(score, 2),
            "mix_metrics": {
                "peak_db": round(self._mix_metrics.peak_db, 2),
                "rms_db": round(self._mix_metrics.rms_db, 2),
                "true_peak_db": round(self._mix_metrics.true_peak_db, 2),
                "headroom_db": round(self._mix_metrics.headroom_db, 2),
                "stereo_width": round(self._mix_metrics.stereo_width, 3),
                "phase_correlation": round(self._mix_metrics.phase_correlation, 3),
                "coherence_score": round(self._mix_metrics.coherence_score, 3),
            },
            "issues": self._issues_to_dict(),
            "critical_count": len([i for i in self._issues if i.severity == "critical"]),
            "warning_count": len([i for i in self._issues if i.severity == "warning"]),
        }
    
    def analyze_frequency_balance(self, tracks_data: List[Dict[str, Any]]) -> Dict[str, Any]:
        """
        Analyze frequency balance and detect masking issues.
        
        Args:
            tracks_data: Track data with spectral information if available
            
        Returns:
            Dictionary with frequency analysis results
        """
        bands = []
        masking_issues = []
        
        # Simulate frequency analysis based on track types
        track_types = self._categorize_tracks_by_frequency(tracks_data)
        
        for band_name, (low, high) in FREQ_RANGES.items():
            # Calculate energy in this band
            energy_db = self._estimate_band_energy(band_name, track_types)
            
            # Check for masking risks
            mask_risk = self._check_masking_risk(band_name, track_types)
            
            band = FrequencyBand(
                name=band_name,
                low_hz=low,
                high_hz=high,
                energy_db=energy_db,
                mask_risk=mask_risk
            )
            bands.append(band)
            
            if mask_risk:
                masking_issues.append({
                    "band": band_name,
                    "frequency_range": f"{low}-{high}Hz",
                    "energy_db": round(energy_db, 2),
                    "conflicting_tracks": track_types.get(band_name, []),
                })
        
        # Determine balance quality
        balance_score = self._calculate_frequency_balance_score(bands)
        
        return {
            "balance_score": round(balance_score, 2),
            "bands": [
                {
                    "name": b.name,
                    "range_hz": f"{b.low_hz}-{b.high_hz}",
                    "energy_db": round(b.energy_db, 2),
                    "masking_risk": b.mask_risk,
                }
                for b in bands
            ],
            "masking_issues": masking_issues,
            "dominant_band": self._find_dominant_band(bands),
            "recommendations": self._generate_frequency_recommendations(bands, masking_issues),
        }
    
    def check_dynamic_range(self, tracks_data: List[Dict[str, Any]]) -> Dict[str, Any]:
        """
        Check dynamic range - LUFS, True Peak, DR measurement.
        
        Args:
            tracks_data: Track data for analysis
            
        Returns:
            Dictionary with dynamic range analysis
        """
        # Calculate estimated LUFS based on track volumes
        lufs = self._estimate_lufs(tracks_data)
        true_peak = self._estimate_true_peak(tracks_data)
        dr = self._estimate_dynamic_range(tracks_data)
        
        issues = []
        
        # Check true peak
        if true_peak > self.thresholds["true_peak_max"]:
            issues.append({
                "type": QualityIssueType.TRUE_PEAK_OVER.value,
                "severity": "critical",
                "value": round(true_peak, 2),
                "threshold": self.thresholds["true_peak_max"],
                "message": f"True peak {true_peak:.2f}dB exceeds {-1.0}dB limit",
                "recommendation": "Apply True Peak limiting on master track",
            })
        
        # Check dynamic range
        if dr < self.thresholds["dynamic_range_min"]:
            issues.append({
                "type": QualityIssueType.DYNAMIC_RANGE_LOW.value,
                "severity": "warning",
                "value": round(dr, 1),
                "threshold": self.thresholds["dynamic_range_min"],
                "message": f"Dynamic range DR{dr:.1f} is low (below {self.thresholds['dynamic_range_min']})",
                "recommendation": "Consider less compression or parallel processing",
            })
        
        # Loudness compliance checks
        loudness_compliance = self._check_loudness_compliance(lufs)
        
        return {
            "lufs_integrated": round(lufs, 2),
            "true_peak_db": round(true_peak, 2),
            "dynamic_range_dr": round(dr, 1),
            "compliance": loudness_compliance,
            "issues": issues,
            "passes_check": len(issues) == 0 and loudness_compliance["compliant"],
        }
    
    def detect_phase_issues(self, track_indices: List[int], 
                            tracks_data: List[Dict[str, Any]]) -> Dict[str, Any]:
        """
        Detect phase issues between tracks.
        
        Args:
            track_indices: Indices of tracks to check
            tracks_data: Full track data
            
        Returns:
            Dictionary with phase analysis results
        """
        phase_issues = []
        correlations = []
        
        # Check phase between pairs of tracks
        for i, idx1 in enumerate(track_indices):
            for idx2 in track_indices[i+1:]:
                track1 = self._get_track_by_index(idx1, tracks_data)
                track2 = self._get_track_by_index(idx2, tracks_data)
                
                if not track1 or not track2:
                    continue
                
                # Estimate phase correlation based on track types
                correlation = self._estimate_phase_correlation(track1, track2)
                correlations.append({
                    "track1": track1.get("name", f"Track {idx1}"),
                    "track2": track2.get("name", f"Track {idx2}"),
                    "correlation": round(correlation, 3),
                })
                
                if correlation < self.thresholds["phase_correlation_min"]:
                    phase_issues.append({
                        "type": QualityIssueType.PHASE_ISSUE.value,
                        "severity": "warning",
                        "tracks": [track1.get("name"), track2.get("name")],
                        "correlation": round(correlation, 3),
                        "message": f"Phase correlation {correlation:.3f} is low between tracks",
                        "recommendation": "Check polarity or adjust timing between tracks",
                    })
        
        # Check mono compatibility
        mono_compat = self._check_mono_compatibility(tracks_data)
        
        return {
            "correlations": correlations,
            "issues_found": len(phase_issues),
            "phase_issues": phase_issues,
            "mono_compatible": mono_compat["compatible"],
            "mono_compatibility_score": round(mono_compat["score"], 3),
            "worst_correlation": min([c["correlation"] for c in correlations]) if correlations else 1.0,
        }
    
    def check_harmonic_coherence(self, tracks_data: List[Dict[str, Any]],
                                   key: str = "Am") -> Dict[str, Any]:
        """
        Check harmonic coherence across tracks.
        
        Args:
            tracks_data: Track data
            key: Musical key to check against
            
        Returns:
            Dictionary with harmonic analysis results
        """
        coherence_scores = []
        
        for track in tracks_data:
            if track.get("is_midi", False):
                # MIDI tracks have better harmonic information
                score = self._estimate_midi_coherence(track, key)
            else:
                # Audio tracks - estimate from name/type
                score = self._estimate_audio_coherence(track, key)
            
            coherence_scores.append({
                "track_index": track.get("index", -1),
                "track_name": track.get("name", "Unknown"),
                "coherence": round(score, 3),
                "in_key": score > self.thresholds["coherence_min"],
            })
        
        # Calculate overall harmonic coherence
        if coherence_scores:
            avg_coherence = sum(s["coherence"] for s in coherence_scores) / len(coherence_scores)
        else:
            avg_coherence = 0.0
        
        issues = []
        if avg_coherence < self.thresholds["coherence_min"]:
            out_of_key = [s for s in coherence_scores if not s["in_key"]]
            issues.append({
                "type": QualityIssueType.HARMONIC_INCOHERENCE.value,
                "severity": "warning",
                "message": f"Low harmonic coherence: {avg_coherence:.3f}",
                "tracks_out_of_key": [s["track_name"] for s in out_of_key[:5]],
                "recommendation": "Check key of tracks or transpose to match project key",
            })
        
        return {
            "key": key,
            "average_coherence": round(avg_coherence, 3),
            "coherence_passes": avg_coherence >= self.thresholds["coherence_min"],
            "track_scores": coherence_scores,
            "issues": issues,
            "grade": self._grade_coherence(avg_coherence),
        }
    
    def analyze_stereo_balance(self, tracks_data: List[Dict[str, Any]]) -> Dict[str, Any]:
        """
        Analyze stereo balance across the mix.
        
        Args:
            tracks_data: Track data with pan information
            
        Returns:
            Dictionary with stereo analysis results
        """
        left_energy = 0.0
        right_energy = 0.0
        center_energy = 0.0
        
        for track in tracks_data:
            pan = track.get("panning", 0.5)  # 0.0 = left, 1.0 = right in Ableton
            # Convert to -1 to 1 range
            pan_balanced = (pan - 0.5) * 2.0
            volume = track.get("volume", 0.0)
            
            if abs(pan_balanced) < 0.1:
                center_energy += volume
            elif pan_balanced < 0:
                left_energy += volume * (1.0 - pan_balanced)
            else:
                right_energy += volume * (1.0 + pan_balanced)
        
        total = left_energy + right_energy + center_energy
        if total > 0:
            left_ratio = left_energy / total
            right_ratio = right_energy / total
            center_ratio = center_energy / total
        else:
            left_ratio = right_ratio = center_ratio = 0.0
        
        # Calculate balance deviation from center
        balance = left_ratio - right_ratio
        balance_score = 1.0 - abs(balance)
        
        issues = []
        if abs(balance) > self.thresholds["balance_tolerance"]:
            side = "left" if balance > 0 else "right"
            issues.append({
                "type": QualityIssueType.BALANCE_ISSUE.value,
                "severity": "warning",
                "message": f"Stereo imbalance: {side} side dominant ({abs(balance):.2f})",
                "balance_value": round(balance, 3),
                "recommendation": f"Adjust panning or levels to balance the {side} side",
            })
        
        return {
            "balance_center": round(balance, 3),
            "balance_score": round(balance_score, 3),
            "distribution": {
                "left": round(left_ratio, 3),
                "center": round(center_ratio, 3),
                "right": round(right_ratio, 3),
            },
            "passes_check": len(issues) == 0,
            "issues": issues,
        }
    
    def full_quality_check(self, tracks_data: List[Dict[str, Any]], 
                           key: str = "Am") -> QualityReport:
        """
        Run complete quality assurance suite.
        
        Args:
            tracks_data: All track data from Ableton
            key: Musical key for harmonic analysis
            
        Returns:
            Complete QualityReport
        """
        # Run all individual checks
        mix_result = self.validate_mix(tracks_data)
        freq_result = self.analyze_frequency_balance(tracks_data)
        dynamic_result = self.check_dynamic_range(tracks_data)
        phase_result = self.detect_phase_issues(
            [t.get("index", i) for i, t in enumerate(tracks_data)],
            tracks_data
        )
        harmonic_result = self.check_harmonic_coherence(tracks_data, key)
        stereo_result = self.analyze_stereo_balance(tracks_data)
        
        # Compile all issues
        all_issues = self._issues.copy()
        
        # Add issues from other checks
        for issue_dict in dynamic_result.get("issues", []):
            all_issues.append(self._dict_to_issue(issue_dict, None, "Mix"))
        for issue_dict in stereo_result.get("issues", []):
            all_issues.append(self._dict_to_issue(issue_dict, None, "Mix"))
        for issue_dict in harmonic_result.get("issues", []):
            all_issues.append(self._dict_to_issue(issue_dict, None, "Mix"))
        
        # Generate recommendations
        recommendations = self._generate_recommendations(
            mix_result, freq_result, dynamic_result, phase_result,
            harmonic_result, stereo_result
        )
        
        # Calculate overall score
        scores = [
            mix_result["score"],
            freq_result["balance_score"] * 100,
            100 if dynamic_result["passes_check"] else 70,
            100 if phase_result["mono_compatible"] else 60,
            harmonic_result["average_coherence"] * 100,
            stereo_result["balance_score"] * 100,
        ]
        overall_score = sum(scores) / len(scores) if scores else 0.0
        
        # Create frequency bands from analysis
        freq_bands = [
            FrequencyBand(
                name=b["name"],
                low_hz=int(b["range_hz"].split("-")[0]),
                high_hz=int(b["range_hz"].split("-")[1]),
                energy_db=b["energy_db"],
                mask_risk=b["masking_risk"]
            )
            for b in freq_result.get("bands", [])
        ]
        
        report = QualityReport(
            overall_score=round(overall_score, 2),
            mix_metrics=self._mix_metrics,
            track_metrics=self._track_metrics,
            issues=all_issues,
            frequency_bands=freq_bands,
            recommendations=recommendations,
            passed_checks=self._get_passed_checks(
                mix_result, dynamic_result, phase_result, stereo_result
            )
        )
        
        return report
    
    def suggest_improvements(self, tracks_data: List[Dict[str, Any]],
                             key: str = "Am") -> Dict[str, Any]:
        """
        Generate AI-style suggestions for project improvement.
        
        Args:
            tracks_data: Track data from Ableton
            key: Musical key
            
        Returns:
            Dictionary with prioritized suggestions
        """
        # Run quality check
        report = self.full_quality_check(tracks_data, key)
        
        suggestions = []
        
        # Critical issues first
        critical = [i for i in report.issues if i.severity == "critical"]
        for issue in critical:
            suggestions.append({
                "priority": "critical",
                "category": issue.issue_type.value,
                "message": issue.message,
                "action": issue.recommendation,
                "impact": "high",
            })
        
        # Warnings
        warnings = [i for i in report.issues if i.severity == "warning"]
        for issue in warnings:
            suggestions.append({
                "priority": "high" if issue.issue_type in [
                    QualityIssueType.PHASE_ISSUE,
                    QualityIssueType.TRUE_PEAK_OVER
                ] else "medium",
                "category": issue.issue_type.value,
                "message": issue.message,
                "action": issue.recommendation,
                "impact": "medium",
            })
        
        # General improvements based on score
        if report.overall_score < 70:
            suggestions.append({
                "priority": "high",
                "category": "general",
                "message": f"Overall quality score is low ({report.overall_score:.1f})",
                "action": "Review all quality issues and apply recommended fixes",
                "impact": "high",
            })
        elif report.overall_score < 85:
            suggestions.append({
                "priority": "medium",
                "category": "general",
                "message": f"Good quality, but room for improvement ({report.overall_score:.1f})",
                "action": "Address warnings to reach professional grade",
                "impact": "medium",
            })
        
        # Specific recommendations based on analysis
        if report.mix_metrics.headroom_db < 3.0:
            suggestions.append({
                "priority": "medium",
                "category": "headroom",
                "message": "Limited headroom on master track",
                "action": "Reduce track levels or apply gentle master compression",
                "impact": "medium",
            })
        
        if report.mix_metrics.stereo_width > 1.2:
            suggestions.append({
                "priority": "low",
                "category": "stereo_field",
                "message": "Very wide stereo image may cause mono compatibility issues",
                "action": "Check mono compatibility or reduce stereo widening",
                "impact": "low",
            })
        
        # Sort by priority
        priority_order = {"critical": 0, "high": 1, "medium": 2, "low": 3}
        suggestions.sort(key=lambda x: priority_order.get(x["priority"], 4))
        
        return {
            "overall_score": report.overall_score,
            "suggestion_count": len(suggestions),
            "critical_count": len([s for s in suggestions if s["priority"] == "critical"]),
            "suggestions": suggestions[:20],  # Limit to top 20
            "categories": self._categorize_suggestions(suggestions),
        }
    
    # -------------------------------------------------------------------------
    # Helper methods
    # -------------------------------------------------------------------------
    
    def _analyze_track(self, track: Dict[str, Any]) -> TrackMetrics:
        """Analyze a single track and return metrics."""
        volume = track.get("volume", 0.0)
        pan = track.get("panning", 0.5)
        
        # Convert to dB
        volume_db = _linear_to_db(volume) if volume > 0 else -inf
        
        return TrackMetrics(
            track_index=track.get("index", 0),
            track_name=track.get("name", "Unknown"),
            peak_db=volume_db,
            rms_db=volume_db - 6.0,  # Estimate RMS 6dB below peak
            true_peak_db=volume_db + 2.0,  # Estimate true peak
            pan=(pan - 0.5) * 2.0,  # Convert to -1..1
            volume=volume,
            muted=track.get("mute", False),
            solo=track.get("solo", False),
        )
    
    def _calculate_mix_metrics(self) -> MixMetrics:
        """Calculate mix-wide metrics from track metrics."""
        if not self._track_metrics:
            return MixMetrics()
        
        # Find loudest track
        max_peak = max((t.peak_db for t in self._track_metrics if not t.muted), default=-inf)
        
        # Estimate mix RMS (simplified)
        active_tracks = [t for t in self._track_metrics if not t.muted]
        if active_tracks:
            avg_rms = sum(t.rms_db for t in active_tracks) / len(active_tracks)
        else:
            avg_rms = -inf
        
        # Calculate headroom
        headroom = -0.1 - max_peak if max_peak > -999 else 0.0
        
        # Estimate stereo width from panning
        pan_variance = sum(t.pan ** 2 for t in active_tracks) / len(active_tracks) if active_tracks else 0
        stereo_width = 1.0 + pan_variance
        
        return MixMetrics(
            peak_db=max_peak,
            rms_db=avg_rms,
            true_peak_db=max_peak + 2.0,
            headroom_db=headroom,
            stereo_width=stereo_width,
            phase_correlation=1.0,  # Would need audio analysis
            coherence_score=0.85,  # Estimate
        )
    
    def _check_clipping(self):
        """Check for clipping issues."""
        for track in self._track_metrics:
            if track.muted:
                continue
            if track.peak_db > self.thresholds["clipping_db"]:
                self._issues.append(QualityIssue(
                    issue_type=QualityIssueType.CLIPPING,
                    track_index=track.track_index,
                    track_name=track.track_name,
                    severity="critical",
                    message=f"Track '{track.track_name}' is clipping ({track.peak_db:.2f}dB)",
                    value=track.peak_db,
                    threshold=self.thresholds["clipping_db"],
                    recommendation="Reduce track volume or apply gain staging",
                ))
    
    def _check_headroom(self):
        """Check master headroom."""
        if self._mix_metrics.headroom_db < 3.0:
            severity = "warning" if self._mix_metrics.headroom_db > 0 else "critical"
            self._issues.append(QualityIssue(
                issue_type=QualityIssueType.LOW_HEADROOM,
                track_index=None,
                track_name="Master",
                severity=severity,
                message=f"Low headroom: {self._mix_metrics.headroom_db:.2f}dB",
                value=self._mix_metrics.headroom_db,
                threshold=3.0,
                recommendation="Reduce overall mix level for better headroom",
            ))
    
    def _check_stereo_balance(self):
        """Check stereo field balance."""
        width = self._mix_metrics.stereo_width
        if width < self.thresholds["stereo_width_min"]:
            self._issues.append(QualityIssue(
                issue_type=QualityIssueType.STEREO_IMBALANCE,
                track_index=None,
                track_name="Mix",
                severity="info",
                message=f"Narrow stereo width: {width:.3f}",
                value=width,
                threshold=self.thresholds["stereo_width_min"],
                recommendation="Consider stereo widening or panning adjustments",
            ))
        elif width > self.thresholds["stereo_width_max"]:
            self._issues.append(QualityIssue(
                issue_type=QualityIssueType.STEREO_IMBALANCE,
                track_index=None,
                track_name="Mix",
                severity="warning",
                message=f"Very wide stereo width: {width:.3f} (may cause mono issues)",
                value=width,
                threshold=self.thresholds["stereo_width_max"],
                recommendation="Check mono compatibility",
            ))
    
    def _check_phase_coherence(self):
        """Check phase coherence across tracks."""
        # Simplified check - would need audio analysis for real implementation
        pass
    
    def _calculate_overall_score(self) -> float:
        """Calculate overall quality score."""
        base_score = 100.0
        
        # Deduct for critical issues
        critical_count = len([i for i in self._issues if i.severity == "critical"])
        base_score -= critical_count * 25.0
        
        # Deduct for warnings
        warning_count = len([i for i in self._issues if i.severity == "warning"])
        base_score -= warning_count * 10.0
        
        # Ensure within bounds
        return max(0.0, min(100.0, base_score))
    
    def _issues_to_dict(self) -> List[Dict[str, Any]]:
        """Convert issues to dictionary format."""
        return [
            {
                "type": i.issue_type.value,
                "track": i.track_name,
                "severity": i.severity,
                "message": i.message,
                "value": round(i.value, 3),
                "threshold": i.threshold,
                "recommendation": i.recommendation,
            }
            for i in self._issues
        ]
    
    def _categorize_tracks_by_frequency(self, tracks_data: List[Dict[str, Any]]) -> Dict[str, List[str]]:
        """Categorize tracks by their dominant frequency range."""
        categories = {k: [] for k in FREQ_RANGES.keys()}
        
        for track in tracks_data:
            name = track.get("name", "").lower()
            
            # Simple heuristics based on track name
            if any(k in name for k in ["kick", "sub", "808", "bass"]):
                categories["sub_bass"].append(name)
                categories["bass"].append(name)
            elif any(k in name for k in ["snare", "clap", "tom", "perc"]):
                categories["low_mid"].append(name)
                categories["mid"].append(name)
            elif any(k in name for k in ["hat", "cymbal", "crash", "ride", "shaker"]):
                categories["high_mid"].append(name)
                categories["high"].append(name)
            elif any(k in name for k in ["vocal", "lead", "melody", "synth", "pad"]):
                categories["mid"].append(name)
                categories["high_mid"].append(name)
            else:
                # Default to mid range
                categories["mid"].append(name)
        
        return categories
    
    def _estimate_band_energy(self, band_name: str, track_types: Dict[str, List[str]]) -> float:
        """Estimate energy in a frequency band."""
        # Simple estimation based on track count in that range
        track_count = len(track_types.get(band_name, []))
        # More tracks = higher energy (simplified)
        return -20.0 + (track_count * 3.0)
    
    def _check_masking_risk(self, band_name: str, track_types: Dict[str, List[str]]) -> bool:
        """Check if there's masking risk in a frequency band."""
        tracks = track_types.get(band_name, [])
        # Risk if more than 3 tracks in same frequency range
        return len(tracks) > 3
    
    def _calculate_frequency_balance_score(self, bands: List[FrequencyBand]) -> float:
        """Calculate frequency balance score."""
        if not bands:
            return 0.0
        
        # Check for masking issues
        mask_count = sum(1 for b in bands if b.mask_risk)
        
        # Calculate energy distribution variance
        energies = [b.energy_db for b in bands]
        if energies:
            avg_energy = sum(energies) / len(energies)
            variance = sum((e - avg_energy) ** 2 for e in energies) / len(energies)
            # Lower variance = better balance
            balance_score = max(0, 100 - variance) / 100.0
        else:
            balance_score = 0.5
        
        # Reduce score for masking issues
        return max(0, balance_score - (mask_count * 0.1))
    
    def _find_dominant_band(self, bands: List[FrequencyBand]) -> Optional[str]:
        """Find the dominant frequency band."""
        if not bands:
            return None
        dominant = max(bands, key=lambda b: b.energy_db)
        return dominant.name
    
    def _generate_frequency_recommendations(self, bands: List[FrequencyBand], 
                                            masking_issues: List[Dict]) -> List[str]:
        """Generate recommendations based on frequency analysis."""
        recommendations = []
        
        for issue in masking_issues:
            recommendations.append(
                f"Consider EQ carving in {issue['band']} range ({issue.get('frequency_range', 'unknown')}) "
                f"to reduce masking between conflicting tracks"
            )
        
        # Check for empty bands
        for band in bands:
            if band.energy_db < -40.0:
                recommendations.append(
                    f"{band.name} range ({band.low_hz}-{band.high_hz}Hz) has low energy - "
                    f"consider adding content or boosting if needed"
                )
        
        return recommendations[:5]  # Limit recommendations
    
    def _estimate_lufs(self, tracks_data: List[Dict[str, Any]]) -> float:
        """Estimate integrated LUFS from track data."""
        # Simplified estimation
        total_volume = sum(t.get("volume", 0.0) for t in tracks_data)
        if total_volume > 0:
            # Rough conversion to LUFS
            return _linear_to_db(total_volume) - 14.0  # Offset for LUFS vs peak
        return -70.0
    
    def _estimate_true_peak(self, tracks_data: List[Dict[str, Any]]) -> float:
        """Estimate true peak from track data."""
        max_volume = max((t.get("volume", 0.0) for t in tracks_data), default=0.0)
        peak_db = _linear_to_db(max_volume) if max_volume > 0 else -999.0
        # True peak typically 1-3dB above digital peak
        return peak_db + 2.0
    
    def _estimate_dynamic_range(self, tracks_data: List[Dict[str, Any]]) -> float:
        """Estimate dynamic range in DR format."""
        # Simplified DR estimation
        volumes = [t.get("volume", 0.0) for t in tracks_data if t.get("volume", 0.0) > 0]
        if len(volumes) > 1:
            db_values = [_linear_to_db(v) for v in volumes]
            peak = max(db_values)
            rms = sum(db_values) / len(db_values)
            # DR = peak - RMS (rough approximation)
            dr = peak - rms
            return max(1.0, dr)
        return 8.0  # Default moderate DR
    
    def _check_loudness_compliance(self, lufs: float) -> Dict[str, Any]:
        """Check loudness compliance for common standards."""
        standards = {
            "streaming": {"target": -14.0, "tolerance": 1.0},
            "cd": {"target": -9.0, "tolerance": 1.0},
            "broadcast": {"target": -23.0, "tolerance": 1.0},
        }
        
        results = {}
        for name, std in standards.items():
            diff = abs(lufs - std["target"])
            results[name] = {
                "target": std["target"],
                "actual": round(lufs, 2),
                "diff": round(diff, 2),
                "compliant": diff <= std["tolerance"],
            }
        
        return {
            "compliant": any(r["compliant"] for r in results.values()),
            "standards": results,
        }
    
    def _get_track_by_index(self, idx: int, tracks_data: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
        """Get track data by index."""
        for track in tracks_data:
            if track.get("index") == idx:
                return track
        return None
    
    def _estimate_phase_correlation(self, track1: Dict, track2: Dict) -> float:
        """Estimate phase correlation between two tracks."""
        # Simplified estimation based on track types
        name1 = track1.get("name", "").lower()
        name2 = track2.get("name", "").lower()
        
        # Kick and bass often have phase issues
        if ("kick" in name1 and "bass" in name2) or ("bass" in name1 and "kick" in name2):
            return 0.7  # Higher risk
        
        # Similar track types tend to correlate
        if any(x in name1 and x in name2 for x in ["hat", "perc", "tom"]):
            return 0.9
        
        return 0.95  # Default good correlation
    
    def _check_mono_compatibility(self, tracks_data: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Check if mix is mono compatible."""
        # Estimate based on stereo width
        wide_tracks = sum(1 for t in tracks_data if abs(t.get("panning", 0.5) - 0.5) > 0.3)
        
        score = 1.0 - (wide_tracks * 0.05)
        return {
            "compatible": score > 0.7,
            "score": max(0, score),
        }
    
    def _estimate_midi_coherence(self, track: Dict[str, Any], key: str) -> float:
        """Estimate harmonic coherence for MIDI tracks."""
        # Simplified - would analyze actual note data
        return 0.85
    
    def _estimate_audio_coherence(self, track: Dict[str, Any], key: str) -> float:
        """Estimate harmonic coherence for audio tracks."""
        # Simplified - would analyze actual audio
        name = track.get("name", "").lower()
        
        # Percussion is always coherent (no pitch)
        if any(x in name for x in ["kick", "snare", "hat", "perc", "clap"]):
            return 1.0
        
        # Assume other tracks are reasonably coherent
        return 0.8
    
    def _grade_coherence(self, score: float) -> str:
        """Convert coherence score to letter grade."""
        if score >= 0.95:
            return "A+"
        elif score >= 0.90:
            return "A"
        elif score >= 0.85:
            return "B+"
        elif score >= 0.80:
            return "B"
        elif score >= 0.70:
            return "C"
        else:
            return "D"
    
    def _dict_to_issue(self, d: Dict[str, Any], track_index: Optional[int], 
                       track_name: str) -> QualityIssue:
        """Convert dictionary to QualityIssue."""
        return QualityIssue(
            issue_type=QualityIssueType(d.get("type", "balance_issue")),
            track_index=track_index,
            track_name=track_name,
            severity=d.get("severity", "warning"),
            message=d.get("message", ""),
            value=d.get("value", 0.0),
            threshold=d.get("threshold", 0.0),
            recommendation=d.get("recommendation", ""),
        )
    
    def _generate_recommendations(self, mix: Dict, freq: Dict, dynamic: Dict,
                                   phase: Dict, harmonic: Dict, stereo: Dict) -> List[str]:
        """Generate comprehensive recommendations."""
        recs = []
        
        # From mix validation
        if mix.get("critical_count", 0) > 0:
            recs.append(f"Address {mix['critical_count']} critical issues immediately")
        
        # From frequency analysis
        for issue in freq.get("masking_issues", []):
            recs.append(f"Apply EQ to reduce masking in {issue['band']} range")
        
        # From dynamic range
        if not dynamic.get("passes_check", True):
            recs.append("Adjust compression to achieve better dynamic range")
        
        # From phase analysis
        if not phase.get("mono_compatible", True):
            recs.append("Improve mono compatibility by checking phase on wide tracks")
        
        # From stereo
        if not stereo.get("passes_check", True):
            recs.append("Balance stereo field by adjusting track panning")
        
        return recs[:10]
    
    def _get_passed_checks(self, mix: Dict, dynamic: Dict, 
                           phase: Dict, stereo: Dict) -> List[str]:
        """List checks that passed."""
        passed = []
        
        if mix.get("critical_count", 0) == 0:
            passed.append("No clipping detected")
        
        if mix.get("mix_metrics", {}).get("headroom_db", 0) > 3.0:
            passed.append("Adequate headroom")
        
        if dynamic.get("passes_check", False):
            passed.append("Dynamic range acceptable")
        
        if phase.get("mono_compatible", False):
            passed.append("Mono compatible")
        
        if stereo.get("passes_check", False):
            passed.append("Stereo balance good")
        
        return passed
    
    def _categorize_suggestions(self, suggestions: List[Dict]) -> Dict[str, int]:
        """Categorize suggestions by type."""
        cats = {}
        for s in suggestions:
            cat = s.get("category", "other")
            cats[cat] = cats.get(cat, 0) + 1
        return cats


# Module-level convenience functions
def validate_project_qa(tracks_data: List[Dict[str, Any]], key: str = "Am") -> Dict[str, Any]:
    """
    Quick quality validation function.
    
    Args:
        tracks_data: Track data from Ableton
        key: Musical key
        
    Returns:
        Quality validation results
    """
    qa = QualityAssurance()
    return qa.validate_mix(tracks_data)


def suggest_improvements_qa(tracks_data: List[Dict[str, Any]], key: str = "Am") -> Dict[str, Any]:
    """
    Quick suggestions function.
    
    Args:
        tracks_data: Track data from Ableton
        key: Musical key
        
    Returns:
        Improvement suggestions
    """
    qa = QualityAssurance()
    return qa.suggest_improvements(tracks_data, key)


