///|
pub(all) enum QualityFindingKind {
  QualityOk
  QualityClipping
  QualityMostlySilent
  QualityVeryQuiet
  QualityDcOffset
  QualityHighCrest
  QualityHighZeroCrossing
} derive(Eq, @debug.Debug)

///|
pub fn QualityFindingKind::label(self : QualityFindingKind) -> String {
  match self {
    QualityOk => "ok"
    QualityClipping => "clipping"
    QualityMostlySilent => "mostly-silent"
    QualityVeryQuiet => "very-quiet"
    QualityDcOffset => "dc-offset"
    QualityHighCrest => "high-crest"
    QualityHighZeroCrossing => "high-zero-crossing"
  }
}

///|
pub(all) struct QualityFinding {
  kind : QualityFindingKind
  severity : ValidationSeverity
  message : String
  value : Double
} derive(Eq, @debug.Debug)

///|
pub fn QualityFinding::new(
  kind : QualityFindingKind,
  severity : ValidationSeverity,
  message : String,
  value? : Double = 0.0,
) -> QualityFinding {
  { kind, severity, message, value }
}

///|
pub(all) struct QualityProfile {
  ok : Bool
  score : Int
  stats : AudioStats
  crest_factor : Double
  zero_crossing_rate : Double
  findings : Array[QualityFinding]
} derive(Eq, @debug.Debug)

///|
pub fn QualityProfile::empty() -> QualityProfile {
  {
    ok: false,
    score: 0,
    stats: AudioStats::empty(),
    crest_factor: 0.0,
    zero_crossing_rate: 0.0,
    findings: [],
  }
}

///|
pub fn QualityProfile::has_warnings(self : QualityProfile) -> Bool {
  for finding in self.findings {
    if finding.severity == SeverityWarning || finding.severity == SeverityError {
      break true
    }
  } nobreak {
    false
  }
}

///|
fn quality_penalty(finding : QualityFinding) -> Int {
  match finding.kind {
    QualityOk => 0
    QualityClipping => 30
    QualityMostlySilent => 20
    QualityVeryQuiet => 15
    QualityDcOffset => 15
    QualityHighCrest => 10
    QualityHighZeroCrossing => 8
  }
}

///|
fn final_quality_score(findings : Array[QualityFinding]) -> Int {
  let penalty = for finding in findings; value = 0 {
    continue value + quality_penalty(finding)
  } nobreak {
    value
  }
  let score = 100 - penalty
  if score < 0 {
    0
  } else {
    score
  }
}

///|
pub fn inspect_audio_quality(buffer : FloatBuffer) -> QualityProfile {
  if !buffer.is_valid() {
    QualityProfile::empty()
  } else {
    let stats = analyze_audio(buffer)
    let crest = crest_factor(buffer)
    let zcr = zero_crossing_rate(buffer)
    let findings : Array[QualityFinding] = []
    if stats.clipped_samples > 0 {
      findings.push(
        QualityFinding::new(
          QualityClipping,
          SeverityError,
          "samples reach or exceed clipping threshold",
          value=stats.clipped_samples.to_double(),
        ),
      )
    }
    if stats.silence_ratio() > 0.95 {
      findings.push(
        QualityFinding::new(
          QualityMostlySilent,
          SeverityWarning,
          "more than 95 percent of samples are near silence",
          value=stats.silence_ratio(),
        ),
      )
    } else if stats.peak < 0.02 {
      findings.push(
        QualityFinding::new(
          QualityVeryQuiet,
          SeverityWarning,
          "peak amplitude is very low",
          value=stats.peak,
        ),
      )
    }
    if abs_double(stats.dc_offset) > 0.05 {
      findings.push(
        QualityFinding::new(
          QualityDcOffset,
          SeverityWarning,
          "average sample value suggests DC offset",
          value=stats.dc_offset,
        ),
      )
    }
    if crest > 8.0 {
      findings.push(
        QualityFinding::new(
          QualityHighCrest,
          SeverityInfo,
          "crest factor is high; signal may have sharp transients",
          value=crest,
        ),
      )
    }
    if zcr > 0.65 {
      findings.push(
        QualityFinding::new(
          QualityHighZeroCrossing,
          SeverityInfo,
          "zero crossing rate is high",
          value=zcr,
        ),
      )
    }
    if findings.length() == 0 {
      findings.push(
        QualityFinding::new(
          QualityOk,
          SeverityInfo,
          "no common quality issue detected",
        ),
      )
    }
    {
      ok: findings[0].kind != QualityClipping,
      score: final_quality_score(findings),
      stats,
      crest_factor: crest,
      zero_crossing_rate: zcr,
      findings,
    }
  }
}

///|
pub fn quality_profile_to_text(profile : QualityProfile) -> String {
  if profile.findings.length() == 0 {
    "MoonWavKit quality: empty"
  } else {
    let header = "MoonWavKit quality\nscore: \{profile.score}\npeak: \{profile.stats.peak}\nrms: \{profile.stats.rms}"
    for finding in profile.findings; text = header {
      continue "\{text}\n- \{finding.severity.label()} \{finding.kind.label()}: \{finding.message}"
    } nobreak {
      text
    }
  }
}

///|
pub fn recommended_quality_action(profile : QualityProfile) -> String {
  if profile.findings.length() == 0 {
    "no-audio"
  } else {
    let first = profile.findings[0]
    match first.kind {
      QualityOk => "ready"
      QualityClipping => "reduce-gain-or-normalize"
      QualityMostlySilent => "trim-or-check-input"
      QualityVeryQuiet => "normalize-peak"
      QualityDcOffset => "remove-dc-offset"
      QualityHighCrest => "inspect-transients"
      QualityHighZeroCrossing => "inspect-noise"
    }
  }
}